Adding documentation, and a 202 Accepted status helper
[catagits/Catalyst-Action-Serialize-Data-Serializer.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;
178 }
179 $c->response->status(201);
180 $c->response->header('Location' => $location);
bb4130f6 181 $self->_set_entity($c, $p{'entity'});
182 return 1;
183}
184
398c5a1b 185=item status_accepted
186
187Returns a "202 ACCEPTED" response. Takes an "entity" to serialize.
188
189Example:
190
191 $self->status_accepted(
192 $c,
193 entity => {
194 status => "queued",
195 }
196 );
197
198=cut
199sub status_accepted {
bb4130f6 200 my $self = shift;
201 my $c = shift;
202 my %p = validate(@_,
203 {
204 entity => 1,
205 },
206 );
207
398c5a1b 208 $c->response->status(202);
bb4130f6 209 $self->_set_entity($c, $p{'entity'});
210 return 1;
211}
212
398c5a1b 213=item status_bad_request
214
215Returns a "400 BAD REQUEST" response. Takes a "message" argument
216as a scalar, which will become the value of "error" in the serialized
217response.
218
219Example:
220
221 $self->status_bad_request(
222 $c,
223 entity => {
224 message => "Cannot do what you have asked!",
225 }
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,
254 entity => {
255 message => "Cannot find what you were looking for!",
256 }
257 );
258
259=cut
bb4130f6 260sub status_not_found {
261 my $self = shift;
262 my $c = shift;
263 my %p = validate(@_,
264 {
265 message => { type => SCALAR },
266 },
267 );
268
269 $c->response->status(404);
edab9038 270 $c->log->debug("Status Not Found: " . $p{'message'});
bdc54939 271 $self->_set_entity($c, { error => $p{'message'} });
bb4130f6 272 return 1;
273}
274
275sub _set_entity {
276 my $self = shift;
277 my $c = shift;
278 my $entity = shift;
279 if (defined($entity)) {
280 $c->stash->{$self->config->{'serialize'}->{'stash_key'}} = $entity;
5511d1ff 281 }
282 return 1;
eccb2137 283}
256c894f 284
398c5a1b 285=back
286
287=head1 MANUAL RESPONSES
288
289If you want to construct your responses yourself, all you need to
290do is put the object you want serialized in $c->stash->{'rest'}.
291
292=head1 SEE ALSO
293
294L<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
295L<Catalyst::Action::Deserialize>
296
297For help with REST in general:
298
299The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt
300
301Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer
302
303The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage
304
305=head1 AUTHOR
306
307Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway
308
309Marchex, Inc. paid me while I developed this module. (http://www.marchex.com)
310
311=head1 LICENSE
312
313You may distribute this code under the same terms as Perl itself.
314
315=cut
316
256c894f 3171;