remove hardcoded version strings
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Action / REST.pm
CommitLineData
256c894f 1package Catalyst::Action::REST;
2
930013e6 3use Moose;
4use namespace::autoclean;
256c894f 5
930013e6 6extends 'Catalyst::Action';
7ad87df9 7use Class::Inspector;
9a76221e 8use Catalyst::Request::REST;
ebba5325 9use Catalyst::Controller::REST;
10
9c5c9bd1 11BEGIN { require 5.008001; }
256c894f 12
cc188065 13# VERSION
9a76221e 14
24748286 15sub BUILDARGS {
16 my $class = shift;
17 my $config = shift;
18 Catalyst::Request::REST->_insert_self_into( $config->{class} );
19 return $class->SUPER::BUILDARGS($config, @_);
5132f5e4 20}
7328f0ab 21
22=head1 NAME
23
24Catalyst::Action::REST - Automated REST Method Dispatching
25
26=head1 SYNOPSIS
27
5e87ec47 28 sub foo :Local :ActionClass('REST') {
29 ... do setup for HTTP method specific handlers ...
30 }
7328f0ab 31
44d48631 32 sub foo_GET {
7328f0ab 33 ... do something for GET requests ...
34 }
35
4ee24376 36 # alternatively use an Action
e51849a0 37 sub foo_PUT : Action {
4ee24376 38 ... do something for PUT requests ...
7328f0ab 39 }
40
41=head1 DESCRIPTION
42
43This Action handles doing automatic method dispatching for REST requests. It
44takes a normal Catalyst action, and changes the dispatch to append an
4ee24376 45underscore and method name. First it will try dispatching to an action with
46the generated name, and failing that it will try to dispatch to a regular
47method.
7328f0ab 48
49For example, in the synopsis above, calling GET on "/foo" would result in
50the foo_GET method being dispatched.
51
44d48631 52If a method is requested that is not implemented, this action will
53return a status 405 (Method Not Found). It will populate the "Allow" header
5e87ec47 54with the list of implemented request methods. You can override this behavior
55by implementing a custom 405 handler like so:
56
57 sub foo_not_implemented {
58 ... handle not implemented methods ...
59 }
60
61If you do not provide an _OPTIONS subroutine, we will automatically respond
62with a 200 OK. The "Allow" header will be populated with the list of
80c886bb 63implemented request methods. If you do not provide an _HEAD either, we will
64auto dispatch to the _GET one in case it exists.
7328f0ab 65
5e87ec47 66It is likely that you really want to look at L<Catalyst::Controller::REST>,
67which brings this class together with automatic Serialization of requests
68and responses.
398c5a1b 69
85aa4e18 70When you use this module, it adds the L<Catalyst::TraitFor::Request::REST>
71role to your request class.
9a76221e 72
7328f0ab 73=head1 METHODS
74
75=over 4
76
77=item dispatch
78
79This method overrides the default dispatch mechanism to the re-dispatching
80mechanism described above.
81
82=cut
d34c067a 83
256c894f 84sub dispatch {
bb4130f6 85 my $self = shift;
d34c067a 86 my $c = shift;
256c894f 87
3faede66 88 my $rest_method = $self->name . "_" . uc( $c->request->method );
679978b1 89
295fe4d5 90 return $self->_dispatch_rest_method( $c, $rest_method );
91}
92
93sub _dispatch_rest_method {
94 my $self = shift;
95 my $c = shift;
96 my $rest_method = shift;
d5252c20 97 my $req = $c->request;
295fe4d5 98
99 my $controller = $c->component( $self->class );
100
3faede66 101 my ($code, $name);
679978b1 102
3c4306f2 103 # Execute normal 'foo' action.
d5252c20 104 $c->execute( $self->class, $self, @{ $req->args } );
3c4306f2 105
3faede66 106 # Common case, for foo_GET etc
7656dd12 107 if ( $code = $controller->action_for($rest_method) ) {
d5252c20 108 return $c->forward( $code, $req->args ); # Forward to foo_GET if it's an action
82b48c61 109 }
110 elsif ($code = $controller->can($rest_method)) {
a34f2309 111 $name = $rest_method; # Stash name and code to run 'foo_GET' like an action below.
256c894f 112 }
256c894f 113
d5252c20 114 # Generic handling for foo_*
3faede66 115 if (!$code) {
d5252c20 116 my $code_action = {
117 OPTIONS => sub {
118 $name = $rest_method;
119 $code = sub { $self->_return_options($self->name, @_) };
120 },
e566b7c7 121 HEAD => sub {
122 $rest_method =~ s{_HEAD$}{_GET}i;
123 $self->_dispatch_rest_method($c, $rest_method);
124 },
d5252c20 125 default => sub {
126 # Otherwise, not implemented.
127 $name = $self->name . "_not_implemented";
128 $code = $controller->can($name) # User method
129 # Generic not implemented
130 || sub { $self->_return_not_implemented($self->name, @_) };
131 },
132 };
9e4398c8 133 my ( $http_method, $action_name ) = ( $rest_method, $self->name );
134 $http_method =~ s{\Q$action_name\E\_}{};
135 my $respond = ($code_action->{$http_method}
e566b7c7 136 || $code_action->{'default'})->();
137 return $respond unless $name;
679978b1 138 }
256c894f 139
3faede66 140 # localise stuff so we can dispatch the action 'as normal, but get
141 # different stats shown, and different code run.
3c4306f2 142 # Also get the full path for the action, and make it look like a forward
3faede66 143 local $self->{code} = $code;
3c4306f2 144 my @name = split m{/}, $self->reverse;
145 $name[-1] = $name;
146 local $self->{reverse} = "-> " . join('/', @name);
d34c067a 147
d5252c20 148 $c->execute( $self->class, $self, @{ $req->args } );
d34c067a 149}
150
b0c2ebd4 151sub get_allowed_methods {
d2d93101 152 my ( $self, $controller, $c, $name ) = @_;
679978b1 153 my $class = ref($controller) ? ref($controller) : $controller;
a08d447b 154 my $methods = {
155 map { /^$name\_(.+)$/ ? ( $1 => 1 ) : () }
156 @{ Class::Inspector->methods($class) }
157 };
158 $methods->{'HEAD'} = 1 if $methods->{'GET'};
258f6e7c 159 delete $methods->{'not_implemented'};
04f96b73 160 return sort keys %$methods;
679978b1 161};
162
163sub _return_options {
3faede66 164 my ( $self, $method_name, $controller, $c) = @_;
b0c2ebd4 165 my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
679978b1 166 $c->response->content_type('text/plain');
167 $c->response->status(200);
168 $c->response->header( 'Allow' => \@allowed );
ffcae141 169 $c->response->body(q{});
d34c067a 170}
171
172sub _return_not_implemented {
3faede66 173 my ( $self, $method_name, $controller, $c ) = @_;
d34c067a 174
b0c2ebd4 175 my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
7ad87df9 176 $c->response->content_type('text/plain');
177 $c->response->status(405);
eccb2137 178 $c->response->header( 'Allow' => \@allowed );
179 $c->response->body( "Method "
180 . $c->request->method
181 . " not implemented for "
679978b1 182 . $c->uri_for( $method_name ) );
7ad87df9 183}
184
24748286 185__PACKAGE__->meta->make_immutable;
186
256c894f 1871;
7328f0ab 188
189=back
190
191=head1 SEE ALSO
192
85aa4e18 193You likely want to look at L<Catalyst::Controller::REST>, which implements a
194sensible set of defaults for a controller doing REST.
195
196This class automatically adds the L<Catalyst::TraitFor::Request::REST> role to
786c212f 197your request class. If you're writing a web application which provides RESTful
d6ece98c 198responses and still needs to accommodate web browsers, you may prefer to use
85aa4e18 199L<Catalyst::TraitFor::Request::REST::ForBrowsers> instead.
7328f0ab 200
201L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>
202
5d7480da 203=head1 TROUBLESHOOTING
204
205=over 4
206
207=item Q: I'm getting a "415 Unsupported Media Type" error. What gives?!
208
69ad525b 209A: Most likely, you haven't set Content-type equal to "application/json", or
210one of the accepted return formats. You can do this by setting it in your query
211accepted return formats. You can do this by setting it in your query string
212thusly: C<< ?content-type=application%2Fjson (where %2F == / uri escaped). >>
5d7480da 213
10bcd217 214B<NOTE> Apache will refuse %2F unless configured otherwise.
215Make sure C<AllowEncodedSlashes On> is in your httpd.conf file in order
69ad525b 216for this to run smoothly.
5d7480da 217
69ad525b 218=back
7328f0ab 219
5cb5f6bb 220=head1 AUTHOR
221
410b24f8 222Adam Jacob E<lt>adam@stalecoffee.orgE<gt>, with lots of help from mst and jrockway
5cb5f6bb 223
224Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
225
2f7533ed 226=head1 CONTRIBUTORS
398c5a1b 227
04882f72 228Tomas Doran (t0m) E<lt>bobtfish@bobtfish.netE<gt>
2f7533ed 229
230John Goulah
33e5de96 231
04882f72 232Christopher Laco
33e5de96 233
04882f72 234Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
97b3cf7c 235
7580fa2b 236Hans Dieter Pearcey
237
16b5133c 238Brian Phillips E<lt>bphillips@cpan.orgE<gt>
8bf1f20e 239
410b24f8 240Dave Rolsky E<lt>autarch@urth.orgE<gt>
76a96edc 241
04882f72 242Luke Saunders
243
244Arthur Axel "fREW" Schmidt E<lt>frioux@gmail.comE<gt>
245
246J. Shirley E<lt>jshirley@gmail.comE<gt>
247
259c53c7 248Gavin Henry E<lt>ghenry@surevoip.co.ukE<gt>
249
8aa1a2ee 250Gerv http://www.gerv.net/
251
252Colin Newell <colin@opusvl.com>
253
239c8eef 254Wallace Reis E<lt>wreis@cpan.orgE<gt>
255
5cb5f6bb 256=head1 COPYRIGHT
2f7533ed 257
259c53c7 258Copyright (c) 2006-2012 the above named AUTHOR and CONTRIBUTORS
2f7533ed 259
7328f0ab 260=head1 LICENSE
261
262You may distribute this code under the same terms as Perl itself.
263
264=cut
d34c067a 265