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