Adding documentation, and a 202 Accepted status helper
adam [Mon, 20 Nov 2006 03:03:02 +0000 (03:03 +0000)]
Changelog
Makefile.PL
TODO
lib/Catalyst/Action/Deserialize.pm
lib/Catalyst/Action/REST.pm
lib/Catalyst/Action/Serialize.pm
lib/Catalyst/Controller/REST.pm

index 36342c0..92584e9 100644 (file)
--- 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
index f7ad13d..eb9a46d 100644 (file)
@@ -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 (file)
--- 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
 
index 2d5343f..8b23cf7 100644 (file)
@@ -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<Catalyst::Action::Deserialize>.  You can see a list
+of currently implemented plugins in L<Catalyst::Controller::REST>.
+
+The results of your Deserializing will wind up in $c->req->data.
+This is done through the magic of L<Catalyst::Request::REST>.
+
+=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<Catalyst::Controller::REST>, which implements
+a sensible set of defaults for a controller doing REST.
+
+L<Catalyst::Action::Serialize>, L<Catalyst::Action::REST>
+
+=head1 AUTHOR
+
+Adam Jacob <adam@stalecoffee.org>, 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;
index 54ade49..9adb921 100644 (file)
@@ -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<Catalyst::Controller::REST>.
+
 =head1 METHODS
 
 =over 4
@@ -106,6 +108,8 @@ L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>
 
 Adam Jacob <adam@stalecoffee.org>, 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.
index 4896438..313ba80 100644 (file)
@@ -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<Catalyst::Action::Serialize>.
+
+=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<Catalyst::Controller::REST>, which implements
+a sensible set of defaults for a controller doing REST.
+
+L<Catalyst::Action::Deserialize>, L<Catalyst::Action::REST>
+
+=head1 AUTHOR
+
+Adam Jacob <adam@stalecoffee.org>, 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
index 91434b5..c2393ec 100644 (file)
@@ -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<Catalyst::Controller::REST> will use YAML as
+the serialization format.
+
+Implementing new Serialization formats is easy!  Contributions
+are most welcome!  See L<Catalyst::Action::Serialize> and
+L<Catalyst::Action::Deserialize> 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<Catalyst::Action::REST>, L<Catalyst::Action::Serialize>,
+L<Catalyst::Action::Deserialize>
+
+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 <adam@stalecoffee.org>, 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;