Remove r11058 - the commit message is lies, it does not fix the TODO test
[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 }
108 if (my $pp = $curr->attributes->{PartPath}) {
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
6365b527 215 my ($actions, $captures, $action_parts) = $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 ||
223 $#$action_parts < $#{$best_action->{parts}} ||
224 ($#$action_parts == $#{$best_action->{parts}} &&
225 $#$captures < $#{$best_action->{captures}}))){
6b495723 226 $best_action = {
227 actions => [ $action, @$actions ],
228 captures=> [ @captures, @$captures ],
229 parts => $action_parts
230 };
231 }
232 }
233 else {
7a7ac23c 234 {
235 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
236 next TRY_ACTION unless $action->match($c);
237 }
f4fba3d7 238
239 # Default args is blank, not 0, since Args()
67e9673e 240 my $args_attr = $action->attributes->{Args}->[0] || '';
953c176d 241
242 # No best action currently
243 # OR This one matches with fewer parts left than the current best action,
244 # And therefore is a better match
b0ad47c1 245 # OR No parts and this expects 0
953c176d 246 # The current best action might also be Args(0),
247 # but we couldn't chose between then anyway so we'll take the last seen
248
249 if (!$best_action ||
250 @parts < @{$best_action->{parts}} ||
a8194217 251 (!@parts && $args_attr eq 0)){
6b495723 252 $best_action = {
253 actions => [ $action ],
254 captures=> [],
255 parts => \@parts
6b495723 256 }
953c176d 257 }
141459fa 258 }
259 }
260 }
953c176d 261 return @$best_action{qw/actions captures parts/} if $best_action;
141459fa 262 return ();
263}
264
265=head2 $self->register( $c, $action )
266
05a90578 267Calls register_path for every Path attribute for the given $action.
141459fa 268
269=cut
270
271sub register {
272 my ( $self, $c, $action ) = @_;
273
1dc8af44 274 my @chained_attr = @{ $action->attributes->{Chained} || [] };
141459fa 275
1dc8af44 276 return 0 unless @chained_attr;
141459fa 277
2f381252 278 if (@chained_attr > 1) {
141459fa 279 Catalyst::Exception->throw(
5882c86e 280 "Multiple Chained attributes not supported registering ${action}"
141459fa 281 );
282 }
13c6b4cc 283 my $chained_to = $chained_attr[0];
141459fa 284
13c6b4cc 285 Catalyst::Exception->throw(
286 "Actions cannot chain to themselves registering /${action}"
287 ) if ($chained_to eq '/' . $action);
288
289 my $children = ($self->_children_of->{ $chained_to } ||= {});
141459fa 290
291 my @path_part = @{ $action->attributes->{PathPart} || [] };
292
09461385 293 my $part = $action->name;
141459fa 294
09461385 295 if (@path_part == 1 && defined $path_part[0]) {
296 $part = $path_part[0];
141459fa 297 } elsif (@path_part > 1) {
298 Catalyst::Exception->throw(
f3414019 299 "Multiple PathPart attributes not supported registering " . $action->reverse()
141459fa 300 );
301 }
302
8a6a6581 303 if ($part =~ m(^/)) {
304 Catalyst::Exception->throw(
f3414019 305 "Absolute parameters to PathPart not allowed registering " . $action->reverse()
8a6a6581 306 );
307 }
308
792b40ac 309 $action->attributes->{PartPath} = [ $part ];
310
141459fa 311 unshift(@{ $children->{$part} ||= [] }, $action);
312
be5cb4e4 313 $self->_actions->{'/'.$action->reverse} = $action;
792b40ac 314
dd97c1ac 315 if (exists $action->attributes->{Args}) {
316 my $args = $action->attributes->{Args}->[0];
317 if (defined($args) and not (
318 Scalar::Util::looks_like_number($args) and
319 int($args) == $args
320 )) {
321 require Data::Dumper;
322 local $Data::Dumper::Terse = 1;
323 local $Data::Dumper::Indent = 0;
324 $args = Data::Dumper::Dumper($args);
325 Catalyst::Exception->throw(
326 "Invalid Args($args) for action " . $action->reverse() .
327 " (use 'Args' or 'Args(<number>)'"
328 );
329 }
330 }
331
1c34f703 332 unless ($action->attributes->{CaptureArgs}) {
be5cb4e4 333 unshift(@{ $self->_endpoints }, $action);
792b40ac 334 }
335
336 return 1;
141459fa 337}
338
339=head2 $self->uri_for_action($action, $captures)
340
05a90578 341Get the URI part for the action, using C<$captures> to fill
342the capturing parts.
141459fa 343
344=cut
345
346sub uri_for_action {
347 my ( $self, $action, $captures ) = @_;
348
5882c86e 349 return undef unless ($action->attributes->{Chained}
8b13f357 350 && !$action->attributes->{CaptureArgs});
792b40ac 351
352 my @parts = ();
353 my @captures = @$captures;
354 my $parent = "DUMMY";
355 my $curr = $action;
356 while ($curr) {
1c34f703 357 if (my $cap = $curr->attributes->{CaptureArgs}) {
792b40ac 358 return undef unless @captures >= $cap->[0]; # not enough captures
8b13f357 359 if ($cap->[0]) {
f9155483 360 unshift(@parts, splice(@captures, -$cap->[0]));
8b13f357 361 }
792b40ac 362 }
363 if (my $pp = $curr->attributes->{PartPath}) {
364 unshift(@parts, $pp->[0])
8b13f357 365 if (defined($pp->[0]) && length($pp->[0]));
792b40ac 366 }
5882c86e 367 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 368 $curr = $self->_actions->{$parent};
141459fa 369 }
792b40ac 370
371 return undef unless $parent eq '/'; # fail for dangling action
372
373 return undef if @captures; # fail for too many captures
374
375 return join('/', '', @parts);
59d5a638 376
141459fa 377}
378
ae0e35ee 379=head2 $c->expand_action($action)
380
b0ad47c1 381Return a list of actions that represents a chained action. See
ae0e35ee 382L<Catalyst::Dispatcher> for more info. You probably want to
383use the expand_action it provides rather than this directly.
384
385=cut
386
52f71256 387sub expand_action {
388 my ($self, $action) = @_;
389
390 return unless $action->attributes && $action->attributes->{Chained};
391
392 my @chain;
393 my $curr = $action;
394
395 while ($curr) {
396 push @chain, $curr;
397 my $parent = $curr->attributes->{Chained}->[0];
398 $curr = $self->_actions->{$parent};
399 }
400
401 return Catalyst::ActionChain->from_chain([reverse @chain]);
402}
403
e5ecd5bc 404__PACKAGE__->meta->make_immutable;
405
05a90578 406=head1 USAGE
407
408=head2 Introduction
409
410The C<Chained> attribute allows you to chain public path parts together
67869327 411by their private names. A chain part's path can be specified with
412C<PathPart> and can be declared to expect an arbitrary number of
413arguments. The endpoint of the chain specifies how many arguments it
414gets through the C<Args> attribute. C<:Args(0)> would be none at all,
415C<:Args> without an integer would be unlimited. The path parts that
416aren't endpoints are using C<CaptureArgs> to specify how many parameters
417they expect to receive. As an example setup:
05a90578 418
419 package MyApp::Controller::Greeting;
420 use base qw/ Catalyst::Controller /;
421
422 # this is the beginning of our chain
423 sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
424 my ( $self, $c, $integer ) = @_;
425 $c->stash->{ message } = "Hello ";
426 $c->stash->{ arg_sum } = $integer;
427 }
428
429 # this is our endpoint, because it has no :CaptureArgs
430 sub world : PathPart('world') Chained('hello') Args(1) {
431 my ( $self, $c, $integer ) = @_;
432 $c->stash->{ message } .= "World!";
433 $c->stash->{ arg_sum } += $integer;
434
435 $c->response->body( join "<br/>\n" =>
436 $c->stash->{ message }, $c->stash->{ arg_sum } );
437 }
438
439The debug output provides a separate table for chained actions, showing
67869327 440the whole chain as it would match and the actions it contains. Here's an
441example of the startup output with our actions above:
05a90578 442
443 ...
444 [debug] Loaded Path Part actions:
445 .-----------------------+------------------------------.
446 | Path Spec | Private |
447 +-----------------------+------------------------------+
448 | /hello/*/world/* | /greeting/hello (1) |
449 | | => /greeting/world |
450 '-----------------------+------------------------------'
451 ...
452
67869327 453As you can see, Catalyst only deals with chains as whole paths and
454builds one for each endpoint, which are the actions with C<:Chained> but
455without C<:CaptureArgs>.
05a90578 456
457Let's assume this application gets a request at the path
67869327 458C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
459to the C<hello> action and pass the value C<23> as an argument to it
460after the context. It does so because we have previously used
461C<:CaptureArgs(1)> to declare that it has one path part after itself as
462its argument. We told Catalyst that this is the beginning of the chain
463by specifying C<:Chained('/')>. Also note that instead of saying
464C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
465defaults to the name of the action.
05a90578 466
467After C<hello> has run, Catalyst goes on to dispatch to the C<world>
67869327 468action. This is the last action to be called: Catalyst knows this is an
469endpoint because we did not specify a C<:CaptureArgs>
470attribute. Nevertheless we specify that this action expects an argument,
471but at this point we're using C<:Args(1)> to do that. We could also have
472said C<:Args> or left it out altogether, which would mean this action
473would get all arguments that are there. This action's C<:Chained>
474attribute says C<hello> and tells Catalyst that the C<hello> action in
475the current controller is its parent.
05a90578 476
477With this we have built a chain consisting of two public path parts.
67869327 478C<hello> captures one part of the path as its argument, and also
479specifies the path root as its parent. So this part is
480C</hello/$arg>. The next part is the endpoint C<world>, expecting one
481argument. It sums up to the path part C<world/$arg>. This leads to a
482complete chain of C</hello/$arg/world/$arg> which is matched against the
483requested paths.
484
485This example application would, if run and called by e.g.
486C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
487value C<arg_sum> to "23". The C<world> action would then append "World!"
488to C<message> and add C<12> to the stash's C<arg_sum> value. For the
489sake of simplicity no view is shown. Instead we just put the values of
490the stash into our body. So the output would look like:
05a90578 491
492 Hello World!
493 35
494
67869327 495And our test server would have given us this debugging output for the
05a90578 496request:
497
498 ...
499 [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
500 [debug] Path is "/greeting/world"
501 [debug] Arguments are "12"
502 [info] Request took 0.164113s (6.093/s)
503 .------------------------------------------+-----------.
504 | Action | Time |
505 +------------------------------------------+-----------+
506 | /greeting/hello | 0.000029s |
507 | /greeting/world | 0.000024s |
508 '------------------------------------------+-----------'
509 ...
510
67869327 511What would be common uses of this dispatch technique? It gives the
512possibility to split up logic that contains steps that each depend on
513each other. An example would be, for example, a wiki path like
05a90578 514C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
515these actions:
516
517 sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
518 my ( $self, $c, $page_name ) = @_;
519 # load the page named $page_name and put the object
520 # into the stash
521 }
522
523 sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
524 my ( $self, $c, $revision_id ) = @_;
67869327 525 # use the page object in the stash to get at its
05a90578 526 # revision with number $revision_id
527 }
528
529 sub view : PathPart Chained('rev') Args(0) {
530 my ( $self, $c ) = @_;
67869327 531 # display the revision in our stash. Another option
05a90578 532 # would be to forward a compatible object to the action
533 # that displays the default wiki pages, unless we want
534 # a different interface here, for example restore
535 # functionality.
536 }
537
67869327 538It would now be possible to add other endpoints, for example C<restore>
539to restore this specific revision as the current state.
05a90578 540
67869327 541You don't have to put all the chained actions in one controller. The
542specification of the parent through C<:Chained> also takes an absolute
543action path as its argument. Just specify it with a leading C</>.
05a90578 544
545If you want, for example, to have actions for the public paths
67869327 546C</foo/12/edit> and C</foo/12>, just specify two actions with
05a90578 547C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
67869327 548path needs a C<:CaptureArgs(1)> attribute and a endpoint with
05a90578 549C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
550the action just a C<:Args(1)> to mark it as endpoint. This sums up to
551this debugging output:
552
553 ...
554 [debug] Loaded Path Part actions:
555 .-----------------------+------------------------------.
556 | Path Spec | Private |
557 +-----------------------+------------------------------+
558 | /foo/* | /controller/foo_view |
559 | /foo/*/edit | /controller/foo_load (1) |
560 | | => /controller/edit |
561 '-----------------------+------------------------------'
562 ...
563
b0ad47c1 564Here's a more detailed specification of the attributes belonging to
05a90578 565C<:Chained>:
566
567=head2 Attributes
568
569=over 8
570
571=item PathPart
572
573Sets the name of this part of the chain. If it is specified without
574arguments, it takes the name of the action as default. So basically
575C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
576This can also contain slashes to bind to a deeper level. An action
577with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
578C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
579effect as using C<:PathPart>, it would default to the action name.
580
d21a2b27 581=item PathPrefix
582
583Sets PathPart to the path_prefix of the current controller.
584
05a90578 585=item Chained
586
587Has to be specified for every child in the chain. Possible values are
d21a2b27 588absolute and relative private action paths or a single slash C</> to
589tell Catalyst that this is the root of a chain. The attribute
590C<:Chained> without arguments also defaults to the C</> behavior.
591Relative action paths may use C<../> to refer to actions in parent
592controllers.
05a90578 593
67869327 594Because you can specify an absolute path to the parent action, it
595doesn't matter to Catalyst where that parent is located. So, if your
596design requests it, you can redispatch a chain through any controller or
597namespace you want.
05a90578 598
599Another interesting possibility gives C<:Chained('.')>, which chains
67869327 600itself to an action with the path of the current controller's namespace.
05a90578 601For example:
602
603 # in MyApp::Controller::Foo
604 sub bar : Chained CaptureArgs(1) { ... }
605
606 # in MyApp::Controller::Foo::Bar
607 sub baz : Chained('.') Args(1) { ... }
608
609This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
67869327 610as the argument to Chained here chains the C<baz> action to an action
611with the path of the current controller namespace, namely
612C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
613chain comes out as the end product.
05a90578 614
d21a2b27 615=item ChainedParent
616
617Chains an action to another action with the same name in the parent
618controller. For Example:
619
620 # in MyApp::Controller::Foo
621 sub bar : Chained CaptureArgs(1) { ... }
622
623 # in MyApp::Controller::Foo::Moo
624 sub bar : ChainedParent Args(1) { ... }
625
626This builds a chain like C</bar/*/bar/*>.
627
05a90578 628=item CaptureArgs
629
67869327 630Must be specified for every part of the chain that is not an
05a90578 631endpoint. With this attribute Catalyst knows how many of the following
67869327 632parts of the path (separated by C</>) this action wants to capture as
633its arguments. If it doesn't expect any, just specify
634C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
635after the context, but you can also find them as array references in
05a90578 636C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
637level of the action in the chain that captured the parts of the path.
638
67869327 639An action that is part of a chain (that is, one that has a C<:Chained>
640attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
641as a chain end.
05a90578 642
643=item Args
644
645By default, endpoints receive the rest of the arguments in the path. You
646can tell Catalyst through C<:Args> explicitly how many arguments your
647endpoint expects, just like you can with C<:CaptureArgs>. Note that this
67869327 648also affects whether this chain is invoked on a request. A chain with an
05a90578 649endpoint specifying one argument will only match if exactly one argument
650exists in the path.
651
652You can specify an exact number of arguments like C<:Args(3)>, including
653C<0>. If you just say C<:Args> without any arguments, it is the same as
67869327 654leaving it out altogether: The chain is matched regardless of the number
05a90578 655of path parts after the endpoint.
656
67869327 657Just as with C<:CaptureArgs>, the arguments get passed to the action in
05a90578 658C<@_> after the context object. They can also be reached through
659C<$c-E<gt>request-E<gt>arguments>.
660
661=back
662
67869327 663=head2 Auto actions, dispatching and forwarding
05a90578 664
665Note that the list of C<auto> actions called depends on the private path
67869327 666of the endpoint of the chain, not on the chained actions way. The
667C<auto> actions will be run before the chain dispatching begins. In
668every other aspect, C<auto> actions behave as documented.
05a90578 669
670The C<forward>ing to other actions does just what you would expect. But if
671you C<detach> out of a chain, the rest of the chain will not get called
67869327 672after the C<detach>.
05a90578 673
2f381252 674=head1 AUTHORS
141459fa 675
2f381252 676Catalyst Contributors, see Catalyst.pm
141459fa 677
678=head1 COPYRIGHT
679
536bee89 680This library is free software. You can redistribute it and/or modify it under
141459fa 681the same terms as Perl itself.
682
683=cut
684
6851;