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