Add _set_env writer to non-PSGI Catalyst
[catagits/Catalyst-Runtime.git] / lib / Catalyst / DispatchType / Chained.pm
CommitLineData
5882c86e 1package Catalyst::DispatchType::Chained;
141459fa 2
3c0186f2 3use Moose;
e8b9f2a9 4extends 'Catalyst::DispatchType';
5
141459fa 6use Text::SimpleTable;
7use Catalyst::ActionChain;
39fc2ce1 8use Catalyst::Utils;
141459fa 9use URI;
dd97c1ac 10use Scalar::Util ();
141459fa 11
be5cb4e4 12has _endpoints => (
13 is => 'rw',
14 isa => 'ArrayRef',
15 required => 1,
16 default => sub{ [] },
17 );
18
19has _actions => (
20 is => 'rw',
21 isa => 'HashRef',
22 required => 1,
23 default => sub{ {} },
24 );
25
26has _children_of => (
27 is => 'rw',
28 isa => 'HashRef',
29 required => 1,
30 default => sub{ {} },
31 );
32
0fc2d522 33no Moose;
34
792b40ac 35# please don't perltidy this. hairy code within.
36
141459fa 37=head1 NAME
38
5882c86e 39Catalyst::DispatchType::Chained - Path Part DispatchType
141459fa 40
41=head1 SYNOPSIS
42
26dc649a 43Path part matching, allowing several actions to sequentially take care of processing a request:
44
05a90578 45 # root action - captures one argument after it
46 sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
47 my ( $self, $c, $foo_arg ) = @_;
48 ...
49 }
50
51 # child action endpoint - takes one argument
52 sub bar : Chained('foo_setup') Args(1) {
53 my ( $self, $c, $bar_arg ) = @_;
54 ...
55 }
141459fa 56
57=head1 DESCRIPTION
58
26dc649a 59Dispatch type managing default behaviour. For more information on
60dispatch types, see:
61
62=over 4
63
b9b89145 64=item * L<Catalyst::Manual::Intro> for how they affect application authors
26dc649a 65
66=item * L<Catalyst::DispatchType> for implementation information.
67
68=back
05a90578 69
141459fa 70=head1 METHODS
71
72=head2 $self->list($c)
73
74Debug output for Path Part dispatch points
75
141459fa 76=cut
77
792b40ac 78sub list {
79 my ( $self, $c ) = @_;
80
be5cb4e4 81 return unless $self->_endpoints;
792b40ac 82
48d435ba 83 my $avail_width = Catalyst::Utils::term_width() - 9;
84 my $col1_width = ($avail_width * .50) < 35 ? 35 : int($avail_width * .50);
85 my $col2_width = $avail_width - $col1_width;
792b40ac 86 my $paths = Text::SimpleTable->new(
48d435ba 87 [ $col1_width, 'Path Spec' ], [ $col2_width, 'Private' ],
39fc2ce1 88 );
792b40ac 89
007a7ca0 90 my $has_unattached_actions;
91 my $unattached_actions = Text::SimpleTable->new(
48d435ba 92 [ $col1_width, 'Private' ], [ $col2_width, 'Missing parent' ],
007a7ca0 93 );
94
792b40ac 95 ENDPOINT: foreach my $endpoint (
96 sort { $a->reverse cmp $b->reverse }
be5cb4e4 97 @{ $self->_endpoints }
792b40ac 98 ) {
99 my $args = $endpoint->attributes->{Args}->[0];
100 my @parts = (defined($args) ? (("*") x $args) : '...');
d34667c3 101 my @parents = ();
792b40ac 102 my $parent = "DUMMY";
103 my $curr = $endpoint;
104 while ($curr) {
1c34f703 105 if (my $cap = $curr->attributes->{CaptureArgs}) {
792b40ac 106 unshift(@parts, (("*") x $cap->[0]));
107 }
77771155 108 if (my $pp = $curr->attributes->{PathPart}) {
792b40ac 109 unshift(@parts, $pp->[0])
110 if (defined $pp->[0] && length $pp->[0]);
111 }
5882c86e 112 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 113 $curr = $self->_actions->{$parent};
d34667c3 114 unshift(@parents, $curr) if $curr;
792b40ac 115 }
007a7ca0 116 if ($parent ne '/') {
117 $has_unattached_actions = 1;
59d5a638 118 $unattached_actions->row('/' . ($parents[0] || $endpoint)->reverse, $parent);
007a7ca0 119 next ENDPOINT;
120 }
d34667c3 121 my @rows;
122 foreach my $p (@parents) {
123 my $name = "/${p}";
1c34f703 124 if (my $cap = $p->attributes->{CaptureArgs}) {
d34667c3 125 $name .= ' ('.$cap->[0].')';
126 }
127 unless ($p eq $parents[0]) {
128 $name = "-> ${name}";
129 }
130 push(@rows, [ '', $name ]);
131 }
132 push(@rows, [ '', (@rows ? "=> " : '')."/${endpoint}" ]);
f4624073 133 $rows[0][0] = join('/', '', @parts) || '/';
d34667c3 134 $paths->row(@$_) for @rows;
792b40ac 135 }
136
1cf0345b 137 $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
007a7ca0 138 $c->log->debug( "Unattached Chained actions:\n", $unattached_actions->draw . "\n" )
139 if $has_unattached_actions;
792b40ac 140}
141459fa 141
142=head2 $self->match( $c, $path )
143
05a90578 144Calls C<recurse_match> to see if a chain matches the C<$path>.
141459fa 145
146=cut
147
148sub match {
149 my ( $self, $c, $path ) = @_;
150
e5ecd5bc 151 my $request = $c->request;
152 return 0 if @{$request->args};
141459fa 153
154 my @parts = split('/', $path);
155
6365b527 156 my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
634780e0 157
158 if ($parts && @$parts) {
159 for my $arg (@$parts) {
160 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
161 push @{$request->args}, $arg;
162 }
163 }
141459fa 164
165 return 0 unless $chain;
166
167 my $action = Catalyst::ActionChain->from_chain($chain);
168
e5ecd5bc 169 $request->action("/${action}");
170 $request->match("/${action}");
171 $request->captures($captures);
141459fa 172 $c->action($action);
173 $c->namespace( $action->namespace );
174
175 return 1;
176}
177
178=head2 $self->recurse_match( $c, $parent, \@path_parts )
179
05a90578 180Recursive search for a matching chain.
141459fa 181
182=cut
183
184sub recurse_match {
185 my ( $self, $c, $parent, $path_parts ) = @_;
be5cb4e4 186 my $children = $self->_children_of->{$parent};
141459fa 187 return () unless $children;
6b495723 188 my $best_action;
141459fa 189 my @captures;
1b04b972 190 TRY: foreach my $try_part (sort { length($b) <=> length($a) }
cdc97b63 191 keys %$children) {
1b04b972 192 # $b then $a to try longest part first
141459fa 193 my @parts = @$path_parts;
194 if (length $try_part) { # test and strip PathPart
195 next TRY unless
196 ($try_part eq join('/', # assemble equal number of parts
197 splice( # and strip them off @parts as well
792b40ac 198 @parts, 0, scalar(@{[split('/', $try_part)]})
199 ))); # @{[]} to avoid split to @_
141459fa 200 }
201 my @try_actions = @{$children->{$try_part}};
202 TRY_ACTION: foreach my $action (@try_actions) {
1c34f703 203 if (my $capture_attr = $action->attributes->{CaptureArgs}) {
f505df49 204
205 # Short-circuit if not enough remaining parts
206 next TRY_ACTION unless @parts >= $capture_attr->[0];
207
141459fa 208 my @captures;
209 my @parts = @parts; # localise
7a7ac23c 210
1c34f703 211 # strip CaptureArgs into list
7a7ac23c 212 push(@captures, splice(@parts, 0, $capture_attr->[0]));
213
141459fa 214 # try the remaining parts against children of this action
0c78acb6 215 my ($actions, $captures, $action_parts, $n_pathparts) = $self->recurse_match(
141459fa 216 $c, '/'.$action->reverse, \@parts
217 );
2f381252 218 # No best action currently
219 # OR The action has less parts
220 # OR The action has equal parts but less captured data (ergo more defined)
221 if ($actions &&
222 (!$best_action ||
f4dbb497 223 $#$action_parts < $#{$best_action->{parts}} ||
2f381252 224 ($#$action_parts == $#{$best_action->{parts}} &&
0c78acb6 225 $#$captures < $#{$best_action->{captures}} &&
226 $n_pathparts > $best_action->{n_pathparts}))) {
227 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
6b495723 228 $best_action = {
229 actions => [ $action, @$actions ],
230 captures=> [ @captures, @$captures ],
0c78acb6 231 parts => $action_parts,
232 n_pathparts => scalar(@pathparts) + $n_pathparts,
233 };
6b495723 234 }
235 }
236 else {
7a7ac23c 237 {
238 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
239 next TRY_ACTION unless $action->match($c);
240 }
5c6d829d 241 my $args_attr = $action->attributes->{Args}->[0];
0c78acb6 242 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
953c176d 243 # No best action currently
244 # OR This one matches with fewer parts left than the current best action,
245 # And therefore is a better match
b0ad47c1 246 # OR No parts and this expects 0
953c176d 247 # The current best action might also be Args(0),
248 # but we couldn't chose between then anyway so we'll take the last seen
249
250 if (!$best_action ||
251 @parts < @{$best_action->{parts}} ||
a8194217 252 (!@parts && $args_attr eq 0)){
6b495723 253 $best_action = {
254 actions => [ $action ],
255 captures=> [],
0c78acb6 256 parts => \@parts,
257 n_pathparts => scalar(@pathparts),
258 };
953c176d 259 }
141459fa 260 }
261 }
262 }
0c78acb6 263 return @$best_action{qw/actions captures parts n_pathparts/} if $best_action;
141459fa 264 return ();
265}
266
267=head2 $self->register( $c, $action )
268
05a90578 269Calls register_path for every Path attribute for the given $action.
141459fa 270
271=cut
272
273sub register {
274 my ( $self, $c, $action ) = @_;
275
1dc8af44 276 my @chained_attr = @{ $action->attributes->{Chained} || [] };
141459fa 277
1dc8af44 278 return 0 unless @chained_attr;
141459fa 279
2f381252 280 if (@chained_attr > 1) {
141459fa 281 Catalyst::Exception->throw(
5882c86e 282 "Multiple Chained attributes not supported registering ${action}"
141459fa 283 );
284 }
13c6b4cc 285 my $chained_to = $chained_attr[0];
141459fa 286
13c6b4cc 287 Catalyst::Exception->throw(
288 "Actions cannot chain to themselves registering /${action}"
289 ) if ($chained_to eq '/' . $action);
290
291 my $children = ($self->_children_of->{ $chained_to } ||= {});
141459fa 292
293 my @path_part = @{ $action->attributes->{PathPart} || [] };
294
09461385 295 my $part = $action->name;
141459fa 296
09461385 297 if (@path_part == 1 && defined $path_part[0]) {
298 $part = $path_part[0];
141459fa 299 } elsif (@path_part > 1) {
300 Catalyst::Exception->throw(
f3414019 301 "Multiple PathPart attributes not supported registering " . $action->reverse()
141459fa 302 );
303 }
304
8a6a6581 305 if ($part =~ m(^/)) {
306 Catalyst::Exception->throw(
f3414019 307 "Absolute parameters to PathPart not allowed registering " . $action->reverse()
8a6a6581 308 );
309 }
310
77771155 311 $action->attributes->{PathPart} = [ $part ];
792b40ac 312
141459fa 313 unshift(@{ $children->{$part} ||= [] }, $action);
314
be5cb4e4 315 $self->_actions->{'/'.$action->reverse} = $action;
792b40ac 316
dd97c1ac 317 if (exists $action->attributes->{Args}) {
318 my $args = $action->attributes->{Args}->[0];
319 if (defined($args) and not (
320 Scalar::Util::looks_like_number($args) and
321 int($args) == $args
322 )) {
323 require Data::Dumper;
324 local $Data::Dumper::Terse = 1;
325 local $Data::Dumper::Indent = 0;
326 $args = Data::Dumper::Dumper($args);
327 Catalyst::Exception->throw(
328 "Invalid Args($args) for action " . $action->reverse() .
329 " (use 'Args' or 'Args(<number>)'"
330 );
331 }
332 }
333
1c34f703 334 unless ($action->attributes->{CaptureArgs}) {
be5cb4e4 335 unshift(@{ $self->_endpoints }, $action);
792b40ac 336 }
337
338 return 1;
141459fa 339}
340
341=head2 $self->uri_for_action($action, $captures)
342
05a90578 343Get the URI part for the action, using C<$captures> to fill
344the capturing parts.
141459fa 345
346=cut
347
348sub uri_for_action {
349 my ( $self, $action, $captures ) = @_;
350
5882c86e 351 return undef unless ($action->attributes->{Chained}
8b13f357 352 && !$action->attributes->{CaptureArgs});
792b40ac 353
354 my @parts = ();
355 my @captures = @$captures;
356 my $parent = "DUMMY";
357 my $curr = $action;
358 while ($curr) {
1c34f703 359 if (my $cap = $curr->attributes->{CaptureArgs}) {
792b40ac 360 return undef unless @captures >= $cap->[0]; # not enough captures
8b13f357 361 if ($cap->[0]) {
f9155483 362 unshift(@parts, splice(@captures, -$cap->[0]));
8b13f357 363 }
792b40ac 364 }
77771155 365 if (my $pp = $curr->attributes->{PathPart}) {
792b40ac 366 unshift(@parts, $pp->[0])
8b13f357 367 if (defined($pp->[0]) && length($pp->[0]));
792b40ac 368 }
5882c86e 369 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 370 $curr = $self->_actions->{$parent};
141459fa 371 }
792b40ac 372
373 return undef unless $parent eq '/'; # fail for dangling action
374
375 return undef if @captures; # fail for too many captures
376
377 return join('/', '', @parts);
59d5a638 378
141459fa 379}
380
ae0e35ee 381=head2 $c->expand_action($action)
382
b0ad47c1 383Return a list of actions that represents a chained action. See
ae0e35ee 384L<Catalyst::Dispatcher> for more info. You probably want to
385use the expand_action it provides rather than this directly.
386
387=cut
388
52f71256 389sub expand_action {
390 my ($self, $action) = @_;
391
392 return unless $action->attributes && $action->attributes->{Chained};
393
394 my @chain;
395 my $curr = $action;
396
397 while ($curr) {
398 push @chain, $curr;
399 my $parent = $curr->attributes->{Chained}->[0];
400 $curr = $self->_actions->{$parent};
401 }
402
403 return Catalyst::ActionChain->from_chain([reverse @chain]);
404}
405
e5ecd5bc 406__PACKAGE__->meta->make_immutable;
407
05a90578 408=head1 USAGE
409
410=head2 Introduction
411
412The C<Chained> attribute allows you to chain public path parts together
67869327 413by their private names. A chain part's path can be specified with
414C<PathPart> and can be declared to expect an arbitrary number of
415arguments. The endpoint of the chain specifies how many arguments it
416gets through the C<Args> attribute. C<:Args(0)> would be none at all,
417C<:Args> without an integer would be unlimited. The path parts that
418aren't endpoints are using C<CaptureArgs> to specify how many parameters
419they expect to receive. As an example setup:
05a90578 420
421 package MyApp::Controller::Greeting;
422 use base qw/ Catalyst::Controller /;
423
424 # this is the beginning of our chain
425 sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
426 my ( $self, $c, $integer ) = @_;
427 $c->stash->{ message } = "Hello ";
428 $c->stash->{ arg_sum } = $integer;
429 }
430
431 # this is our endpoint, because it has no :CaptureArgs
432 sub world : PathPart('world') Chained('hello') Args(1) {
433 my ( $self, $c, $integer ) = @_;
434 $c->stash->{ message } .= "World!";
435 $c->stash->{ arg_sum } += $integer;
436
437 $c->response->body( join "<br/>\n" =>
438 $c->stash->{ message }, $c->stash->{ arg_sum } );
439 }
440
441The debug output provides a separate table for chained actions, showing
67869327 442the whole chain as it would match and the actions it contains. Here's an
443example of the startup output with our actions above:
05a90578 444
445 ...
446 [debug] Loaded Path Part actions:
447 .-----------------------+------------------------------.
448 | Path Spec | Private |
449 +-----------------------+------------------------------+
450 | /hello/*/world/* | /greeting/hello (1) |
451 | | => /greeting/world |
452 '-----------------------+------------------------------'
453 ...
454
67869327 455As you can see, Catalyst only deals with chains as whole paths and
456builds one for each endpoint, which are the actions with C<:Chained> but
457without C<:CaptureArgs>.
05a90578 458
459Let's assume this application gets a request at the path
67869327 460C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
461to the C<hello> action and pass the value C<23> as an argument to it
462after the context. It does so because we have previously used
463C<:CaptureArgs(1)> to declare that it has one path part after itself as
464its argument. We told Catalyst that this is the beginning of the chain
465by specifying C<:Chained('/')>. Also note that instead of saying
466C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
467defaults to the name of the action.
05a90578 468
469After C<hello> has run, Catalyst goes on to dispatch to the C<world>
67869327 470action. This is the last action to be called: Catalyst knows this is an
471endpoint because we did not specify a C<:CaptureArgs>
472attribute. Nevertheless we specify that this action expects an argument,
473but at this point we're using C<:Args(1)> to do that. We could also have
474said C<:Args> or left it out altogether, which would mean this action
475would get all arguments that are there. This action's C<:Chained>
476attribute says C<hello> and tells Catalyst that the C<hello> action in
477the current controller is its parent.
05a90578 478
479With this we have built a chain consisting of two public path parts.
67869327 480C<hello> captures one part of the path as its argument, and also
481specifies the path root as its parent. So this part is
482C</hello/$arg>. The next part is the endpoint C<world>, expecting one
483argument. It sums up to the path part C<world/$arg>. This leads to a
484complete chain of C</hello/$arg/world/$arg> which is matched against the
485requested paths.
486
487This example application would, if run and called by e.g.
488C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
489value C<arg_sum> to "23". The C<world> action would then append "World!"
490to C<message> and add C<12> to the stash's C<arg_sum> value. For the
491sake of simplicity no view is shown. Instead we just put the values of
492the stash into our body. So the output would look like:
05a90578 493
494 Hello World!
495 35
496
67869327 497And our test server would have given us this debugging output for the
05a90578 498request:
499
500 ...
501 [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
502 [debug] Path is "/greeting/world"
503 [debug] Arguments are "12"
504 [info] Request took 0.164113s (6.093/s)
505 .------------------------------------------+-----------.
506 | Action | Time |
507 +------------------------------------------+-----------+
508 | /greeting/hello | 0.000029s |
509 | /greeting/world | 0.000024s |
510 '------------------------------------------+-----------'
511 ...
512
67869327 513What would be common uses of this dispatch technique? It gives the
514possibility to split up logic that contains steps that each depend on
515each other. An example would be, for example, a wiki path like
05a90578 516C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
517these actions:
518
519 sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
520 my ( $self, $c, $page_name ) = @_;
521 # load the page named $page_name and put the object
522 # into the stash
523 }
524
525 sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
526 my ( $self, $c, $revision_id ) = @_;
67869327 527 # use the page object in the stash to get at its
05a90578 528 # revision with number $revision_id
529 }
530
531 sub view : PathPart Chained('rev') Args(0) {
532 my ( $self, $c ) = @_;
67869327 533 # display the revision in our stash. Another option
05a90578 534 # would be to forward a compatible object to the action
535 # that displays the default wiki pages, unless we want
536 # a different interface here, for example restore
537 # functionality.
538 }
539
67869327 540It would now be possible to add other endpoints, for example C<restore>
541to restore this specific revision as the current state.
05a90578 542
67869327 543You don't have to put all the chained actions in one controller. The
544specification of the parent through C<:Chained> also takes an absolute
545action path as its argument. Just specify it with a leading C</>.
05a90578 546
547If you want, for example, to have actions for the public paths
67869327 548C</foo/12/edit> and C</foo/12>, just specify two actions with
05a90578 549C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
67869327 550path needs a C<:CaptureArgs(1)> attribute and a endpoint with
05a90578 551C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
552the action just a C<:Args(1)> to mark it as endpoint. This sums up to
553this debugging output:
554
555 ...
556 [debug] Loaded Path Part actions:
557 .-----------------------+------------------------------.
558 | Path Spec | Private |
559 +-----------------------+------------------------------+
560 | /foo/* | /controller/foo_view |
561 | /foo/*/edit | /controller/foo_load (1) |
562 | | => /controller/edit |
563 '-----------------------+------------------------------'
564 ...
565
b0ad47c1 566Here's a more detailed specification of the attributes belonging to
05a90578 567C<:Chained>:
568
569=head2 Attributes
570
571=over 8
572
573=item PathPart
574
575Sets the name of this part of the chain. If it is specified without
576arguments, it takes the name of the action as default. So basically
577C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
578This can also contain slashes to bind to a deeper level. An action
579with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
580C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
581effect as using C<:PathPart>, it would default to the action name.
582
d21a2b27 583=item PathPrefix
584
585Sets PathPart to the path_prefix of the current controller.
586
05a90578 587=item Chained
588
589Has to be specified for every child in the chain. Possible values are
d21a2b27 590absolute and relative private action paths or a single slash C</> to
591tell Catalyst that this is the root of a chain. The attribute
592C<:Chained> without arguments also defaults to the C</> behavior.
593Relative action paths may use C<../> to refer to actions in parent
594controllers.
05a90578 595
67869327 596Because you can specify an absolute path to the parent action, it
597doesn't matter to Catalyst where that parent is located. So, if your
598design requests it, you can redispatch a chain through any controller or
599namespace you want.
05a90578 600
601Another interesting possibility gives C<:Chained('.')>, which chains
67869327 602itself to an action with the path of the current controller's namespace.
05a90578 603For example:
604
605 # in MyApp::Controller::Foo
606 sub bar : Chained CaptureArgs(1) { ... }
607
608 # in MyApp::Controller::Foo::Bar
609 sub baz : Chained('.') Args(1) { ... }
610
611This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
67869327 612as the argument to Chained here chains the C<baz> action to an action
613with the path of the current controller namespace, namely
614C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
615chain comes out as the end product.
05a90578 616
d21a2b27 617=item ChainedParent
618
619Chains an action to another action with the same name in the parent
620controller. For Example:
621
622 # in MyApp::Controller::Foo
623 sub bar : Chained CaptureArgs(1) { ... }
624
625 # in MyApp::Controller::Foo::Moo
626 sub bar : ChainedParent Args(1) { ... }
627
628This builds a chain like C</bar/*/bar/*>.
629
05a90578 630=item CaptureArgs
631
67869327 632Must be specified for every part of the chain that is not an
05a90578 633endpoint. With this attribute Catalyst knows how many of the following
67869327 634parts of the path (separated by C</>) this action wants to capture as
635its arguments. If it doesn't expect any, just specify
636C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
637after the context, but you can also find them as array references in
05a90578 638C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
639level of the action in the chain that captured the parts of the path.
640
67869327 641An action that is part of a chain (that is, one that has a C<:Chained>
642attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
643as a chain end.
05a90578 644
645=item Args
646
647By default, endpoints receive the rest of the arguments in the path. You
648can tell Catalyst through C<:Args> explicitly how many arguments your
649endpoint expects, just like you can with C<:CaptureArgs>. Note that this
67869327 650also affects whether this chain is invoked on a request. A chain with an
05a90578 651endpoint specifying one argument will only match if exactly one argument
652exists in the path.
653
654You can specify an exact number of arguments like C<:Args(3)>, including
655C<0>. If you just say C<:Args> without any arguments, it is the same as
67869327 656leaving it out altogether: The chain is matched regardless of the number
05a90578 657of path parts after the endpoint.
658
67869327 659Just as with C<:CaptureArgs>, the arguments get passed to the action in
05a90578 660C<@_> after the context object. They can also be reached through
661C<$c-E<gt>request-E<gt>arguments>.
662
663=back
664
67869327 665=head2 Auto actions, dispatching and forwarding
05a90578 666
667Note that the list of C<auto> actions called depends on the private path
67869327 668of the endpoint of the chain, not on the chained actions way. The
669C<auto> actions will be run before the chain dispatching begins. In
670every other aspect, C<auto> actions behave as documented.
05a90578 671
672The C<forward>ing to other actions does just what you would expect. But if
673you C<detach> out of a chain, the rest of the chain will not get called
67869327 674after the C<detach>.
05a90578 675
2f381252 676=head1 AUTHORS
141459fa 677
2f381252 678Catalyst Contributors, see Catalyst.pm
141459fa 679
680=head1 COPYRIGHT
681
536bee89 682This library is free software. You can redistribute it and/or modify it under
141459fa 683the same terms as Perl itself.
684
685=cut
686
6871;