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