From: adam Date: Mon, 20 Nov 2006 03:03:02 +0000 (+0000) Subject: Adding documentation, and a 202 Accepted status helper X-Git-Tag: 0.67_01~63 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Action-REST.git;a=commitdiff_plain;h=398c5a1bfa52e417d07af96341ec75424dd519ed Adding documentation, and a 202 Accepted status helper --- diff --git a/Changelog b/Changelog index 36342c0..92584e9 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,7 @@ +Sun Nov 19 16:24:20 PST 2006 (adam) + Added status_accepted (Code 202) + Added a first pass at documentation. + Mon Oct 16 14:48:54 PDT 2006 (adam) Added in Test Suite Created Catalyst::Action::Serialize and Catalyst::Action::Deserialize diff --git a/Makefile.PL b/Makefile.PL index f7ad13d..eb9a46d 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,4 +1,4 @@ -use inc::Module::Install 0.64; +use inc::Module::Install; perl_version '5.8.1'; diff --git a/TODO b/TODO index 606a21a..05f4776 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,11 @@ * Override setup_classes from Catalyst::Base, so things that use C::Controller:REST don't need to have ActionClass('REST') on them -* Document everything +* More documentation of the Serialization/Deserialization process. + +* More Serializers! + +* More status methods! * More tests diff --git a/lib/Catalyst/Action/Deserialize.pm b/lib/Catalyst/Action/Deserialize.pm index 2d5343f..8b23cf7 100644 --- a/lib/Catalyst/Action/Deserialize.pm +++ b/lib/Catalyst/Action/Deserialize.pm @@ -68,4 +68,76 @@ sub execute { } } +=head1 NAME + +Catalyst::Action::Deserialize - Deserialize Data in a Request + +=head1 SYNOPSIS + + package Foo::Controller::Bar; + + __PACKAGE__->config( + serialize => { + 'default' => 'YAML', + 'stash_key' => 'rest', + 'map' => { + 'text/x-yaml' => 'YAML', + 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], + }, + } + ); + + sub begin : ActionClass('Deserialize') {} + +=head1 DESCRIPTION + +This action will deserialize HTTP POST, PUT, and OPTIONS requests. +It assumes that the body of the HTTP Request is a serialized object. +The serializer is selected by introspecting the requests content-type +header. + +It requires that your Catalyst controller have a "serialize" entry +in it's configuration. + +The specifics of deserializing each content-type is implemented as +a plugin to L. You can see a list +of currently implemented plugins in L. + +The results of your Deserializing will wind up in $c->req->data. +This is done through the magic of L. + +=head1 CONFIGURATION + +=over 4 + +=item default + +The default Serialization format. See the next section for +available options. + +=item map + +Takes a hashref, mapping Content-Types to a given plugin. + +=back + +=head1 SEE ALSO + +You likely want to look at L, which implements +a sensible set of defaults for a controller doing REST. + +L, L + +=head1 AUTHOR + +Adam Jacob , with lots of help from mst and jrockway + +Marchex, Inc. paid me while I developed this module. (http://www.marchex.com) + +=head1 LICENSE + +You may distribute this code under the same terms as Perl itself. + +=cut + 1; diff --git a/lib/Catalyst/Action/REST.pm b/lib/Catalyst/Action/REST.pm index 54ade49..9adb921 100644 --- a/lib/Catalyst/Action/REST.pm +++ b/lib/Catalyst/Action/REST.pm @@ -46,6 +46,8 @@ If a method is requested that is not implemented, this action will return a status 405 (Method Not Found). It will populate the "Allow" header with the list of implemented request methods. +It is likely that you really want to look at L. + =head1 METHODS =over 4 @@ -106,6 +108,8 @@ L, L Adam Jacob , with lots of help from mst and jrockway +Marchex, Inc. paid me while I developed this module. (http://www.marchex.com) + =head1 LICENSE You may distribute this code under the same terms as Perl itself. diff --git a/lib/Catalyst/Action/Serialize.pm b/lib/Catalyst/Action/Serialize.pm index 4896438..313ba80 100644 --- a/lib/Catalyst/Action/Serialize.pm +++ b/lib/Catalyst/Action/Serialize.pm @@ -75,3 +75,74 @@ sub execute { } 1; + +=head1 NAME + +Catalyst::Action::Serialize - Serialize Data in a Response + +=head1 SYNOPSIS + + package Foo::Controller::Bar; + + __PACKAGE__->config( + serialize => { + 'default' => 'YAML', + 'stash_key' => 'rest', + 'map' => { + 'text/x-yaml' => 'YAML', + 'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ], + }, + } + ); + + sub end : ActionClass('Serialize') {} + +=head1 DESCRIPTION + +This action will serialize the body of an HTTP Response. The serializer is +selected by introspecting the requests content-type header. + +It requires that your Catalyst controller have a "serialize" entry +in it's configuration. + +The specifics of serializing each content-type is implemented as +a plugin to L. + +=head1 CONFIGURATION + +=over 4 + +=item default + +The default Serialization format. See the next section for +available options. This is used if a requested content-type +is not recognized. + +=item stash_key + +Where in the stash the data you want serialized lives. + +=item map + +Takes a hashref, mapping Content-Types to a given plugin. + +=back + +=head1 SEE ALSO + +You likely want to look at L, which implements +a sensible set of defaults for a controller doing REST. + +L, L + +=head1 AUTHOR + +Adam Jacob , with lots of help from mst and jrockway + +Marchex, Inc. paid me while I developed this module. (http://www.marchex.com) + +=head1 LICENSE + +You may distribute this code under the same terms as Perl itself. + +=cut diff --git a/lib/Catalyst/Controller/REST.pm b/lib/Catalyst/Controller/REST.pm index 91434b5..c2393ec 100644 --- a/lib/Catalyst/Controller/REST.pm +++ b/lib/Catalyst/Controller/REST.pm @@ -1,5 +1,95 @@ package Catalyst::Controller::REST; +=head1 NAME + +Catalyst::Controller::REST - A RESTful controller + +=head1 SYNOPSIS + + package Foo::Controller::Bar; + + use base 'Catalyst::Controller::REST'; + + sub thing : Local : ActionClass('REST') { } + + # Answer GET requests to "thing" + sub thing_GET { + my ( $self, $c ) = @_; + + # Return a 200 OK, with the data in entity + # serialized in the body + $self->status_ok( + $c, + entity => { + some => 'data', + foo => 'is real bar-y', + }, + ); + } + + # Answer PUT requests to "thing" + sub thing_PUT { + .. some action .. + } + +=head1 DESCRIPTION + +Catalyst::Controller::REST implements a mechanism for building +RESTful services in Catalyst. It does this by extending the +normal Catalyst dispatch mechanism to allow for different +subroutines to be called based on the HTTP Method requested, +while also transparently handling all the serialization/deserialization for +you. + +This is probably best served by an example. In the above +controller, we have declared a Local Catalyst action on +"sub thing", and have used the ActionClass('REST'). + +Below, we have declared "thing_GET" and "thing_PUT". Any +GET requests to thing will be dispatched to "thing_GET", +while any PUT requests will be dispatched to "thing_PUT". + +Any unimplemented HTTP METHODS will be met with a "405 Method Not Allowed" +response, automatically containing the proper list of available methods. + +The HTTP POST, PUT, and OPTIONS methods will all automatically deserialize the +contents of $c->request->body based on the requests content-type header. +A list of understood serialization formats is below. + +Also included in this class are several helper methods, which +will automatically handle setting up proper response objects +for you. + +To make your Controller RESTful, simply have it + + use base 'Catalyst::Controller::REST'; + +=head1 SERIALIZATION + +Catalyst::Controller::REST will automatically serialize your +responses. The currently implemented serialization formats are: + + text/x-yaml -> YAML::Syck + text/x-data-dumper -> Data::Serializer + +By default, L will use YAML as +the serialization format. + +Implementing new Serialization formats is easy! Contributions +are most welcome! See L and +L for more information. + +=head1 STATUS HELPERS + +These helpers try and conform to the HTTP 1.1 Specification. You can +refer to it at: http://www.w3.org/Protocols/rfc2616/rfc2616.txt. +These routines are all implemented as regular subroutines, and as +such require you pass the current context ($c) as the first argument. + +=over 4 + +=cut + use strict; use warnings; use base 'Catalyst::Controller'; @@ -18,14 +108,59 @@ __PACKAGE__->config( } ); + sub begin : ActionClass('Deserialize') {} sub end : ActionClass('Serialize') { } -# You probably want to refer to the HTTP 1.1 Spec for these; they should -# conform as much as possible. -# -# ftp://ftp.isi.edu/in-notes/rfc2616.txt +=item status_ok + +Returns a "200 OK" response. Takes an "entity" to serialize. + +Example: + + $self->status_ok( + $c, + entity => { + radiohead => "Is a good band!", + } + ); + +=cut + +sub status_ok { + my $self = shift; + my $c = shift; + my %p = validate(@_, + { + entity => 1, + }, + ); + + $c->response->status(200); + $self->_set_entity($c, $p{'entity'}); + return 1; +} + +=item status_created + +Returns a "201 CREATED" response. Takes an "entity" to serialize, +and a "location" where the created object can be found. + +Example: + + $self->status_created( + $c, + location => $c->req->uri->as_string, + entity => { + radiohead => "Is a good band!", + } + ); + +In the above example, we use the requested URI as our location. +This is probably what you want for most PUT requests. + +=cut sub status_created { my $self = shift; @@ -47,7 +182,21 @@ sub status_created { return 1; } -sub status_ok { +=item status_accepted + +Returns a "202 ACCEPTED" response. Takes an "entity" to serialize. + +Example: + + $self->status_accepted( + $c, + entity => { + status => "queued", + } + ); + +=cut +sub status_accepted { my $self = shift; my $c = shift; my %p = validate(@_, @@ -56,11 +205,27 @@ sub status_ok { }, ); - $c->response->status(200); + $c->response->status(202); $self->_set_entity($c, $p{'entity'}); return 1; } +=item status_bad_request + +Returns a "400 BAD REQUEST" response. Takes a "message" argument +as a scalar, which will become the value of "error" in the serialized +response. + +Example: + + $self->status_bad_request( + $c, + entity => { + message => "Cannot do what you have asked!", + } + ); + +=cut sub status_bad_request { my $self = shift; my $c = shift; @@ -76,6 +241,22 @@ sub status_bad_request { return 1; } +=item status_not_found + +Returns a "404 NOT FOUND" response. Takes a "message" argument +as a scalar, which will become the value of "error" in the serialized +response. + +Example: + + $self->status_not_found( + $c, + entity => { + message => "Cannot find what you were looking for!", + } + ); + +=cut sub status_not_found { my $self = shift; my $c = shift; @@ -101,4 +282,36 @@ sub _set_entity { return 1; } +=back + +=head1 MANUAL RESPONSES + +If you want to construct your responses yourself, all you need to +do is put the object you want serialized in $c->stash->{'rest'}. + +=head1 SEE ALSO + +L, L, +L + +For help with REST in general: + +The HTTP 1.1 Spec is required reading. http://www.w3.org/Protocols/rfc2616/rfc2616.txt + +Wikipedia! http://en.wikipedia.org/wiki/Representational_State_Transfer + +The REST Wiki: http://rest.blueoxen.net/cgi-bin/wiki.pl?FrontPage + +=head1 AUTHOR + +Adam Jacob , with lots of help from mst and jrockway + +Marchex, Inc. paid me while I developed this module. (http://www.marchex.com) + +=head1 LICENSE + +You may distribute this code under the same terms as Perl itself. + +=cut + 1;