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