Merge branch 'grim8634-104-path_empty_brackets't push origin master
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Action.pm
CommitLineData
fbcc39ad 1package Catalyst::Action;
2
b2ddf6d7 3=head1 NAME
4
5Catalyst::Action - Catalyst Action
6
7=head1 SYNOPSIS
8
804fb55d 9 <form action="[%c.uri_for(c.action)%]">
85d9fce6 10
009b5b23 11 $c->forward( $action->private_path );
12
b2ddf6d7 13=head1 DESCRIPTION
14
43c58153 15This class represents a Catalyst Action. You can access the object for the
b2ddf6d7 16currently dispatched action via $c->action. See the L<Catalyst::Dispatcher>
17for more information on how actions are dispatched. Actions are defined in
18L<Catalyst::Controller> subclasses.
19
20=cut
21
059c085b 22use Moose;
59051400 23use Scalar::Util 'looks_like_number', 'blessed';
6d62355b 24use Moose::Util::TypeConstraints ();
241edc9b 25with 'MooseX::Emulate::Class::Accessor::Fast';
05b47f2e 26use namespace::clean -except => 'meta';
241edc9b 27
5fb12dbb 28has class => (is => 'rw');
29has namespace => (is => 'rw');
30has 'reverse' => (is => 'rw');
31has attributes => (is => 'rw');
32has name => (is => 'rw');
33has code => (is => 'rw');
009b5b23 34has private_path => (
35 reader => 'private_path',
36 isa => 'Str',
37 lazy => 1,
38 required => 1,
39 default => sub { '/'.shift->reverse },
40);
059c085b 41
81436df9 42has number_of_args => (
43 is=>'ro',
44 init_arg=>undef,
45 isa=>'Int|Undef',
46 required=>1,
47 lazy=>1,
48 builder=>'_build_number_of_args');
49
50 sub _build_number_of_args {
51 my $self = shift;
d4e8996f 52 if( ! exists $self->attributes->{Args} ) {
53 # When 'Args' does not exist, that means we want 'any number of args'.
54 return undef;
43b44b3a 55 } elsif(
56 !defined($self->attributes->{Args}[0]) ||
57 $self->attributes->{Args}[0] eq '' ) {
81436df9 58 # When its 'Args' that internal cue for 'unlimited'
59 return undef;
4a0218ca 60 } elsif(
61 scalar(@{$self->attributes->{Args}}) == 1 &&
62 looks_like_number($self->attributes->{Args}[0])
63 ) {
a7ab9aa9 64 # 'Old school' numbered args (is allowed to be undef as well)
81436df9 65 return $self->attributes->{Args}[0];
66 } else {
d4e8996f 67 # New hotness named arg constraints
81436df9 68 return $self->number_of_args_constraints;
69 }
70 }
71
d4e8996f 72sub normalized_arg_number {
73 return defined($_[0]->number_of_args) ? $_[0]->number_of_args : ~0;
74}
75
bf4f1643 76has number_of_args_constraints => (
77 is=>'ro',
78 isa=>'Int|Undef',
79 init_arg=>undef,
80 required=>1,
81 lazy=>1,
82 builder=>'_build_number_of_args_constraints');
83
84 sub _build_number_of_args_constraints {
85 my $self = shift;
86 return unless $self->has_args_constraints;
87
d9f0a350 88 # If there is one constraint and its a ref, we need to decide
89 # if this number 'unknown' number or if the ref allows us to
90 # determine a length.
91
92 if(scalar @{$self->args_constraints} == 1) {
93 my $tc = $self->args_constraints->[0];
94 if(
95 $tc->can('is_strictly_a_type_of') &&
96 $tc->is_strictly_a_type_of('Tuple'))
97 {
98 my @parameters = @{ $tc->parameters||[]};
99 if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
bf4f1643 100 return undef;
d9f0a350 101 } else {
102 return my $total_params = scalar(@parameters);
bf4f1643 103 }
d9f0a350 104 } elsif($tc->is_a_type_of('Ref')) {
105 return undef;
bf4f1643 106 } else {
d9f0a350 107 return 1; # Its a normal 1 arg type constraint.
bf4f1643 108 }
d9f0a350 109 } else {
79fb8f95 110 # We need to loop through and error on ref types. We don't allow a ref type
d9f0a350 111 # in the middle.
112 my $total = 0;
113 foreach my $tc( @{$self->args_constraints}) {
114 if($tc->is_a_type_of('Ref')) {
115 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in Args for action ${\$self->reverse}";
116 } else {
117 ++$total;
118 }
119 }
120 return $total;
bf4f1643 121 }
bf4f1643 122 }
123
6d62355b 124has args_constraints => (
125 is=>'ro',
81436df9 126 init_arg=>undef,
6d62355b 127 traits=>['Array'],
128 isa=>'ArrayRef',
129 required=>1,
130 lazy=>1,
131 builder=>'_build_args_constraints',
132 handles => {
133 has_args_constraints => 'count',
bf4f1643 134 args_constraint_count => 'count',
6d62355b 135 });
136
137 sub _build_args_constraints {
138 my $self = shift;
139 my @arg_protos = @{$self->attributes->{Args}||[]};
140
141 return [] unless scalar(@arg_protos);
79b7db20 142 return [] unless defined($arg_protos[0]);
43b44b3a 143 return [] if ($arg_protos[0] eq '' && scalar(@arg_protos) == 1);
79b7db20 144
6d62355b 145 # If there is only one arg and it looks like a number
146 # we assume its 'classic' and the number is the number of
147 # constraints.
148 my @args = ();
149 if(
150 scalar(@arg_protos) == 1 &&
151 looks_like_number($arg_protos[0])
152 ) {
81436df9 153 return \@args;
6d62355b 154 } else {
4a0218ca 155 @args =
bf4f1643 156 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
337a627a 157 @arg_protos;
6d62355b 158 }
6d62355b 159 return \@args;
160 }
161
bf4f1643 162has number_of_captures_constraints => (
163 is=>'ro',
164 isa=>'Int|Undef',
165 init_arg=>undef,
166 required=>1,
167 lazy=>1,
168 builder=>'_build_number_of_capture_constraints');
169
170 sub _build_number_of_capture_constraints {
171 my $self = shift;
172 return unless $self->has_captures_constraints;
173
d9f0a350 174 # If there is one constraint and its a ref, we need to decide
175 # if this number 'unknown' number or if the ref allows us to
176 # determine a length.
177
178 if(scalar @{$self->captures_constraints} == 1) {
179 my $tc = $self->captures_constraints->[0];
180 if(
181 $tc->can('is_strictly_a_type_of') &&
182 $tc->is_strictly_a_type_of('Tuple'))
183 {
184 my @parameters = @{ $tc->parameters||[]};
185 if( defined($parameters[-1]) and exists($parameters[-1]->{slurpy})) {
186 return undef;
bf4f1643 187 } else {
d9f0a350 188 return my $total_params = scalar(@parameters);
bf4f1643 189 }
d9f0a350 190 } elsif($tc->is_a_type_of('Ref')) {
191 die "You cannot use CaptureArgs($tc) in ${\$self->reverse} because we cannot determined the number of its parameters";
bf4f1643 192 } else {
d9f0a350 193 return 1; # Its a normal 1 arg type constraint.
194 }
195 } else {
79fb8f95 196 # We need to loop through and error on ref types. We don't allow a ref type
d9f0a350 197 # in the middle.
198 my $total = 0;
199 foreach my $tc( @{$self->captures_constraints}) {
200 if($tc->is_a_type_of('Ref')) {
201 die "$tc is a Ref type constraint. You cannot mix Ref and non Ref type constraints in CaptureArgs for action ${\$self->reverse}";
202 } else {
203 ++$total;
204 }
bf4f1643 205 }
d9f0a350 206 return $total;
bf4f1643 207 }
bf4f1643 208 }
209
a82c96cf 210has captures_constraints => (
211 is=>'ro',
212 init_arg=>undef,
213 traits=>['Array'],
214 isa=>'ArrayRef',
215 required=>1,
216 lazy=>1,
217 builder=>'_build_captures_constraints',
218 handles => {
219 has_captures_constraints => 'count',
bf4f1643 220 captures_constraints_count => 'count',
a82c96cf 221 });
222
223 sub _build_captures_constraints {
224 my $self = shift;
225 my @arg_protos = @{$self->attributes->{CaptureArgs}||[]};
226
227 return [] unless scalar(@arg_protos);
79b7db20 228 return [] unless defined($arg_protos[0]);
a82c96cf 229 # If there is only one arg and it looks like a number
230 # we assume its 'classic' and the number is the number of
231 # constraints.
232 my @args = ();
233 if(
234 scalar(@arg_protos) == 1 &&
235 looks_like_number($arg_protos[0])
236 ) {
237 return \@args;
238 } else {
239 @args =
bf4f1643 240 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
a82c96cf 241 @arg_protos;
242 }
243
244 return \@args;
245 }
246
842180f7 247sub resolve_type_constraint {
248 my ($self, $name) = @_;
59051400 249
250 if(defined($name) && blessed($name) && $name->can('check')) {
251 # Its already a TC, good to go.
252 return $name;
253 }
254
d2995a76 255 # This is broken for when there is more than one constraint
59051400 256 if($name=~m/::/) {
257 eval "use Type::Registry; 1" || die "Can't resolve type constraint $name without installing Type::Tiny";
258 my $tc = Type::Registry->new->foreign_lookup($name);
d2995a76 259 return defined $tc ? $tc : die "'$name' not a full namespace type constraint in ${\$self->private_path}";
59051400 260 }
d2995a76 261
262 my @tc = grep { defined $_ } (eval("package ${\$self->class}; $name"));
59051400 263
d2995a76 264 unless(scalar @tc) {
59051400 265 # ok... so its not defined in the package. we need to look at all the roles
266 # and superclasses, look for attributes and figure it out.
267 # Superclasses take precedence;
d2995a76 268
a521afcc 269 my @supers = $self->class->can('meta') ? map { $_->meta } $self->class->meta->superclasses : ();
270 my @roles = $self->class->can('meta') ? $self->class->meta->calculate_all_roles : ();
59051400 271
79fb8f95 272 # So look through all the super and roles in order and return the
59051400 273 # first type constraint found. We should probably find all matching
274 # type constraints and try to do some sort of resolution.
275
276 foreach my $parent (@roles, @supers) {
277 if(my $m = $parent->get_method($self->name)) {
278 if($m->can('attributes')) {
279 my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*(.+?)\s*\))?$/ }
280 grep { $_=~/^Args\(/ or $_=~/^CaptureArgs\(/ }
281 @{$m->attributes};
282 next unless $value eq $name;
283 my @tc = eval "package ${\$parent->name}; $name";
d2995a76 284 if(scalar(@tc)) {
285 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
286 } else {
287 return;
288 }
289 }
59051400 290 }
291 }
a521afcc 292
293 my $classes = join(',', $self->class, @roles, @supers);
294 die "'$name' not a type constraint in '${\$self->private_path}', Looked in: $classes";
d2995a76 295 }
59051400 296
d2995a76 297 if(scalar(@tc)) {
75ce30d0 298 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
299 } else {
300 return;
301 }
842180f7 302}
303
a82c96cf 304has number_of_captures => (
305 is=>'ro',
306 init_arg=>undef,
307 isa=>'Int',
308 required=>1,
309 lazy=>1,
310 builder=>'_build_number_of_captures');
311
312 sub _build_number_of_captures {
313 my $self = shift;
314 if( ! exists $self->attributes->{CaptureArgs} ) {
315 # If there are no defined capture args, thats considered 0.
316 return 0;
317 } elsif(!defined($self->attributes->{CaptureArgs}[0])) {
318 # If you fail to give a defined value, that's also 0
319 return 0;
320 } elsif(
321 scalar(@{$self->attributes->{CaptureArgs}}) == 1 &&
322 looks_like_number($self->attributes->{CaptureArgs}[0])
323 ) {
324 # 'Old school' numbered captures
325 return $self->attributes->{CaptureArgs}[0];
326 } else {
327 # New hotness named arg constraints
328 return $self->number_of_captures_constraints;
329 }
330 }
331
332
2055d9ad 333use overload (
334
335 # Stringify to reverse for debug output etc.
336 q{""} => sub { shift->{reverse} },
337
338 # Codulate to execute to invoke the encapsulated action coderef
339 '&{}' => sub { my $self = shift; sub { $self->execute(@_); }; },
340
341 # Make general $stuff still work
342 fallback => 1,
343
344);
345
059c085b 346no warnings 'recursion';
347
b2ddf6d7 348sub dispatch { # Execute ourselves against a context
349 my ( $self, $c ) = @_;
049f82e2 350 return $c->execute( $self->class, $self );
b2ddf6d7 351}
fbcc39ad 352
b2ddf6d7 353sub execute {
354 my $self = shift;
059c085b 355 $self->code->(@_);
b2ddf6d7 356}
fbcc39ad 357
b2ddf6d7 358sub match {
60034b8c 359 my ( $self, $c ) = @_;
c1192f1e 360 return $self->match_args($c, $c->req->args);
361}
362
363sub match_args {
364 my ($self, $c, $args) = @_;
365 my @args = @{$args||[]};
81436df9 366
d4e8996f 367 # There there are arg constraints, we must see to it that the constraints
368 # check positive for each arg in the list.
5d198e3f 369 if($self->has_args_constraints) {
4a0218ca 370 # If there is only one type constraint, and its a Ref or subtype of Ref,
371 # That means we expect a reference, so use the full args arrayref.
372 if(
bf4f1643 373 $self->args_constraint_count == 1 &&
a7ab9aa9 374 (
375 $self->args_constraints->[0]->is_a_type_of('Ref') ||
376 $self->args_constraints->[0]->is_a_type_of('ClassName')
377 )
4a0218ca 378 ) {
d9f0a350 379 # Ok, the the type constraint is a ref type, which is allowed to have
380 # any number of args. We need to check the arg length, if one is defined.
381 # If we had a ref type constraint that allowed us to determine the allowed
382 # number of args, we need to match that number. Otherwise if there was an
383 # undetermined number (~0) then we allow all the args. This is more of an
384 # Optimization since Tuple[Int, Int] would fail on 3,4,5 anyway, but this
385 # way we can avoid calling the constraint when the arg length is incorrect.
386 if(
387 $self->normalized_arg_number == ~0 ||
388 scalar( @args ) == $self->normalized_arg_number
389 ) {
390 return $self->args_constraints->[0]->check($args);
391 } else {
392 return 0;
393 }
a7ab9aa9 394 # Removing coercion stuff for the first go
395 #if($self->args_constraints->[0]->coercion && $self->attributes->{Coerce}) {
396 # my $coerced = $self->args_constraints->[0]->coerce($c) || return 0;
397 # $c->req->args([$coerced]);
398 # return 1;
399 #}
4a0218ca 400 } else {
a82c96cf 401 # Because of the way chaining works, we can expect args that are totally not
402 # what you'd expect length wise. When they don't match length, thats a fail
c1192f1e 403 return 0 unless scalar( @args ) == $self->normalized_arg_number;
a82c96cf 404
c1192f1e 405 for my $i(0..$#args) {
406 $self->args_constraints->[$i]->check($args[$i]) || return 0;
4a0218ca 407 }
408 return 1;
6d62355b 409 }
6d62355b 410 } else {
d9f0a350 411 # If infinite args with no constraints, we always match
412 return 1 if $self->normalized_arg_number == ~0;
413
d4e8996f 414 # Otherwise, we just need to match the number of args.
c1192f1e 415 return scalar( @args ) == $self->normalized_arg_number;
6d62355b 416 }
760d121e 417}
418
a82c96cf 419sub match_captures {
420 my ($self, $c, $captures) = @_;
421 my @captures = @{$captures||[]};
422
423 return 1 unless scalar(@captures); # If none, just say its ok
ec4d7259 424 return $self->has_captures_constraints ?
425 $self->match_captures_constraints($c, $captures) : 1;
a82c96cf 426
ec4d7259 427 return 1;
428}
429
430sub match_captures_constraints {
431 my ($self, $c, $captures) = @_;
432 my @captures = @{$captures||[]};
433
434 # Match is positive if you don't have any.
435 return 1 unless $self->has_captures_constraints;
436
437 if(
438 $self->captures_constraints_count == 1 &&
439 (
440 $self->captures_constraints->[0]->is_a_type_of('Ref') ||
441 $self->captures_constraints->[0]->is_a_type_of('ClassName')
442 )
443 ) {
444 return $self->captures_constraints->[0]->check($captures);
a82c96cf 445 } else {
ec4d7259 446 for my $i(0..$#captures) {
447 $self->captures_constraints->[$i]->check($captures[$i]) || return 0;
448 }
a82c96cf 449 return 1;
ec4d7259 450 }
451
a82c96cf 452}
fbcc39ad 453
ec4d7259 454
05b47f2e 455sub compare {
456 my ($a1, $a2) = @_;
d4e8996f 457 return $a1->normalized_arg_number <=> $a2->normalized_arg_number;
05b47f2e 458}
459
342d2169 460sub scheme {
461 return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
462}
463
ffca3e96 464sub list_extra_info {
465 my $self = shift;
466 return {
a82c96cf 467 Args => $self->normalized_arg_number,
ffca3e96 468 CaptureArgs => $self->number_of_captures,
469 }
470}
3c0da3ec 471
e5ecd5bc 472__PACKAGE__->meta->make_immutable;
473
b2ddf6d7 4741;
fbcc39ad 475
b2ddf6d7 476__END__
4ab87e27 477
fbcc39ad 478=head1 METHODS
479
b5ecfcf0 480=head2 attributes
fbcc39ad 481
4ab87e27 482The sub attributes that are set for this action, like Local, Path, Private
b2ddf6d7 483and so on. This determines how the action is dispatched to.
4ab87e27 484
b5ecfcf0 485=head2 class
b96f127f 486
4d38cb07 487Returns the name of the component where this action is defined.
f9818250 488Derived by calling the L<catalyst_component_name|Catalyst::Component/catalyst_component_name>
fb0c5b21 489method on each component.
4ab87e27 490
b5ecfcf0 491=head2 code
11bd4e3e 492
b2ddf6d7 493Returns a code reference to this action.
4ab87e27 494
b8f669f3 495=head2 dispatch( $c )
4ab87e27 496
18a9655c 497Dispatch this action against a context.
fbcc39ad 498
b8f669f3 499=head2 execute( $controller, $c, @args )
500
501Execute this action's coderef against a given controller with a given
502context and arguments
503
649fd1fa 504=head2 match( $c )
4ab87e27 505
649fd1fa 506Check Args attribute, and makes sure number of args matches the setting.
b2ddf6d7 507Always returns true if Args is omitted.
4082e678 508
760d121e 509=head2 match_captures ($c, $captures)
510
511Can be implemented by action class and action role authors. If the method
512exists, then it will be called with the request context and an array reference
513of the captures for this action.
514
515Returning true from this method causes the chain match to continue, returning
516makes the chain not match (and alternate, less preferred chains will be attempted).
517
ec4d7259 518=head2 match_captures_constraints ($c, \@captures);
519
520Does the \@captures given match any constraints (if any constraints exist). Returns
521true if you ask but there are no constraints.
522
c1192f1e 523=head2 match_args($c, $args)
524
75ce30d0 525Does the Args match or not?
c1192f1e 526
6f0b85d2 527=head2 resolve_type_constraint
528
79fb8f95 529Tries to find a type constraint if you have on on a type constrained method.
760d121e 530
91955398 531=head2 compare
532
cbe555e8 533Compares 2 actions based on the value of the C<Args> attribute, with no C<Args>
534having the highest precedence.
91955398 535
b5ecfcf0 536=head2 namespace
fbcc39ad 537
4ab87e27 538Returns the private namespace this action lives in.
539
b5ecfcf0 540=head2 reverse
6b239949 541
4ab87e27 542Returns the private path for this action.
543
009b5b23 544=head2 private_path
545
546Returns absolute private path for this action. Unlike C<reverse>, the
547C<private_path> of an action is always suitable for passing to C<forward>.
548
b5ecfcf0 549=head2 name
fbcc39ad 550
18a9655c 551Returns the sub name of this action.
4ab87e27 552
0cff119a 553=head2 number_of_args
554
d4e8996f 555Returns the number of args this action expects. This is 0 if the action doesn't
556take any arguments and undef if it will take any number of arguments.
557
558=head2 normalized_arg_number
559
560For the purposes of comparison we normalize 'number_of_args' so that if it is
561undef we mean ~0 (as many args are we can think of).
0cff119a 562
563=head2 number_of_captures
564
565Returns the number of captures this action expects for L<Chained|Catalyst::DispatchType::Chained> actions.
566
3c0da3ec 567=head2 list_extra_info
568
ffca3e96 569A HashRef of key-values that an action can provide to a debugging screen
3c0da3ec 570
342d2169 571=head2 scheme
572
573Any defined scheme for the action
574
059c085b 575=head2 meta
576
18a9655c 577Provided by Moose.
059c085b 578
2f381252 579=head1 AUTHORS
fbcc39ad 580
2f381252 581Catalyst Contributors, see Catalyst.pm
fbcc39ad 582
583=head1 COPYRIGHT
584
536bee89 585This library is free software. You can redistribute it and/or modify it under
fbcc39ad 586the same terms as Perl itself.
587
85d9fce6 588=cut
81436df9 589
590