r48@latte: adam | 2006-12-03 11:32:40 -0800
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Controller / REST.pm
CommitLineData
256c894f 1package Catalyst::Controller::REST;
2
398c5a1b 3=head1 NAME
4
5Catalyst::Controller::REST - A RESTful controller
6
7=head1 SYNOPSIS
8
9 package Foo::Controller::Bar;
10
11 use base 'Catalyst::Controller::REST';
12
13 sub thing : Local : ActionClass('REST') { }
14
15 # Answer GET requests to "thing"
16 sub thing_GET {
17 my ( $self, $c ) = @_;
18
19 # Return a 200 OK, with the data in entity
20 # serialized in the body
21 $self->status_ok(
22 $c,
23 entity => {
24 some => 'data',
25 foo => 'is real bar-y',
26 },
27 );
28 }
29
30 # Answer PUT requests to "thing"
31 sub thing_PUT {
32 .. some action ..
33 }
34
35=head1 DESCRIPTION
36
37Catalyst::Controller::REST implements a mechanism for building
38RESTful services in Catalyst. It does this by extending the
39normal Catalyst dispatch mechanism to allow for different
40subroutines to be called based on the HTTP Method requested,
41while also transparently handling all the serialization/deserialization for
42you.
43
44This is probably best served by an example. In the above
45controller, we have declared a Local Catalyst action on
46"sub thing", and have used the ActionClass('REST').
47
48Below, we have declared "thing_GET" and "thing_PUT". Any
49GET requests to thing will be dispatched to "thing_GET",
50while any PUT requests will be dispatched to "thing_PUT".
51
52Any unimplemented HTTP METHODS will be met with a "405 Method Not Allowed"
53response, automatically containing the proper list of available methods.
54
55The HTTP POST, PUT, and OPTIONS methods will all automatically deserialize the
56contents of $c->request->body based on the requests content-type header.
57A list of understood serialization formats is below.
58
59Also included in this class are several helper methods, which
60will automatically handle setting up proper response objects
61for you.
62
63To make your Controller RESTful, simply have it
64
65 use base 'Catalyst::Controller::REST';
66
67=head1 SERIALIZATION
68
69Catalyst::Controller::REST will automatically serialize your
70responses. The currently implemented serialization formats are:
71
72 text/x-yaml -> YAML::Syck
73 text/x-data-dumper -> Data::Serializer
74
75By default, L<Catalyst::Controller::REST> will use YAML as
76the serialization format.
77
78Implementing new Serialization formats is easy! Contributions
79are most welcome! See L<Catalyst::Action::Serialize> and
80L<Catalyst::Action::Deserialize> for more information.
81
82=head1 STATUS HELPERS
83
84These helpers try and conform to the HTTP 1.1 Specification. You can
85refer to it at: http://www.w3.org/Protocols/rfc2616/rfc2616.txt.
86These routines are all implemented as regular subroutines, and as
87such require you pass the current context ($c) as the first argument.
88
89=over 4
90
91=cut
92
256c894f 93use strict;
94use warnings;
95use base 'Catalyst::Controller';
5511d1ff 96use Params::Validate qw(:all);
256c894f 97
98__PACKAGE__->mk_accessors(qw(serialize));
99
100__PACKAGE__->config(
101 serialize => {
eccb2137 102 'default' => 'YAML',
256c894f 103 'stash_key' => 'rest',
eccb2137 104 'map' => {
105 'text/x-yaml' => 'YAML',
7ad87df9 106 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
107 },
256c894f 108 }
109);
110
398c5a1b 111
5511d1ff 112sub begin : ActionClass('Deserialize') {}
113
114sub end : ActionClass('Serialize') { }
115
398c5a1b 116=item status_ok
117
118Returns a "200 OK" response. Takes an "entity" to serialize.
119
120Example:
121
122 $self->status_ok(
123 $c,
124 entity => {
125 radiohead => "Is a good band!",
126 }
127 );
128
129=cut
130
131sub status_ok {
132 my $self = shift;
133 my $c = shift;
134 my %p = validate(@_,
135 {
136 entity => 1,
137 },
138 );
139
140 $c->response->status(200);
141 $self->_set_entity($c, $p{'entity'});
142 return 1;
143}
144
145=item status_created
146
147Returns a "201 CREATED" response. Takes an "entity" to serialize,
148and a "location" where the created object can be found.
149
150Example:
151
152 $self->status_created(
153 $c,
154 location => $c->req->uri->as_string,
155 entity => {
156 radiohead => "Is a good band!",
157 }
158 );
159
160In the above example, we use the requested URI as our location.
161This is probably what you want for most PUT requests.
162
163=cut
bb4130f6 164
5511d1ff 165sub status_created {
166 my $self = shift;
167 my $c = shift;
168 my %p = validate(@_,
169 {
170 location => { type => SCALAR | OBJECT },
171 entity => { optional => 1 },
172 },
173 );
256c894f 174
5511d1ff 175 my $location;
176 if (ref($p{'location'})) {
177 $location = $p{'location'}->as_string;
33e5de96 178 } else {
179 $location = $p{'location'};
5511d1ff 180 }
181 $c->response->status(201);
182 $c->response->header('Location' => $location);
bb4130f6 183 $self->_set_entity($c, $p{'entity'});
184 return 1;
185}
186
398c5a1b 187=item status_accepted
188
189Returns a "202 ACCEPTED" response. Takes an "entity" to serialize.
190
191Example:
192
193 $self->status_accepted(
194 $c,
195 entity => {
196 status => "queued",
197 }
198 );
199
200=cut
201sub status_accepted {
bb4130f6 202 my $self = shift;
203 my $c = shift;
204 my %p = validate(@_,
205 {
206 entity => 1,
207 },
208 );
209
398c5a1b 210 $c->response->status(202);
bb4130f6 211 $self->_set_entity($c, $p{'entity'});
212 return 1;
213}
214
398c5a1b 215=item status_bad_request
216
217Returns a "400 BAD REQUEST" response. Takes a "message" argument
218as a scalar, which will become the value of "error" in the serialized
219response.
220
221Example:
222
223 $self->status_bad_request(
224 $c,
33e5de96 225 message => "Cannot do what you have asked!",
398c5a1b 226 );
227
228=cut
cc186a5b 229sub status_bad_request {
230 my $self = shift;
231 my $c = shift;
232 my %p = validate(@_,
233 {
234 message => { type => SCALAR },
235 },
236 );
237
238 $c->response->status(400);
edab9038 239 $c->log->debug("Status Bad Request: " . $p{'message'});
bdc54939 240 $self->_set_entity($c, { error => $p{'message'} });
cc186a5b 241 return 1;
242}
243
398c5a1b 244=item status_not_found
245
246Returns a "404 NOT FOUND" response. Takes a "message" argument
247as a scalar, which will become the value of "error" in the serialized
248response.
249
250Example:
251
252 $self->status_not_found(
253 $c,
33e5de96 254 message => "Cannot find what you were looking for!",
398c5a1b 255 );
256
257=cut
bb4130f6 258sub status_not_found {
259 my $self = shift;
260 my $c = shift;
261 my %p = validate(@_,
262 {
263 message => { type => SCALAR },
264 },
265 );
266
267 $c->response->status(404);
edab9038 268 $c->log->debug("Status Not Found: " . $p{'message'});
bdc54939 269 $self->_set_entity($c, { error => $p{'message'} });
bb4130f6 270 return 1;
271}
272
273sub _set_entity {
274 my $self = shift;
275 my $c = shift;
276 my $entity = shift;
277 if (defined($entity)) {
278 $c->stash->{$self->config->{'serialize'}->{'stash_key'}} = $entity;
5511d1ff 279 }
280 return 1;
eccb2137 281}
256c894f 282
398c5a1b 283=back
284
285=head1 MANUAL RESPONSES
286
287If you want to construct your responses yourself, all you need to
288do is put the object you want serialized in $c->stash->{'rest'}.
289
290=head1 SEE ALSO
291
292L<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
293L<Catalyst::Action::Deserialize>
294
295For help with REST in general:
296
297The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt
298
299Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer
300
301The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage
302
303=head1 AUTHOR
304
305Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway
306
307Marchex, Inc. paid me while I developed this module. (http://www.marchex.com)
308
309=head1 LICENSE
310
311You may distribute this code under the same terms as Perl itself.
312
313=cut
314
256c894f 3151;