Small refactoring of case where there is no code for a given rest metohd
[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
a9e5f3d2 13our $VERSION = '0.88';
a66af307 14$VERSION = eval $VERSION;
9a76221e 15
24748286 16sub BUILDARGS {
17 my $class = shift;
18 my $config = shift;
19 Catalyst::Request::REST->_insert_self_into( $config->{class} );
20 return $class->SUPER::BUILDARGS($config, @_);
5132f5e4 21}
7328f0ab 22
23=head1 NAME
24
25Catalyst::Action::REST - Automated REST Method Dispatching
26
27=head1 SYNOPSIS
28
5e87ec47 29 sub foo :Local :ActionClass('REST') {
30 ... do setup for HTTP method specific handlers ...
31 }
7328f0ab 32
44d48631 33 sub foo_GET {
7328f0ab 34 ... do something for GET requests ...
35 }
36
4ee24376 37 # alternatively use an Action
e51849a0 38 sub foo_PUT : Action {
4ee24376 39 ... do something for PUT requests ...
7328f0ab 40 }
41
42=head1 DESCRIPTION
43
44This Action handles doing automatic method dispatching for REST requests. It
45takes a normal Catalyst action, and changes the dispatch to append an
4ee24376 46underscore and method name. First it will try dispatching to an action with
47the generated name, and failing that it will try to dispatch to a regular
48method.
7328f0ab 49
50For example, in the synopsis above, calling GET on "/foo" would result in
51the foo_GET method being dispatched.
52
44d48631 53If a method is requested that is not implemented, this action will
54return a status 405 (Method Not Found). It will populate the "Allow" header
5e87ec47 55with the list of implemented request methods. You can override this behavior
56by implementing a custom 405 handler like so:
57
58 sub foo_not_implemented {
59 ... handle not implemented methods ...
60 }
61
62If you do not provide an _OPTIONS subroutine, we will automatically respond
63with a 200 OK. The "Allow" header will be populated with the list of
64implemented request methods.
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
2f91bf68 88 my $controller = $c->component( $self->class );
3faede66 89 my $rest_method = $self->name . "_" . uc( $c->request->method );
679978b1 90
3faede66 91 my ($code, $name);
679978b1 92
3faede66 93 # Common case, for foo_GET etc
7656dd12 94 if ( $code = $controller->action_for($rest_method) ) {
a34f2309 95 $c->execute( $self->class, $self, @{ $c->req->args } ); # Execute normal 'foo' action.
96 return $c->forward( $code, $c->req->args ); # Forward to foo_GET if it's an action
82b48c61 97 }
98 elsif ($code = $controller->can($rest_method)) {
5f48cbe4 99 # Execute normal action
d34c067a 100 $c->execute( $self->class, $self, @{ $c->req->args } );
a34f2309 101 $name = $rest_method; # Stash name and code to run 'foo_GET' like an action below.
256c894f 102 }
256c894f 103
3faede66 104 # Generic handling for foo_OPTIONS
3faede66 105 if (!$code) {
65987ff6 106 if ( $c->request->method eq "OPTIONS") {
107 $name = $rest_method;
108 $code = sub { $self->_return_options($self->name, @_) };
109 }
110 else {
111 # Otherwise, not implemented.
112 $name = $self->name . "_not_implemented";
113 $code = $controller->can($name) # User method
114 # Generic not implemented
115 || sub { $self->_return_not_implemented($self->name, @_) };
116 }
679978b1 117 }
256c894f 118
3faede66 119 # localise stuff so we can dispatch the action 'as normal, but get
120 # different stats shown, and different code run.
121 local $self->{code} = $code;
122 local $self->{reverse} = $name;
d34c067a 123
679978b1 124 $c->execute( $self->class, $self, @{ $c->req->args } );
d34c067a 125}
126
127sub _get_allowed_methods {
d2d93101 128 my ( $self, $controller, $c, $name ) = @_;
679978b1 129 my $class = ref($controller) ? ref($controller) : $controller;
846b6b07 130 my $methods = Class::Inspector->methods($class);
131 return map { /^$name\_(.+)$/ } @$methods;
679978b1 132};
133
134sub _return_options {
3faede66 135 my ( $self, $method_name, $controller, $c) = @_;
d2d93101 136 my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
679978b1 137 $c->response->content_type('text/plain');
138 $c->response->status(200);
139 $c->response->header( 'Allow' => \@allowed );
d34c067a 140}
141
142sub _return_not_implemented {
3faede66 143 my ( $self, $method_name, $controller, $c ) = @_;
d34c067a 144
d2d93101 145 my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
7ad87df9 146 $c->response->content_type('text/plain');
147 $c->response->status(405);
eccb2137 148 $c->response->header( 'Allow' => \@allowed );
149 $c->response->body( "Method "
150 . $c->request->method
151 . " not implemented for "
679978b1 152 . $c->uri_for( $method_name ) );
7ad87df9 153}
154
24748286 155__PACKAGE__->meta->make_immutable;
156
256c894f 1571;
7328f0ab 158
159=back
160
161=head1 SEE ALSO
162
85aa4e18 163You likely want to look at L<Catalyst::Controller::REST>, which implements a
164sensible set of defaults for a controller doing REST.
165
166This class automatically adds the L<Catalyst::TraitFor::Request::REST> role to
786c212f 167your request class. If you're writing a web application which provides RESTful
d6ece98c 168responses and still needs to accommodate web browsers, you may prefer to use
85aa4e18 169L<Catalyst::TraitFor::Request::REST::ForBrowsers> instead.
7328f0ab 170
171L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>
172
5d7480da 173=head1 TROUBLESHOOTING
174
175=over 4
176
177=item Q: I'm getting a "415 Unsupported Media Type" error. What gives?!
178
69ad525b 179A: Most likely, you haven't set Content-type equal to "application/json", or
180one of the accepted return formats. You can do this by setting it in your query
181accepted return formats. You can do this by setting it in your query string
182thusly: C<< ?content-type=application%2Fjson (where %2F == / uri escaped). >>
5d7480da 183
10bcd217 184B<NOTE> Apache will refuse %2F unless configured otherwise.
185Make sure C<AllowEncodedSlashes On> is in your httpd.conf file in order
69ad525b 186for this to run smoothly.
5d7480da 187
69ad525b 188=back
7328f0ab 189
5cb5f6bb 190=head1 AUTHOR
191
410b24f8 192Adam Jacob E<lt>adam@stalecoffee.orgE<gt>, with lots of help from mst and jrockway
5cb5f6bb 193
194Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
195
2f7533ed 196=head1 CONTRIBUTORS
398c5a1b 197
04882f72 198Tomas Doran (t0m) E<lt>bobtfish@bobtfish.netE<gt>
2f7533ed 199
200John Goulah
33e5de96 201
04882f72 202Christopher Laco
33e5de96 203
04882f72 204Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
97b3cf7c 205
7580fa2b 206Hans Dieter Pearcey
207
410b24f8 208Dave Rolsky E<lt>autarch@urth.orgE<gt>
76a96edc 209
04882f72 210Luke Saunders
211
212Arthur Axel "fREW" Schmidt E<lt>frioux@gmail.comE<gt>
213
214J. Shirley E<lt>jshirley@gmail.comE<gt>
215
5cb5f6bb 216=head1 COPYRIGHT
2f7533ed 217
5cb5f6bb 218Copyright the above named AUTHOR and CONTRIBUTORS
2f7533ed 219
7328f0ab 220=head1 LICENSE
221
222You may distribute this code under the same terms as Perl itself.
223
224=cut
d34c067a 225