Adding documentation, and a 202 Accepted status helper
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Controller / REST.pm
1 package Catalyst::Controller::REST;
2
3 =head1 NAME
4
5 Catalyst::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
37 Catalyst::Controller::REST implements a mechanism for building
38 RESTful services in Catalyst.  It does this by extending the
39 normal Catalyst dispatch mechanism to allow for different 
40 subroutines to be called based on the HTTP Method requested, 
41 while also transparently handling all the serialization/deserialization for
42 you.
43
44 This is probably best served by an example.  In the above
45 controller, we have declared a Local Catalyst action on
46 "sub thing", and have used the ActionClass('REST').  
47
48 Below, we have declared "thing_GET" and "thing_PUT".  Any
49 GET requests to thing will be dispatched to "thing_GET", 
50 while any PUT requests will be dispatched to "thing_PUT".  
51
52 Any unimplemented HTTP METHODS will be met with a "405 Method Not Allowed"
53 response, automatically containing the proper list of available methods. 
54
55 The HTTP POST, PUT, and OPTIONS methods will all automatically deserialize the
56 contents of $c->request->body based on the requests content-type header.
57 A list of understood serialization formats is below.
58
59 Also included in this class are several helper methods, which
60 will automatically handle setting up proper response objects 
61 for you.
62
63 To make your Controller RESTful, simply have it
64
65   use base 'Catalyst::Controller::REST'; 
66
67 =head1 SERIALIZATION
68
69 Catalyst::Controller::REST will automatically serialize your
70 responses.  The currently implemented serialization formats are:
71
72    text/x-yaml        ->   YAML::Syck
73    text/x-data-dumper ->   Data::Serializer
74
75 By default, L<Catalyst::Controller::REST> will use YAML as
76 the serialization format.
77
78 Implementing new Serialization formats is easy!  Contributions
79 are most welcome!  See L<Catalyst::Action::Serialize> and
80 L<Catalyst::Action::Deserialize> for more information.
81
82 =head1 STATUS HELPERS
83
84 These helpers try and conform to the HTTP 1.1 Specification.  You can
85 refer to it at: http://www.w3.org/Protocols/rfc2616/rfc2616.txt.  
86 These routines are all implemented as regular subroutines, and as
87 such require you pass the current context ($c) as the first argument.
88
89 =over 4
90
91 =cut
92
93 use strict;
94 use warnings;
95 use base 'Catalyst::Controller';
96 use Params::Validate qw(:all);
97
98 __PACKAGE__->mk_accessors(qw(serialize));
99
100 __PACKAGE__->config(
101     serialize => {
102         'default'   => 'YAML',
103         'stash_key' => 'rest',
104         'map'       => {
105             'text/x-yaml'        => 'YAML',
106             'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
107         },
108     }
109 );
110
111
112 sub begin : ActionClass('Deserialize') {}
113
114 sub end : ActionClass('Serialize') { }
115
116 =item status_ok
117
118 Returns a "200 OK" response.  Takes an "entity" to serialize.
119
120 Example:
121
122   $self->status_ok(
123     $c, 
124     entity => {
125         radiohead => "Is a good band!",
126     }
127   );
128
129 =cut
130
131 sub 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
147 Returns a "201 CREATED" response.  Takes an "entity" to serialize,
148 and a "location" where the created object can be found.
149
150 Example:
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
160 In the above example, we use the requested URI as our location.
161 This is probably what you want for most PUT requests.
162
163 =cut
164
165 sub 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     );
174
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);
181     $self->_set_entity($c, $p{'entity'});
182     return 1;
183 }
184
185 =item status_accepted
186
187 Returns a "202 ACCEPTED" response.  Takes an "entity" to serialize.
188
189 Example:
190
191   $self->status_accepted(
192     $c, 
193     entity => {
194         status => "queued",
195     }
196   );
197
198 =cut
199 sub status_accepted {
200     my $self = shift;
201     my $c = shift;
202     my %p = validate(@_,
203         {
204             entity => 1, 
205         },
206     );
207
208     $c->response->status(202);
209     $self->_set_entity($c, $p{'entity'});
210     return 1;
211 }
212
213 =item status_bad_request
214
215 Returns a "400 BAD REQUEST" response.  Takes a "message" argument
216 as a scalar, which will become the value of "error" in the serialized
217 response.
218
219 Example:
220
221   $self->status_bad_request(
222     $c, 
223     entity => {
224         message => "Cannot do what you have asked!",
225     }
226   );
227
228 =cut
229 sub 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);
239     $c->log->debug("Status Bad Request: " . $p{'message'});
240     $self->_set_entity($c, { error => $p{'message'} });
241     return 1;
242 }
243
244 =item status_not_found
245
246 Returns a "404 NOT FOUND" response.  Takes a "message" argument
247 as a scalar, which will become the value of "error" in the serialized
248 response.
249
250 Example:
251
252   $self->status_not_found(
253     $c, 
254     entity => {
255         message => "Cannot find what you were looking for!",
256     }
257   );
258
259 =cut
260 sub 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);
270     $c->log->debug("Status Not Found: " . $p{'message'});
271     $self->_set_entity($c, { error => $p{'message'} });
272     return 1;
273 }
274
275 sub _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;
281     }
282     return 1;
283 }
284
285 =back
286
287 =head1 MANUAL RESPONSES
288
289 If you want to construct your responses yourself, all you need to
290 do is put the object you want serialized in $c->stash->{'rest'}.
291
292 =head1 SEE ALSO
293
294 L<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
295 L<Catalyst::Action::Deserialize>
296
297 For help with REST in general:
298
299 The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt
300
301 Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer
302
303 The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage
304
305 =head1 AUTHOR
306
307 Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway
308
309 Marchex, Inc. paid me while I developed this module.  (http://www.marchex.com)
310
311 =head1 LICENSE
312
313 You may distribute this code under the same terms as Perl itself.
314
315 =cut
316
317 1;