tweaked how constraints work to narrow and tighten scope
[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;
05b47f2e 23use Scalar::Util 'looks_like_number';
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) = @_;
d9f0a350 246 my @tc = eval "package ${\$self->class}; $name" or die "'$name' not a type constraint in ${\$self->private_path}";
75ce30d0 247 if($tc[0]) {
248 return map { ref($_) ? $_ : Moose::Util::TypeConstraints::find_or_parse_type_constraint($_) } @tc;
249 } else {
250 return;
251 }
842180f7 252}
253
a82c96cf 254has number_of_captures => (
255 is=>'ro',
256 init_arg=>undef,
257 isa=>'Int',
258 required=>1,
259 lazy=>1,
260 builder=>'_build_number_of_captures');
261
262 sub _build_number_of_captures {
263 my $self = shift;
264 if( ! exists $self->attributes->{CaptureArgs} ) {
265 # If there are no defined capture args, thats considered 0.
266 return 0;
267 } elsif(!defined($self->attributes->{CaptureArgs}[0])) {
268 # If you fail to give a defined value, that's also 0
269 return 0;
270 } elsif(
271 scalar(@{$self->attributes->{CaptureArgs}}) == 1 &&
272 looks_like_number($self->attributes->{CaptureArgs}[0])
273 ) {
274 # 'Old school' numbered captures
275 return $self->attributes->{CaptureArgs}[0];
276 } else {
277 # New hotness named arg constraints
278 return $self->number_of_captures_constraints;
279 }
280 }
281
282
2055d9ad 283use overload (
284
285 # Stringify to reverse for debug output etc.
286 q{""} => sub { shift->{reverse} },
287
288 # Codulate to execute to invoke the encapsulated action coderef
289 '&{}' => sub { my $self = shift; sub { $self->execute(@_); }; },
290
291 # Make general $stuff still work
292 fallback => 1,
293
294);
295
059c085b 296no warnings 'recursion';
297
b2ddf6d7 298sub dispatch { # Execute ourselves against a context
299 my ( $self, $c ) = @_;
049f82e2 300 return $c->execute( $self->class, $self );
b2ddf6d7 301}
fbcc39ad 302
b2ddf6d7 303sub execute {
304 my $self = shift;
059c085b 305 $self->code->(@_);
b2ddf6d7 306}
fbcc39ad 307
b2ddf6d7 308sub match {
60034b8c 309 my ( $self, $c ) = @_;
c1192f1e 310 return $self->match_args($c, $c->req->args);
311}
312
313sub match_args {
314 my ($self, $c, $args) = @_;
315 my @args = @{$args||[]};
81436df9 316
d4e8996f 317 # There there are arg constraints, we must see to it that the constraints
318 # check positive for each arg in the list.
5d198e3f 319 if($self->has_args_constraints) {
4a0218ca 320 # If there is only one type constraint, and its a Ref or subtype of Ref,
321 # That means we expect a reference, so use the full args arrayref.
322 if(
bf4f1643 323 $self->args_constraint_count == 1 &&
a7ab9aa9 324 (
325 $self->args_constraints->[0]->is_a_type_of('Ref') ||
326 $self->args_constraints->[0]->is_a_type_of('ClassName')
327 )
4a0218ca 328 ) {
d9f0a350 329 # Ok, the the type constraint is a ref type, which is allowed to have
330 # any number of args. We need to check the arg length, if one is defined.
331 # If we had a ref type constraint that allowed us to determine the allowed
332 # number of args, we need to match that number. Otherwise if there was an
333 # undetermined number (~0) then we allow all the args. This is more of an
334 # Optimization since Tuple[Int, Int] would fail on 3,4,5 anyway, but this
335 # way we can avoid calling the constraint when the arg length is incorrect.
336 if(
337 $self->normalized_arg_number == ~0 ||
338 scalar( @args ) == $self->normalized_arg_number
339 ) {
340 return $self->args_constraints->[0]->check($args);
341 } else {
342 return 0;
343 }
a7ab9aa9 344 # Removing coercion stuff for the first go
345 #if($self->args_constraints->[0]->coercion && $self->attributes->{Coerce}) {
346 # my $coerced = $self->args_constraints->[0]->coerce($c) || return 0;
347 # $c->req->args([$coerced]);
348 # return 1;
349 #}
4a0218ca 350 } else {
a82c96cf 351 # Because of the way chaining works, we can expect args that are totally not
352 # what you'd expect length wise. When they don't match length, thats a fail
c1192f1e 353 return 0 unless scalar( @args ) == $self->normalized_arg_number;
a82c96cf 354
c1192f1e 355 for my $i(0..$#args) {
356 $self->args_constraints->[$i]->check($args[$i]) || return 0;
4a0218ca 357 }
358 return 1;
6d62355b 359 }
6d62355b 360 } else {
d9f0a350 361 # If infinite args with no constraints, we always match
362 return 1 if $self->normalized_arg_number == ~0;
363
d4e8996f 364 # Otherwise, we just need to match the number of args.
c1192f1e 365 return scalar( @args ) == $self->normalized_arg_number;
6d62355b 366 }
760d121e 367}
368
a82c96cf 369sub match_captures {
370 my ($self, $c, $captures) = @_;
371 my @captures = @{$captures||[]};
372
373 return 1 unless scalar(@captures); # If none, just say its ok
374
375 if($self->has_captures_constraints) {
376 if(
bf4f1643 377 $self->captures_constraints_count == 1 &&
a82c96cf 378 (
379 $self->captures_constraints->[0]->is_a_type_of('Ref') ||
380 $self->captures_constraints->[0]->is_a_type_of('ClassName')
381 )
382 ) {
bf4f1643 383 return $self->captures_constraints->[0]->check($captures);
a82c96cf 384 } else {
385 for my $i(0..$#captures) {
386 $self->captures_constraints->[$i]->check($captures[$i]) || return 0;
387 }
388 return 1;
389 }
390 } else {
391 return 1;
392 }
393 return 1;
394}
fbcc39ad 395
05b47f2e 396sub compare {
397 my ($a1, $a2) = @_;
d4e8996f 398 return $a1->normalized_arg_number <=> $a2->normalized_arg_number;
05b47f2e 399}
400
342d2169 401sub scheme {
402 return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
403}
404
ffca3e96 405sub list_extra_info {
406 my $self = shift;
407 return {
a82c96cf 408 Args => $self->normalized_arg_number,
ffca3e96 409 CaptureArgs => $self->number_of_captures,
410 }
411}
3c0da3ec 412
e5ecd5bc 413__PACKAGE__->meta->make_immutable;
414
b2ddf6d7 4151;
fbcc39ad 416
b2ddf6d7 417__END__
4ab87e27 418
fbcc39ad 419=head1 METHODS
420
b5ecfcf0 421=head2 attributes
fbcc39ad 422
4ab87e27 423The sub attributes that are set for this action, like Local, Path, Private
b2ddf6d7 424and so on. This determines how the action is dispatched to.
4ab87e27 425
b5ecfcf0 426=head2 class
b96f127f 427
4d38cb07 428Returns the name of the component where this action is defined.
f9818250 429Derived by calling the L<catalyst_component_name|Catalyst::Component/catalyst_component_name>
fb0c5b21 430method on each component.
4ab87e27 431
b5ecfcf0 432=head2 code
11bd4e3e 433
b2ddf6d7 434Returns a code reference to this action.
4ab87e27 435
b8f669f3 436=head2 dispatch( $c )
4ab87e27 437
18a9655c 438Dispatch this action against a context.
fbcc39ad 439
b8f669f3 440=head2 execute( $controller, $c, @args )
441
442Execute this action's coderef against a given controller with a given
443context and arguments
444
649fd1fa 445=head2 match( $c )
4ab87e27 446
649fd1fa 447Check Args attribute, and makes sure number of args matches the setting.
b2ddf6d7 448Always returns true if Args is omitted.
4082e678 449
760d121e 450=head2 match_captures ($c, $captures)
451
452Can be implemented by action class and action role authors. If the method
453exists, then it will be called with the request context and an array reference
454of the captures for this action.
455
456Returning true from this method causes the chain match to continue, returning
457makes the chain not match (and alternate, less preferred chains will be attempted).
458
c1192f1e 459=head2 match_args($c, $args)
460
75ce30d0 461Does the Args match or not?
c1192f1e 462
6f0b85d2 463=head2 resolve_type_constraint
464
465Trys to find a type constraint if you have on on a type constrained method.
760d121e 466
91955398 467=head2 compare
468
cbe555e8 469Compares 2 actions based on the value of the C<Args> attribute, with no C<Args>
470having the highest precedence.
91955398 471
b5ecfcf0 472=head2 namespace
fbcc39ad 473
4ab87e27 474Returns the private namespace this action lives in.
475
b5ecfcf0 476=head2 reverse
6b239949 477
4ab87e27 478Returns the private path for this action.
479
009b5b23 480=head2 private_path
481
482Returns absolute private path for this action. Unlike C<reverse>, the
483C<private_path> of an action is always suitable for passing to C<forward>.
484
b5ecfcf0 485=head2 name
fbcc39ad 486
18a9655c 487Returns the sub name of this action.
4ab87e27 488
0cff119a 489=head2 number_of_args
490
d4e8996f 491Returns the number of args this action expects. This is 0 if the action doesn't
492take any arguments and undef if it will take any number of arguments.
493
494=head2 normalized_arg_number
495
496For the purposes of comparison we normalize 'number_of_args' so that if it is
497undef we mean ~0 (as many args are we can think of).
0cff119a 498
499=head2 number_of_captures
500
501Returns the number of captures this action expects for L<Chained|Catalyst::DispatchType::Chained> actions.
502
3c0da3ec 503=head2 list_extra_info
504
ffca3e96 505A HashRef of key-values that an action can provide to a debugging screen
3c0da3ec 506
342d2169 507=head2 scheme
508
509Any defined scheme for the action
510
059c085b 511=head2 meta
512
18a9655c 513Provided by Moose.
059c085b 514
2f381252 515=head1 AUTHORS
fbcc39ad 516
2f381252 517Catalyst Contributors, see Catalyst.pm
fbcc39ad 518
519=head1 COPYRIGHT
520
536bee89 521This library is free software. You can redistribute it and/or modify it under
fbcc39ad 522the same terms as Perl itself.
523
85d9fce6 524=cut
81436df9 525
526