are coercions feasable at all?
[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 ) {
d4e8996f 62 # 'Old school' numberd 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
6d62355b 74has args_constraints => (
75 is=>'ro',
81436df9 76 init_arg=>undef,
6d62355b 77 traits=>['Array'],
78 isa=>'ArrayRef',
79 required=>1,
80 lazy=>1,
81 builder=>'_build_args_constraints',
82 handles => {
83 has_args_constraints => 'count',
81436df9 84 number_of_args_constraints => 'count',
6d62355b 85 });
86
87 sub _build_args_constraints {
88 my $self = shift;
89 my @arg_protos = @{$self->attributes->{Args}||[]};
90
91 return [] unless scalar(@arg_protos);
92 # If there is only one arg and it looks like a number
93 # we assume its 'classic' and the number is the number of
94 # constraints.
95 my @args = ();
96 if(
97 scalar(@arg_protos) == 1 &&
98 looks_like_number($arg_protos[0])
99 ) {
81436df9 100 return \@args;
6d62355b 101 } else {
4a0218ca 102 @args =
842180f7 103 map { $self->resolve_type_constraint($_) || die "$_ is not a constraint!" }
337a627a 104 @arg_protos;
6d62355b 105 }
106
107 return \@args;
108 }
109
842180f7 110sub resolve_type_constraint {
111 my ($self, $name) = @_;
112 my $tc = eval "package ${\$self->class}; $name" || undef;
113 return $tc || Moose::Util::TypeConstraints::find_or_parse_type_constraint($name);
114}
115
2055d9ad 116use overload (
117
118 # Stringify to reverse for debug output etc.
119 q{""} => sub { shift->{reverse} },
120
121 # Codulate to execute to invoke the encapsulated action coderef
122 '&{}' => sub { my $self = shift; sub { $self->execute(@_); }; },
123
124 # Make general $stuff still work
125 fallback => 1,
126
127);
128
059c085b 129no warnings 'recursion';
130
b2ddf6d7 131sub dispatch { # Execute ourselves against a context
132 my ( $self, $c ) = @_;
049f82e2 133 return $c->execute( $self->class, $self );
b2ddf6d7 134}
fbcc39ad 135
b2ddf6d7 136sub execute {
137 my $self = shift;
059c085b 138 $self->code->(@_);
b2ddf6d7 139}
fbcc39ad 140
b2ddf6d7 141sub match {
60034b8c 142 my ( $self, $c ) = @_;
81436df9 143
d4e8996f 144 # If infinite args, we always match
145 return 1 if $self->normalized_arg_number == ~0;
146
147 # There there are arg constraints, we must see to it that the constraints
148 # check positive for each arg in the list.
5d198e3f 149 if($self->has_args_constraints) {
4a0218ca 150 # If there is only one type constraint, and its a Ref or subtype of Ref,
151 # That means we expect a reference, so use the full args arrayref.
152 if(
153 $self->number_of_args_constraints == 1 &&
6f0b85d2 154 ($self->args_constraints->[0]->is_a_type_of('Ref') || $self->args_constraints->[0]->is_a_type_of('ClassName'))
4a0218ca 155 ) {
6f0b85d2 156 return 1 if $self->args_constraints->[0]->check($c->req->args);
157 if($self->args_constraints->[0]->coercion && $self->attributes->{Coerce}) {
158 my $coerced = $self->args_constraints->[0]->coerce($c) || return 0;
159 $c->req->args([$coerced]);
160 return 1;
161 }
4a0218ca 162 } else {
e5604544 163 for my $i(0..$#{ $c->req->args }) {
4a0218ca 164 $self->args_constraints->[$i]->check($c->req->args->[$i]) || return 0;
165 }
166 return 1;
6d62355b 167 }
6d62355b 168 } else {
d4e8996f 169 # Otherwise, we just need to match the number of args.
170 return scalar( @{ $c->req->args } ) == $self->normalized_arg_number;
6d62355b 171 }
760d121e 172}
173
60034b8c 174sub match_captures { 1 }
fbcc39ad 175
05b47f2e 176sub compare {
177 my ($a1, $a2) = @_;
d4e8996f 178 return $a1->normalized_arg_number <=> $a2->normalized_arg_number;
05b47f2e 179}
180
0cff119a 181sub number_of_captures {
182 my ( $self ) = @_;
183
184 return 0 unless exists $self->attributes->{CaptureArgs};
185 return $self->attributes->{CaptureArgs}[0] || 0;
186}
187
342d2169 188sub scheme {
189 return exists $_[0]->attributes->{Scheme} ? $_[0]->attributes->{Scheme}[0] : undef;
190}
191
ffca3e96 192sub list_extra_info {
193 my $self = shift;
194 return {
195 Args => $self->attributes->{Args}[0],
196 CaptureArgs => $self->number_of_captures,
197 }
198}
3c0da3ec 199
e5ecd5bc 200__PACKAGE__->meta->make_immutable;
201
b2ddf6d7 2021;
fbcc39ad 203
b2ddf6d7 204__END__
4ab87e27 205
fbcc39ad 206=head1 METHODS
207
b5ecfcf0 208=head2 attributes
fbcc39ad 209
4ab87e27 210The sub attributes that are set for this action, like Local, Path, Private
b2ddf6d7 211and so on. This determines how the action is dispatched to.
4ab87e27 212
b5ecfcf0 213=head2 class
b96f127f 214
4d38cb07 215Returns the name of the component where this action is defined.
f9818250 216Derived by calling the L<catalyst_component_name|Catalyst::Component/catalyst_component_name>
fb0c5b21 217method on each component.
4ab87e27 218
b5ecfcf0 219=head2 code
11bd4e3e 220
b2ddf6d7 221Returns a code reference to this action.
4ab87e27 222
b8f669f3 223=head2 dispatch( $c )
4ab87e27 224
18a9655c 225Dispatch this action against a context.
fbcc39ad 226
b8f669f3 227=head2 execute( $controller, $c, @args )
228
229Execute this action's coderef against a given controller with a given
230context and arguments
231
649fd1fa 232=head2 match( $c )
4ab87e27 233
649fd1fa 234Check Args attribute, and makes sure number of args matches the setting.
b2ddf6d7 235Always returns true if Args is omitted.
4082e678 236
760d121e 237=head2 match_captures ($c, $captures)
238
239Can be implemented by action class and action role authors. If the method
240exists, then it will be called with the request context and an array reference
241of the captures for this action.
242
243Returning true from this method causes the chain match to continue, returning
244makes the chain not match (and alternate, less preferred chains will be attempted).
245
6f0b85d2 246=head2 resolve_type_constraint
247
248Trys to find a type constraint if you have on on a type constrained method.
760d121e 249
91955398 250=head2 compare
251
cbe555e8 252Compares 2 actions based on the value of the C<Args> attribute, with no C<Args>
253having the highest precedence.
91955398 254
b5ecfcf0 255=head2 namespace
fbcc39ad 256
4ab87e27 257Returns the private namespace this action lives in.
258
b5ecfcf0 259=head2 reverse
6b239949 260
4ab87e27 261Returns the private path for this action.
262
009b5b23 263=head2 private_path
264
265Returns absolute private path for this action. Unlike C<reverse>, the
266C<private_path> of an action is always suitable for passing to C<forward>.
267
b5ecfcf0 268=head2 name
fbcc39ad 269
18a9655c 270Returns the sub name of this action.
4ab87e27 271
0cff119a 272=head2 number_of_args
273
d4e8996f 274Returns the number of args this action expects. This is 0 if the action doesn't
275take any arguments and undef if it will take any number of arguments.
276
277=head2 normalized_arg_number
278
279For the purposes of comparison we normalize 'number_of_args' so that if it is
280undef we mean ~0 (as many args are we can think of).
0cff119a 281
282=head2 number_of_captures
283
284Returns the number of captures this action expects for L<Chained|Catalyst::DispatchType::Chained> actions.
285
3c0da3ec 286=head2 list_extra_info
287
ffca3e96 288A HashRef of key-values that an action can provide to a debugging screen
3c0da3ec 289
342d2169 290=head2 scheme
291
292Any defined scheme for the action
293
059c085b 294=head2 meta
295
18a9655c 296Provided by Moose.
059c085b 297
2f381252 298=head1 AUTHORS
fbcc39ad 299
2f381252 300Catalyst Contributors, see Catalyst.pm
fbcc39ad 301
302=head1 COPYRIGHT
303
536bee89 304This library is free software. You can redistribute it and/or modify it under
fbcc39ad 305the same terms as Perl itself.
306
85d9fce6 307=cut
81436df9 308
309