show constraint name if available, and fix bug where end of chain dod not show arg...
[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
86 my $total = 0;
87 foreach my $tc( @{$self->args_constraints}) {
88 if($tc->is_a_type_of('Ref')) {
89 if($tc->can('parameters') && $tc->has_parameters) {
90 my $total_params = scalar(@{ $tc->parameters||[] });
91 $total = $total + $total_params;
92 } else {
93 # Its a Reftype but we don't know the number of params it
94 # actually validates.
95 return undef;
96 }
97 } else {
98 $total++;
99 }
100 }
101
102 return $total;
103 }
104
6d62355b 105has args_constraints => (
106 is=>'ro',
81436df9 107 init_arg=>undef,
6d62355b 108 traits=>['Array'],
109 isa=>'ArrayRef',
110 required=>1,
111 lazy=>1,
112 builder=>'_build_args_constraints',
113 handles => {
114 has_args_constraints => 'count',
bf4f1643 115 args_constraint_count => 'count',
6d62355b 116 });
117
118 sub _build_args_constraints {
119 my $self = shift;
120 my @arg_protos = @{$self->attributes->{Args}||[]};
121
122 return [] unless scalar(@arg_protos);
123 # If there is only one arg and it looks like a number
124 # we assume its 'classic' and the number is the number of
125 # constraints.
126 my @args = ();
127 if(
128 scalar(@arg_protos) == 1 &&
129 looks_like_number($arg_protos[0])
130 ) {
81436df9 131 return \@args;
6d62355b 132 } else {
4a0218ca 133 @args =
bf4f1643 134 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
337a627a 135 @arg_protos;
6d62355b 136 }
6d62355b 137 return \@args;
138 }
139
bf4f1643 140has number_of_captures_constraints => (
141 is=>'ro',
142 isa=>'Int|Undef',
143 init_arg=>undef,
144 required=>1,
145 lazy=>1,
146 builder=>'_build_number_of_capture_constraints');
147
148 sub _build_number_of_capture_constraints {
149 my $self = shift;
150 return unless $self->has_captures_constraints;
151
152 my $total = 0;
153 foreach my $tc( @{$self->captures_constraints}) {
154 if($tc->is_a_type_of('Ref')) {
155 if($tc->can('parameters') && $tc->has_parameters) {
156 my $total_params = scalar(@{ $tc->parameters||[] });
157 $total = $total + $total_params;
158 } else {
159 # Its a Reftype but we don't know the number of params it
160 # actually validates. This is not currently permitted in
161 # a capture...
162 die "You cannot use CaptureArgs($tc) in ${\$self->reverse} because we cannot determined the number of its parameters";
163 }
164 } else {
165 $total++;
166 }
167 }
168
169 return $total;
170 }
171
a82c96cf 172has captures_constraints => (
173 is=>'ro',
174 init_arg=>undef,
175 traits=>['Array'],
176 isa=>'ArrayRef',
177 required=>1,
178 lazy=>1,
179 builder=>'_build_captures_constraints',
180 handles => {
181 has_captures_constraints => 'count',
bf4f1643 182 captures_constraints_count => 'count',
a82c96cf 183 });
184
185 sub _build_captures_constraints {
186 my $self = shift;
187 my @arg_protos = @{$self->attributes->{CaptureArgs}||[]};
188
189 return [] unless scalar(@arg_protos);
190 # If there is only one arg and it looks like a number
191 # we assume its 'classic' and the number is the number of
192 # constraints.
193 my @args = ();
194 if(
195 scalar(@arg_protos) == 1 &&
196 looks_like_number($arg_protos[0])
197 ) {
198 return \@args;
199 } else {
200 @args =
bf4f1643 201 map { my @tc = $self->resolve_type_constraint($_); scalar(@tc) ? @tc : die "$_ is not a constraint!" }
a82c96cf 202 @arg_protos;
203 }
204
205 return \@args;
206 }
207
842180f7 208sub resolve_type_constraint {
209 my ($self, $name) = @_;
bf4f1643 210 my @tc = eval "package ${\$self->class}; $name";
211 return @tc if $tc[0];
212 return Moose::Util::TypeConstraints::find_or_parse_type_constraint($name);
842180f7 213}
214
a82c96cf 215has number_of_captures => (
216 is=>'ro',
217 init_arg=>undef,
218 isa=>'Int',
219 required=>1,
220 lazy=>1,
221 builder=>'_build_number_of_captures');
222
223 sub _build_number_of_captures {
224 my $self = shift;
225 if( ! exists $self->attributes->{CaptureArgs} ) {
226 # If there are no defined capture args, thats considered 0.
227 return 0;
228 } elsif(!defined($self->attributes->{CaptureArgs}[0])) {
229 # If you fail to give a defined value, that's also 0
230 return 0;
231 } elsif(
232 scalar(@{$self->attributes->{CaptureArgs}}) == 1 &&
233 looks_like_number($self->attributes->{CaptureArgs}[0])
234 ) {
235 # 'Old school' numbered captures
236 return $self->attributes->{CaptureArgs}[0];
237 } else {
238 # New hotness named arg constraints
239 return $self->number_of_captures_constraints;
240 }
241 }
242
243
2055d9ad 244use overload (
245
246 # Stringify to reverse for debug output etc.
247 q{""} => sub { shift->{reverse} },
248
249 # Codulate to execute to invoke the encapsulated action coderef
250 '&{}' => sub { my $self = shift; sub { $self->execute(@_); }; },
251
252 # Make general $stuff still work
253 fallback => 1,
254
255);
256
059c085b 257no warnings 'recursion';
258
b2ddf6d7 259sub dispatch { # Execute ourselves against a context
260 my ( $self, $c ) = @_;
049f82e2 261 return $c->execute( $self->class, $self );
b2ddf6d7 262}
fbcc39ad 263
b2ddf6d7 264sub execute {
265 my $self = shift;
059c085b 266 $self->code->(@_);
b2ddf6d7 267}
fbcc39ad 268
b2ddf6d7 269sub match {
60034b8c 270 my ( $self, $c ) = @_;
81436df9 271
d4e8996f 272 # If infinite args, we always match
273 return 1 if $self->normalized_arg_number == ~0;
274
275 # There there are arg constraints, we must see to it that the constraints
276 # check positive for each arg in the list.
5d198e3f 277 if($self->has_args_constraints) {
4a0218ca 278 # If there is only one type constraint, and its a Ref or subtype of Ref,
279 # That means we expect a reference, so use the full args arrayref.
280 if(
bf4f1643 281 $self->args_constraint_count == 1 &&
a7ab9aa9 282 (
283 $self->args_constraints->[0]->is_a_type_of('Ref') ||
284 $self->args_constraints->[0]->is_a_type_of('ClassName')
285 )
4a0218ca 286 ) {
bf4f1643 287 return $self->args_constraints->[0]->check($c->req->args);
a7ab9aa9 288 # Removing coercion stuff for the first go
289 #if($self->args_constraints->[0]->coercion && $self->attributes->{Coerce}) {
290 # my $coerced = $self->args_constraints->[0]->coerce($c) || return 0;
291 # $c->req->args([$coerced]);
292 # return 1;
293 #}
4a0218ca 294 } else {
a82c96cf 295 # Because of the way chaining works, we can expect args that are totally not
296 # what you'd expect length wise. When they don't match length, thats a fail
297 return 0 unless scalar( @{ $c->req->args } ) == $self->normalized_arg_number;
298
e5604544 299 for my $i(0..$#{ $c->req->args }) {
4a0218ca 300 $self->args_constraints->[$i]->check($c->req->args->[$i]) || return 0;
301 }
302 return 1;
6d62355b 303 }
6d62355b 304 } else {
d4e8996f 305 # Otherwise, we just need to match the number of args.
306 return scalar( @{ $c->req->args } ) == $self->normalized_arg_number;
6d62355b 307 }
760d121e 308}
309
a82c96cf 310sub match_captures {
311 my ($self, $c, $captures) = @_;
312 my @captures = @{$captures||[]};
313
314 return 1 unless scalar(@captures); # If none, just say its ok
315
316 if($self->has_captures_constraints) {
317 if(
bf4f1643 318 $self->captures_constraints_count == 1 &&
a82c96cf 319 (
320 $self->captures_constraints->[0]->is_a_type_of('Ref') ||
321 $self->captures_constraints->[0]->is_a_type_of('ClassName')
322 )
323 ) {
bf4f1643 324 return $self->captures_constraints->[0]->check($captures);
a82c96cf 325 } else {
326 for my $i(0..$#captures) {
327 $self->captures_constraints->[$i]->check($captures[$i]) || return 0;
328 }
329 return 1;
330 }
331 } else {
332 return 1;
333 }
334 return 1;
335}
fbcc39ad 336
05b47f2e 337sub compare {
338 my ($a1, $a2) = @_;
d4e8996f 339 return $a1->normalized_arg_number <=> $a2->normalized_arg_number;
05b47f2e 340}
341
342d2169 342sub scheme {
343 return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
344}
345
ffca3e96 346sub list_extra_info {
347 my $self = shift;
348 return {
a82c96cf 349 Args => $self->normalized_arg_number,
ffca3e96 350 CaptureArgs => $self->number_of_captures,
351 }
352}
3c0da3ec 353
e5ecd5bc 354__PACKAGE__->meta->make_immutable;
355
b2ddf6d7 3561;
fbcc39ad 357
b2ddf6d7 358__END__
4ab87e27 359
fbcc39ad 360=head1 METHODS
361
b5ecfcf0 362=head2 attributes
fbcc39ad 363
4ab87e27 364The sub attributes that are set for this action, like Local, Path, Private
b2ddf6d7 365and so on. This determines how the action is dispatched to.
4ab87e27 366
b5ecfcf0 367=head2 class
b96f127f 368
4d38cb07 369Returns the name of the component where this action is defined.
f9818250 370Derived by calling the L<catalyst_component_name|Catalyst::Component/catalyst_component_name>
fb0c5b21 371method on each component.
4ab87e27 372
b5ecfcf0 373=head2 code
11bd4e3e 374
b2ddf6d7 375Returns a code reference to this action.
4ab87e27 376
b8f669f3 377=head2 dispatch( $c )
4ab87e27 378
18a9655c 379Dispatch this action against a context.
fbcc39ad 380
b8f669f3 381=head2 execute( $controller, $c, @args )
382
383Execute this action's coderef against a given controller with a given
384context and arguments
385
649fd1fa 386=head2 match( $c )
4ab87e27 387
649fd1fa 388Check Args attribute, and makes sure number of args matches the setting.
b2ddf6d7 389Always returns true if Args is omitted.
4082e678 390
760d121e 391=head2 match_captures ($c, $captures)
392
393Can be implemented by action class and action role authors. If the method
394exists, then it will be called with the request context and an array reference
395of the captures for this action.
396
397Returning true from this method causes the chain match to continue, returning
398makes the chain not match (and alternate, less preferred chains will be attempted).
399
6f0b85d2 400=head2 resolve_type_constraint
401
402Trys to find a type constraint if you have on on a type constrained method.
760d121e 403
91955398 404=head2 compare
405
cbe555e8 406Compares 2 actions based on the value of the C<Args> attribute, with no C<Args>
407having the highest precedence.
91955398 408
b5ecfcf0 409=head2 namespace
fbcc39ad 410
4ab87e27 411Returns the private namespace this action lives in.
412
b5ecfcf0 413=head2 reverse
6b239949 414
4ab87e27 415Returns the private path for this action.
416
009b5b23 417=head2 private_path
418
419Returns absolute private path for this action. Unlike C<reverse>, the
420C<private_path> of an action is always suitable for passing to C<forward>.
421
b5ecfcf0 422=head2 name
fbcc39ad 423
18a9655c 424Returns the sub name of this action.
4ab87e27 425
0cff119a 426=head2 number_of_args
427
d4e8996f 428Returns the number of args this action expects. This is 0 if the action doesn't
429take any arguments and undef if it will take any number of arguments.
430
431=head2 normalized_arg_number
432
433For the purposes of comparison we normalize 'number_of_args' so that if it is
434undef we mean ~0 (as many args are we can think of).
0cff119a 435
436=head2 number_of_captures
437
438Returns the number of captures this action expects for L<Chained|Catalyst::DispatchType::Chained> actions.
439
3c0da3ec 440=head2 list_extra_info
441
ffca3e96 442A HashRef of key-values that an action can provide to a debugging screen
3c0da3ec 443
342d2169 444=head2 scheme
445
446Any defined scheme for the action
447
059c085b 448=head2 meta
449
18a9655c 450Provided by Moose.
059c085b 451
2f381252 452=head1 AUTHORS
fbcc39ad 453
2f381252 454Catalyst Contributors, see Catalyst.pm
fbcc39ad 455
456=head1 COPYRIGHT
457
536bee89 458This library is free software. You can redistribute it and/or modify it under
fbcc39ad 459the same terms as Perl itself.
460
85d9fce6 461=cut
81436df9 462
463