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