Docs and fix test
[catagits/Catalyst-Action-REST.git] / lib / Catalyst / Controller / REST.pm
CommitLineData
256c894f 1package Catalyst::Controller::REST;
930013e6 2use Moose;
3use namespace::autoclean;
256c894f 4
3bb36dca 5our $VERSION = '0.82';
a66af307 6$VERSION = eval $VERSION;
832e768d 7
398c5a1b 8=head1 NAME
9
db8bb647 10Catalyst::Controller::REST - A RESTful controller
398c5a1b 11
12=head1 SYNOPSIS
13
14 package Foo::Controller::Bar;
5cb5f6bb 15 use Moose;
16 use namespace::autoclean;
17
18 BEGIN { extends 'Catalyst::Controller::REST' }
398c5a1b 19
20 sub thing : Local : ActionClass('REST') { }
21
22 # Answer GET requests to "thing"
23 sub thing_GET {
24 my ( $self, $c ) = @_;
db8bb647 25
398c5a1b 26 # Return a 200 OK, with the data in entity
db8bb647 27 # serialized in the body
398c5a1b 28 $self->status_ok(
db8bb647 29 $c,
398c5a1b 30 entity => {
31 some => 'data',
32 foo => 'is real bar-y',
33 },
34 );
35 }
36
37 # Answer PUT requests to "thing"
db8bb647 38 sub thing_PUT {
10bcd217 39 $radiohead = $req->data->{radiohead};
40
41 $self->status_created(
42 $c,
43 location => $c->req->uri->as_string,
44 entity => {
45 radiohead => $radiohead,
46 }
47 );
48 }
398c5a1b 49
50=head1 DESCRIPTION
51
52Catalyst::Controller::REST implements a mechanism for building
53RESTful services in Catalyst. It does this by extending the
db8bb647 54normal Catalyst dispatch mechanism to allow for different
55subroutines to be called based on the HTTP Method requested,
398c5a1b 56while also transparently handling all the serialization/deserialization for
57you.
58
59This is probably best served by an example. In the above
60controller, we have declared a Local Catalyst action on
db8bb647 61"sub thing", and have used the ActionClass('REST').
398c5a1b 62
63Below, we have declared "thing_GET" and "thing_PUT". Any
db8bb647 64GET requests to thing will be dispatched to "thing_GET",
65while any PUT requests will be dispatched to "thing_PUT".
398c5a1b 66
e601adda 67Any unimplemented HTTP methods will be met with a "405 Method Not Allowed"
68response, automatically containing the proper list of available methods. You
69can override this behavior through implementing a custom
db8bb647 70C<thing_not_implemented> method.
e601adda 71
72If you do not provide an OPTIONS handler, we will respond to any OPTIONS
73requests with a "200 OK", populating the Allowed header automatically.
74
75Any data included in C<< $c->stash->{'rest'} >> will be serialized for you.
76The serialization format will be selected based on the content-type
77of the incoming request. It is probably easier to use the L<STATUS HELPERS>,
78which are described below.
398c5a1b 79
10bcd217 80"The HTTP POST, PUT, and OPTIONS methods will all automatically
81L<deserialize|Catalyst::Action::Deserialize> the contents of
82C<< $c->request->body >> into the C<< $c->request->data >> hashref", based on
83the request's C<Content-type> header. A list of understood serialization
84formats is L<below|/AVAILABLE SERIALIZERS>.
398c5a1b 85
e601adda 86If we do not have (or cannot run) a serializer for a given content-type, a 415
db8bb647 87"Unsupported Media Type" error is generated.
398c5a1b 88
89To make your Controller RESTful, simply have it
90
5cb5f6bb 91 BEGIN { extends 'Catalyst::Controller::REST' }
398c5a1b 92
9cd203c9 93=head1 CONFIGURATION
94
95See L<Catalyst::Action::Serialize/CONFIGURATION>. Note that the C<serialize>
96key has been deprecated.
97
398c5a1b 98=head1 SERIALIZATION
99
100Catalyst::Controller::REST will automatically serialize your
e601adda 101responses, and deserialize any POST, PUT or OPTIONS requests. It evaluates
102which serializer to use by mapping a content-type to a Serialization module.
db8bb647 103We select the content-type based on:
e601adda 104
5cb5f6bb 105=over
e601adda 106
107=item B<The Content-Type Header>
108
109If the incoming HTTP Request had a Content-Type header set, we will use it.
110
111=item B<The content-type Query Parameter>
112
113If this is a GET request, you can supply a content-type query parameter.
114
115=item B<Evaluating the Accept Header>
116
117Finally, if the client provided an Accept header, we will evaluate
db8bb647 118it and use the best-ranked choice.
e601adda 119
120=back
121
122=head1 AVAILABLE SERIALIZERS
123
124A given serialization mechanism is only available if you have the underlying
125modules installed. For example, you can't use XML::Simple if it's not already
db8bb647 126installed.
e601adda 127
95318468 128In addition, each serializer has its quirks in terms of what sorts of data
e601adda 129structures it will properly handle. L<Catalyst::Controller::REST> makes
db8bb647 130no attempt to save you from yourself in this regard. :)
e601adda 131
132=over 2
133
95318468 134=item * C<text/x-yaml> => C<YAML::Syck>
e601adda 135
136Returns YAML generated by L<YAML::Syck>.
137
95318468 138=item * C<text/html> => C<YAML::HTML>
e601adda 139
140This uses L<YAML::Syck> and L<URI::Find> to generate YAML with all URLs turned
26b59bcb 141to hyperlinks. Only usable for Serialization.
e601adda 142
95318468 143=item * C<application/json> => C<JSON>
e601adda 144
db8bb647 145Uses L<JSON> to generate JSON output. It is strongly advised to also have
e540a1fa 146L<JSON::XS> installed. The C<text/x-json> content type is supported but is
147deprecated and you will receive warnings in your log.
e601adda 148
d0d292d4 149=item * C<text/javascript> => C<JSONP>
150
151If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON);
152
92d78e8f 153Note - this is disabled by default as it can be a security risk if you are unaware.
154
155The usual MIME types for this serialization format are: 'text/javascript', 'application/x-javascript',
156'application/javascript'.
157
95318468 158=item * C<text/x-data-dumper> => C<Data::Serializer>
e601adda 159
160Uses the L<Data::Serializer> module to generate L<Data::Dumper> output.
161
95318468 162=item * C<text/x-data-denter> => C<Data::Serializer>
e601adda 163
164Uses the L<Data::Serializer> module to generate L<Data::Denter> output.
165
95318468 166=item * C<text/x-data-taxi> => C<Data::Serializer>
e601adda 167
168Uses the L<Data::Serializer> module to generate L<Data::Taxi> output.
169
95318468 170=item * C<application/x-storable> => C<Data::Serializer>
e601adda 171
172Uses the L<Data::Serializer> module to generate L<Storable> output.
173
95318468 174=item * C<application/x-freezethaw> => C<Data::Serializer>
e601adda 175
176Uses the L<Data::Serializer> module to generate L<FreezeThaw> output.
177
95318468 178=item * C<text/x-config-general> => C<Data::Serializer>
e601adda 179
180Uses the L<Data::Serializer> module to generate L<Config::General> output.
181
95318468 182=item * C<text/x-php-serialization> => C<Data::Serializer>
e601adda 183
184Uses the L<Data::Serializer> module to generate L<PHP::Serialization> output.
185
95318468 186=item * C<text/xml> => C<XML::Simple>
e601adda 187
188Uses L<XML::Simple> to generate XML output. This is probably not suitable
189for any real heavy XML work. Due to L<XML::Simple>s requirement that the data
190you serialize be a HASHREF, we transform outgoing data to be in the form of:
191
192 { data => $yourdata }
193
95318468 194=item * L<View>
9a76221e 195
db8bb647 196Uses a regular Catalyst view. For example, if you wanted to have your
3d8a0645 197C<text/html> and C<text/xml> views rendered by TT, set:
198
199 __PACKAGE__->config(
200 map => {
201 'text/html' => [ 'View', 'TT' ],
202 'text/xml' => [ 'View', 'XML' ],
203 }
5cb5f6bb 204 );
3d8a0645 205
206Your views should have a C<process> method like this:
207
208 sub process {
209 my ( $self, $c, $stash_key ) = @_;
5cb5f6bb 210
3d8a0645 211 my $output;
212 eval {
213 $output = $self->serialize( $c->stash->{$stash_key} );
214 };
215 return $@ if $@;
5cb5f6bb 216
3d8a0645 217 $c->response->body( $output );
218 return 1; # important
219 }
220
221 sub serialize {
222 my ( $self, $data ) = @_;
5cb5f6bb 223
3d8a0645 224 my $serialized = ... process $data here ...
5cb5f6bb 225
3d8a0645 226 return $serialized;
227 }
9a76221e 228
e601adda 229=back
230
95318468 231By default, L<Catalyst::Controller::REST> will return a
232C<415 Unsupported Media Type> response if an attempt to use an unsupported
233content-type is made. You can ensure that something is always returned by
234setting the C<default> config option:
398c5a1b 235
5cb5f6bb 236 __PACKAGE__->config(default => 'text/x-yaml');
398c5a1b 237
95318468 238would make it always fall back to the serializer plugin defined for
239C<text/x-yaml>.
398c5a1b 240
e601adda 241=head1 CUSTOM SERIALIZERS
242
95318468 243Implementing new Serialization formats is easy! Contributions
244are most welcome! If you would like to implement a custom serializer,
245you should create two new modules in the L<Catalyst::Action::Serialize>
246and L<Catalyst::Action::Deserialize> namespace. Then assign your new
247class to the content-type's you want, and you're done.
248
249See L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>
250for more information.
e601adda 251
398c5a1b 252=head1 STATUS HELPERS
253
e601adda 254Since so much of REST is in using HTTP, we provide these Status Helpers.
255Using them will ensure that you are responding with the proper codes,
256headers, and entities.
257
398c5a1b 258These helpers try and conform to the HTTP 1.1 Specification. You can
db8bb647 259refer to it at: L<http://www.w3.org/Protocols/rfc2616/rfc2616.txt>.
398c5a1b 260These routines are all implemented as regular subroutines, and as
261such require you pass the current context ($c) as the first argument.
262
5cb5f6bb 263=over
398c5a1b 264
265=cut
266
930013e6 267BEGIN { extends 'Catalyst::Controller' }
d4611771 268use Params::Validate qw(SCALAR OBJECT);
256c894f 269
270__PACKAGE__->mk_accessors(qw(serialize));
271
272__PACKAGE__->config(
e540a1fa 273 'stash_key' => 'rest',
274 'map' => {
275 'text/html' => 'YAML::HTML',
276 'text/xml' => 'XML::Simple',
277 'text/x-yaml' => 'YAML',
278 'application/json' => 'JSON',
279 'text/x-json' => 'JSON',
d0d292d4 280 'application/x-javascript' => 'JSONP',
281 'application/javascript' => 'JSONP',
282 'text/javascript' => 'JSONP',
e540a1fa 283 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
284 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
285 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ],
95318468 286 'application/x-storable' => [ 'Data::Serializer', 'Storable' ],
287 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
288 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ],
e540a1fa 289 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
290 },
256c894f 291);
292
e540a1fa 293sub begin : ActionClass('Deserialize') { }
5511d1ff 294
0ba73721 295sub end : ActionClass('Serialize') { }
296
398c5a1b 297=item status_ok
298
299Returns a "200 OK" response. Takes an "entity" to serialize.
300
301Example:
302
303 $self->status_ok(
db8bb647 304 $c,
398c5a1b 305 entity => {
306 radiohead => "Is a good band!",
307 }
308 );
309
310=cut
311
312sub status_ok {
313 my $self = shift;
e601adda 314 my $c = shift;
d4611771 315 my %p = Params::Validate::validate( @_, { entity => 1, }, );
398c5a1b 316
317 $c->response->status(200);
e601adda 318 $self->_set_entity( $c, $p{'entity'} );
398c5a1b 319 return 1;
320}
321
322=item status_created
323
324Returns a "201 CREATED" response. Takes an "entity" to serialize,
325and a "location" where the created object can be found.
326
327Example:
328
329 $self->status_created(
db8bb647 330 $c,
398c5a1b 331 location => $c->req->uri->as_string,
332 entity => {
333 radiohead => "Is a good band!",
334 }
335 );
336
337In the above example, we use the requested URI as our location.
338This is probably what you want for most PUT requests.
339
340=cut
bb4130f6 341
5511d1ff 342sub status_created {
343 my $self = shift;
e601adda 344 my $c = shift;
d4611771 345 my %p = Params::Validate::validate(
e601adda 346 @_,
5511d1ff 347 {
e601adda 348 location => { type => SCALAR | OBJECT },
349 entity => { optional => 1 },
5511d1ff 350 },
351 );
256c894f 352
5511d1ff 353 my $location;
e601adda 354 if ( ref( $p{'location'} ) ) {
5511d1ff 355 $location = $p{'location'}->as_string;
33e5de96 356 } else {
357 $location = $p{'location'};
5511d1ff 358 }
359 $c->response->status(201);
e601adda 360 $c->response->header( 'Location' => $location );
361 $self->_set_entity( $c, $p{'entity'} );
bb4130f6 362 return 1;
363}
364
398c5a1b 365=item status_accepted
366
367Returns a "202 ACCEPTED" response. Takes an "entity" to serialize.
368
369Example:
370
371 $self->status_accepted(
db8bb647 372 $c,
398c5a1b 373 entity => {
374 status => "queued",
375 }
376 );
377
378=cut
e601adda 379
398c5a1b 380sub status_accepted {
bb4130f6 381 my $self = shift;
e601adda 382 my $c = shift;
d4611771 383 my %p = Params::Validate::validate( @_, { entity => 1, }, );
bb4130f6 384
398c5a1b 385 $c->response->status(202);
e601adda 386 $self->_set_entity( $c, $p{'entity'} );
bb4130f6 387 return 1;
388}
389
bbf0feae 390=item status_no_content
391
392Returns a "204 NO CONTENT" response.
393
394=cut
395
396sub status_no_content {
397 my $self = shift;
398 my $c = shift;
399 $c->response->status(204);
400 $self->_set_entity( $c, undef );
401 return 1.;
402}
403
398c5a1b 404=item status_bad_request
405
406Returns a "400 BAD REQUEST" response. Takes a "message" argument
407as a scalar, which will become the value of "error" in the serialized
408response.
409
410Example:
411
412 $self->status_bad_request(
db8bb647 413 $c,
33e5de96 414 message => "Cannot do what you have asked!",
398c5a1b 415 );
416
417=cut
e601adda 418
cc186a5b 419sub status_bad_request {
420 my $self = shift;
e601adda 421 my $c = shift;
d4611771 422 my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
cc186a5b 423
424 $c->response->status(400);
faf5c20b 425 $c->log->debug( "Status Bad Request: " . $p{'message'} ) if $c->debug;
e601adda 426 $self->_set_entity( $c, { error => $p{'message'} } );
cc186a5b 427 return 1;
428}
429
398c5a1b 430=item status_not_found
431
432Returns a "404 NOT FOUND" response. Takes a "message" argument
433as a scalar, which will become the value of "error" in the serialized
434response.
435
436Example:
437
438 $self->status_not_found(
db8bb647 439 $c,
33e5de96 440 message => "Cannot find what you were looking for!",
398c5a1b 441 );
442
443=cut
e601adda 444
bb4130f6 445sub status_not_found {
446 my $self = shift;
e601adda 447 my $c = shift;
d4611771 448 my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
bb4130f6 449
450 $c->response->status(404);
faf5c20b 451 $c->log->debug( "Status Not Found: " . $p{'message'} ) if $c->debug;
e601adda 452 $self->_set_entity( $c, { error => $p{'message'} } );
bb4130f6 453 return 1;
454}
455
bbf0feae 456=item gone
457
458Returns a "41O GONE" response. Takes a "message" argument as a scalar,
459which will become the value of "error" in the serialized response.
460
461Example:
462
463 $self->status_gone(
464 $c,
465 message => "The document have been deleted by foo",
466 );
467
468=cut
469
470sub status_gone {
471 my $self = shift;
472 my $c = shift;
473 my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
474
475 $c->response->status(410);
476 $c->log->debug( "Status Gone " . $p{'message'} ) if $c->debug;
477 $self->_set_entity( $c, { error => $p{'message'} } );
478 return 1;
479}
480
bb4130f6 481sub _set_entity {
e601adda 482 my $self = shift;
483 my $c = shift;
bb4130f6 484 my $entity = shift;
e601adda 485 if ( defined($entity) ) {
faf5c20b 486 $c->stash->{ $self->{'stash_key'} } = $entity;
5511d1ff 487 }
488 return 1;
eccb2137 489}
256c894f 490
398c5a1b 491=back
492
493=head1 MANUAL RESPONSES
494
495If you want to construct your responses yourself, all you need to
496do is put the object you want serialized in $c->stash->{'rest'}.
497
e601adda 498=head1 IMPLEMENTATION DETAILS
499
500This Controller ties together L<Catalyst::Action::REST>,
501L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>. It should be suitable for most applications. You should be aware that it:
502
503=over 4
504
505=item Configures the Serialization Actions
506
507This class provides a default configuration for Serialization. It is currently:
508
509 __PACKAGE__->config(
95318468 510 'stash_key' => 'rest',
511 'map' => {
512 'text/html' => 'YAML::HTML',
513 'text/xml' => 'XML::Simple',
514 'text/x-yaml' => 'YAML',
515 'application/json' => 'JSON',
516 'text/x-json' => 'JSON',
d0d292d4 517 'application/x-javascript' => 'JSONP',
518 'application/javascript' => 'JSONP',
519 'text/javascript' => 'JSONP',
95318468 520 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
521 'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
522 'text/x-data-taxi' => [ 'Data::Serializer', 'Data::Taxi' ],
523 'application/x-storable' => [ 'Data::Serializer', 'Storable' ],
524 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
525 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ],
526 'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
527 },
e601adda 528 );
529
530You can read the full set of options for this configuration block in
531L<Catalyst::Action::Serialize>.
532
533=item Sets a C<begin> and C<end> method for you
534
535The C<begin> method uses L<Catalyst::Action::Deserialize>. The C<end>
536method uses L<Catalyst::Action::Serialize>. If you want to override
537either behavior, simply implement your own C<begin> and C<end> actions
def65dcc 538and use MRO::Compat:
e601adda 539
10bcd217 540 package Foo::Controller::Monkey;
541 use Moose;
542 use namespace::autoclean;
543
544 BEGIN { extends 'Catalyst::Controller::REST' }
e601adda 545
546 sub begin :Private {
547 my ($self, $c) = @_;
db8bb647 548 ... do things before Deserializing ...
549 $self->maybe::next::method($c);
e601adda 550 ... do things after Deserializing ...
db8bb647 551 }
e601adda 552
553 sub end :Private {
554 my ($self, $c) = @_;
db8bb647 555 ... do things before Serializing ...
def65dcc 556 $self->maybe::next::method($c);
e601adda 557 ... do things after Serializing ...
558 }
559
e540a1fa 560=back
561
e601adda 562=head1 A MILD WARNING
563
564I have code in production using L<Catalyst::Controller::REST>. That said,
565it is still under development, and it's possible that things may change
d6ece98c 566between releases. I promise to not break things unnecessarily. :)
e601adda 567
398c5a1b 568=head1 SEE ALSO
569
570L<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
571L<Catalyst::Action::Deserialize>
572
573For help with REST in general:
574
575The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt
576
577Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer
578
579The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage
580
5cb5f6bb 581=head1 AUTHORS
e540a1fa 582
5cb5f6bb 583See L<Catalyst::Action::REST> for authors.
e540a1fa 584
398c5a1b 585=head1 LICENSE
586
587You may distribute this code under the same terms as Perl itself.
588
589=cut
590
256c894f 5911;