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