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