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