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