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