Split into new dist
[catagits/Catalyst-Action-Serialize-Data-Serializer.git] / lib / Catalyst / Controller / REST.pm
diff --git a/lib/Catalyst/Controller/REST.pm b/lib/Catalyst/Controller/REST.pm
deleted file mode 100644 (file)
index 6af9b76..0000000
+++ /dev/null
@@ -1,690 +0,0 @@
-package Catalyst::Controller::REST;
-use Moose;
-use namespace::autoclean;
-
-our $VERSION = '1.07';
-$VERSION = eval $VERSION;
-
-=head1 NAME
-
-Catalyst::Controller::REST - A RESTful controller
-
-=head1 SYNOPSIS
-
-    package Foo::Controller::Bar;
-    use Moose;
-    use namespace::autoclean;
-
-    BEGIN { extends '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 {
-        my ( $self, $c ) = @_;
-
-        $radiohead = $c->req->data->{radiohead};
-
-        $self->status_created(
-            $c,
-            location => $c->req->uri,
-            entity => {
-                radiohead => $radiohead,
-            }
-        );
-    }
-
-=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.  You
-can override this behavior through implementing a custom
-C<thing_not_implemented> method.
-
-If you do not provide an OPTIONS handler, we will respond to any OPTIONS
-requests with a "200 OK", populating the Allowed header automatically.
-
-Any data included in C<< $c->stash->{'rest'} >> will be serialized for you.
-The serialization format will be selected based on the content-type
-of the incoming request.  It is probably easier to use the L<STATUS HELPERS>,
-which are described below.
-
-"The HTTP POST, PUT, and OPTIONS methods will all automatically
-L<deserialize|Catalyst::Action::Deserialize> the contents of
-C<< $c->request->body >> into the C<< $c->request->data >> hashref", based on
-the request's C<Content-type> header. A list of understood serialization
-formats is L<below|/AVAILABLE SERIALIZERS>.
-
-If we do not have (or cannot run) a serializer for a given content-type, a 415
-"Unsupported Media Type" error is generated.
-
-To make your Controller RESTful, simply have it
-
-  BEGIN { extends 'Catalyst::Controller::REST' }
-
-=head1 CONFIGURATION
-
-See L<Catalyst::Action::Serialize/CONFIGURATION>. Note that the C<serialize>
-key has been deprecated.
-
-=head1 SERIALIZATION
-
-Catalyst::Controller::REST will automatically serialize your
-responses, and deserialize any POST, PUT or OPTIONS requests. It evaluates
-which serializer to use by mapping a content-type to a Serialization module.
-We select the content-type based on:
-
-=over
-
-=item B<The Content-Type Header>
-
-If the incoming HTTP Request had a Content-Type header set, we will use it.
-
-=item B<The content-type Query Parameter>
-
-If this is a GET request, you can supply a content-type query parameter.
-
-=item B<Evaluating the Accept Header>
-
-Finally, if the client provided an Accept header, we will evaluate
-it and use the best-ranked choice.
-
-=back
-
-=head1 AVAILABLE SERIALIZERS
-
-A given serialization mechanism is only available if you have the underlying
-modules installed.  For example, you can't use XML::Simple if it's not already
-installed.
-
-In addition, each serializer has its quirks in terms of what sorts of data
-structures it will properly handle.  L<Catalyst::Controller::REST> makes
-no attempt to save you from yourself in this regard. :)
-
-=over 2
-
-=item * C<text/x-yaml> => C<YAML::Syck>
-
-Returns YAML generated by L<YAML::Syck>.
-
-=item * C<text/html> => C<YAML::HTML>
-
-This uses L<YAML::Syck> and L<URI::Find> to generate YAML with all URLs turned
-to hyperlinks.  Only usable for Serialization.
-
-=item * C<application/json> => C<JSON>
-
-Uses L<JSON> to generate JSON output.  It is strongly advised to also have
-L<JSON::XS> installed.  The C<text/x-json> content type is supported but is
-deprecated and you will receive warnings in your log.
-
-You can also add a hash in your controller config to pass options to the json object.
-For instance, to relax permissions when deserializing input, add:
-  __PACKAGE__->config(
-    json_options => { relaxed => 1 }
-  )
-
-=item * C<text/javascript> => C<JSONP>
-
-If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON);
-
-Note - this is disabled by default as it can be a security risk if you are unaware.
-
-The usual MIME types for this serialization format are: 'text/javascript', 'application/x-javascript',
-'application/javascript'.
-
-=item * C<text/x-data-dumper> => C<Data::Serializer>
-
-Uses the L<Data::Serializer> module to generate L<Data::Dumper> output.
-
-=item * C<text/x-data-denter> => C<Data::Serializer>
-
-Uses the L<Data::Serializer> module to generate L<Data::Denter> output.
-
-=item * C<text/x-data-taxi> => C<Data::Serializer>
-
-Uses the L<Data::Serializer> module to generate L<Data::Taxi> output.
-
-=item * C<text/x-config-general> => C<Data::Serializer>
-
-Uses the L<Data::Serializer> module to generate L<Config::General> output.
-
-=item * C<text/x-php-serialization> => C<Data::Serializer>
-
-Uses the L<Data::Serializer> module to generate L<PHP::Serialization> output.
-
-=item * C<text/xml> => C<XML::Simple>
-
-Uses L<XML::Simple> to generate XML output.  This is probably not suitable
-for any real heavy XML work. Due to L<XML::Simple>s requirement that the data
-you serialize be a HASHREF, we transform outgoing data to be in the form of:
-
-  { data => $yourdata }
-
-=item * L<View>
-
-Uses a regular Catalyst view.  For example, if you wanted to have your
-C<text/html> and C<text/xml> views rendered by TT, set:
-
-  __PACKAGE__->config(
-      map => {
-          'text/html' => [ 'View', 'TT' ],
-          'text/xml'  => [ 'View', 'XML' ],
-      }
-  );
-
-Your views should have a C<process> method like this:
-
-  sub process {
-      my ( $self, $c, $stash_key ) = @_;
-
-      my $output;
-      eval {
-          $output = $self->serialize( $c->stash->{$stash_key} );
-      };
-      return $@ if $@;
-
-      $c->response->body( $output );
-      return 1;  # important
-  }
-
-  sub serialize {
-      my ( $self, $data ) = @_;
-
-      my $serialized = ... process $data here ...
-
-      return $serialized;
-  }
-
-=item * Callback
-
-For infinite flexibility, you can provide a callback for the
-deserialization/serialization steps.
-
-  __PACKAGE__->config(
-      map => {
-          'text/xml'  => [ 'Callback', { deserialize => \&parse_xml, serialize => \&render_xml } ],
-      }
-  );
-
-The C<deserialize> callback is passed a string that is the body of the
-request and is expected to return a scalar value that results from
-the deserialization.  The C<serialize> callback is passed the data
-structure that needs to be serialized and must return a string suitable
-for returning in the HTTP response.  In addition to receiving the scalar
-to act on, both callbacks are passed the controller object and the context
-(i.e. C<$c>) as the second and third arguments.
-
-=back
-
-By default, L<Catalyst::Controller::REST> will return a
-C<415 Unsupported Media Type> response if an attempt to use an unsupported
-content-type is made.  You can ensure that something is always returned by
-setting the C<default> config option:
-
-  __PACKAGE__->config(default => 'text/x-yaml');
-
-would make it always fall back to the serializer plugin defined for
-C<text/x-yaml>.
-
-=head1 CUSTOM SERIALIZERS
-
-Implementing new Serialization formats is easy!  Contributions
-are most welcome!  If you would like to implement a custom serializer,
-you should create two new modules in the L<Catalyst::Action::Serialize>
-and L<Catalyst::Action::Deserialize> namespace.  Then assign your new
-class to the content-type's you want, and you're done.
-
-See L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>
-for more information.
-
-=head1 STATUS HELPERS
-
-Since so much of REST is in using HTTP, we provide these Status Helpers.
-Using them will ensure that you are responding with the proper codes,
-headers, and entities.
-
-These helpers try and conform to the HTTP 1.1 Specification.  You can
-refer to it at: L<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
-
-=cut
-
-BEGIN { extends 'Catalyst::Controller' }
-use Params::Validate qw(SCALAR OBJECT);
-
-__PACKAGE__->mk_accessors(qw(serialize));
-
-__PACKAGE__->config(
-    'stash_key' => 'rest',
-    'map'       => {
-        'text/html'          => 'YAML::HTML',
-        'text/xml'           => 'XML::Simple',
-        'text/x-yaml'        => 'YAML',
-        'application/json'   => 'JSON',
-        'text/x-json'        => 'JSON',
-        'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
-        'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
-        'text/x-data-taxi'   => [ 'Data::Serializer', 'Data::Taxi'   ],
-        'text/x-config-general'    => [ 'Data::Serializer', 'Config::General' ],
-        'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
-    },
-);
-
-sub begin : ActionClass('Deserialize') { }
-
-sub end : ActionClass('Serialize') { }
-
-=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    = Params::Validate::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,
-    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;
-    my $c    = shift;
-    my %p    = Params::Validate::validate(
-        @_,
-        {
-            location => { type     => SCALAR | OBJECT },
-            entity   => { optional => 1 },
-        },
-    );
-
-    $c->response->status(201);
-    $c->response->header( 'Location' => $p{location} );
-    $self->_set_entity( $c, $p{'entity'} );
-    return 1;
-}
-
-=item status_accepted
-
-Returns a "202 ACCEPTED" response.  Takes an "entity" to serialize.
-Also takes optional "location" for queue type scenarios.
-
-Example:
-
-  $self->status_accepted(
-    $c,
-    location => $c->req->uri,
-    entity => {
-        status => "queued",
-    }
-  );
-
-=cut
-
-sub status_accepted {
-    my $self = shift;
-    my $c    = shift;
-    my %p    = Params::Validate::validate(
-        @_,
-        {
-            location => { type => SCALAR | OBJECT, optional => 1 },
-            entity   => 1,
-        },
-    );
-
-    $c->response->status(202);
-    $c->response->header( 'Location' => $p{location} ) if exists $p{location};
-    $self->_set_entity( $c, $p{'entity'} );
-    return 1;
-}
-
-=item status_no_content
-
-Returns a "204 NO CONTENT" response.
-
-=cut
-
-sub status_no_content {
-    my $self = shift;
-    my $c    = shift;
-    $c->response->status(204);
-    $self->_set_entity( $c, undef );
-    return 1;
-}
-
-=item status_multiple_choices
-
-Returns a "300 MULTIPLE CHOICES" response. Takes an "entity" to serialize, which should
-provide list of possible locations. Also takes optional "location" for preferred choice.
-
-=cut
-
-sub status_multiple_choices {
-    my $self = shift;
-    my $c    = shift;
-    my %p    = Params::Validate::validate(
-        @_,
-        {
-            entity => 1,
-            location => { type     => SCALAR | OBJECT, optional => 1 },
-        },
-    );
-
-    $c->response->status(300);
-    $c->response->header( 'Location' => $p{location} ) if exists $p{'location'};
-    $self->_set_entity( $c, $p{'entity'} );
-    return 1;
-}
-
-=item status_found
-
-Returns a "302 FOUND" response. Takes an "entity" to serialize.
-Also takes optional "location".
-
-=cut
-
-sub status_found {
-    my $self = shift;
-    my $c    = shift;
-    my %p    = Params::Validate::validate(
-        @_,
-        {
-            entity => 1,
-            location => { type     => SCALAR | OBJECT, optional => 1 },
-        },
-    );
-
-    $c->response->status(302);
-    $c->response->header( 'Location' => $p{location} ) if exists $p{'location'};
-    $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,
-    message => "Cannot do what you have asked!",
-  );
-
-=cut
-
-sub status_bad_request {
-    my $self = shift;
-    my $c    = shift;
-    my %p    = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
-
-    $c->response->status(400);
-    $c->log->debug( "Status Bad Request: " . $p{'message'} ) if $c->debug;
-    $self->_set_entity( $c, { error => $p{'message'} } );
-    return 1;
-}
-
-=item status_forbidden
-
-Returns a "403 FORBIDDEN" response.  Takes a "message" argument
-as a scalar, which will become the value of "error" in the serialized
-response.
-
-Example:
-
-  $self->status_forbidden(
-    $c,
-    message => "access denied",
-  );
-
-=cut
-
-sub status_forbidden {
-    my $self = shift;
-    my $c    = shift;
-    my %p    = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
-
-    $c->response->status(403);
-    $c->log->debug( "Status Forbidden: " . $p{'message'} ) if $c->debug;
-    $self->_set_entity( $c, { error => $p{'message'} } );
-    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,
-    message => "Cannot find what you were looking for!",
-  );
-
-=cut
-
-sub status_not_found {
-    my $self = shift;
-    my $c    = shift;
-    my %p    = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
-
-    $c->response->status(404);
-    $c->log->debug( "Status Not Found: " . $p{'message'} ) if $c->debug;
-    $self->_set_entity( $c, { error => $p{'message'} } );
-    return 1;
-}
-
-=item gone
-
-Returns a "41O GONE" response.  Takes a "message" argument as a scalar,
-which will become the value of "error" in the serialized response.
-
-Example:
-
-  $self->status_gone(
-    $c,
-    message => "The document have been deleted by foo",
-  );
-
-=cut
-
-sub status_gone {
-    my $self = shift;
-    my $c    = shift;
-    my %p    = Params::Validate::validate( @_, { message => { type => SCALAR }, }, );
-
-    $c->response->status(410);
-    $c->log->debug( "Status Gone " . $p{'message'} ) if $c->debug;
-    $self->_set_entity( $c, { error => $p{'message'} } );
-    return 1;
-}
-
-sub _set_entity {
-    my $self   = shift;
-    my $c      = shift;
-    my $entity = shift;
-    if ( defined($entity) ) {
-        $c->stash->{ $self->{'stash_key'} } = $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 IMPLEMENTATION DETAILS
-
-This Controller ties together L<Catalyst::Action::REST>,
-L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize>.  It should be suitable for most applications.  You should be aware that it:
-
-=over 4
-
-=item Configures the Serialization Actions
-
-This class provides a default configuration for Serialization.  It is currently:
-
-  __PACKAGE__->config(
-      'stash_key' => 'rest',
-      'map'       => {
-         'text/html'          => 'YAML::HTML',
-         'text/xml'           => 'XML::Simple',
-         'text/x-yaml'        => 'YAML',
-         'application/json'   => 'JSON',
-         'text/x-json'        => 'JSON',
-         'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
-         'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
-         'text/x-data-taxi'   => [ 'Data::Serializer', 'Data::Taxi'   ],
-         'application/x-storable'   => [ 'Data::Serializer', 'Storable' ],
-         'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ],
-         'text/x-config-general'    => [ 'Data::Serializer', 'Config::General' ],
-         'text/x-php-serialization' => [ 'Data::Serializer', 'PHP::Serialization' ],
-      },
-  );
-
-You can read the full set of options for this configuration block in
-L<Catalyst::Action::Serialize>.
-
-=item Sets a C<begin> and C<end> method for you
-
-The C<begin> method uses L<Catalyst::Action::Deserialize>.  The C<end>
-method uses L<Catalyst::Action::Serialize>.  If you want to override
-either behavior, simply implement your own C<begin> and C<end> actions
-and forward to another action with the Serialize and/or Deserialize
-action classes:
-
-  package Foo::Controller::Monkey;
-  use Moose;
-  use namespace::autoclean;
-
-  BEGIN { extends 'Catalyst::Controller::REST' }
-
-  sub begin : Private {
-    my ($self, $c) = @_;
-    ... do things before Deserializing ...
-    $c->forward('deserialize');
-    ... do things after Deserializing ...
-  }
-
-  sub deserialize : ActionClass('Deserialize') {}
-
-  sub end :Private {
-    my ($self, $c) = @_;
-    ... do things before Serializing ...
-    $c->forward('serialize');
-    ... do things after Serializing ...
-  }
-
-  sub serialize : ActionClass('Serialize') {}
-
-If you need to deserialize multipart requests (i.e. REST data in
-one part and file uploads in others) you can do so by using the
-L<Catalyst::Action::DeserializeMultiPart> action class.
-
-=back
-
-=head1 A MILD WARNING
-
-I have code in production using L<Catalyst::Controller::REST>.  That said,
-it is still under development, and it's possible that things may change
-between releases.  I promise to not break things unnecessarily. :)
-
-=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 AUTHORS
-
-See L<Catalyst::Action::REST> for authors.
-
-=head1 LICENSE
-
-You may distribute this code under the same terms as Perl itself.
-
-=cut
-
-__PACKAGE__->meta->make_immutable;
-
-1;