X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FController%2FREST.pm;h=54609186cfa79053c0da89dbb68abd15264ab097;hb=d0822465cc09629df95ad9c522bce758f2badc8c;hp=689ca901bd21b9c64fc7e5769d4e3e71ade92b45;hpb=96a61a601b093754a99bb61b57e41dbfc3228ad7;p=catagits%2FCatalyst-Action-REST.git diff --git a/lib/Catalyst/Controller/REST.pm b/lib/Catalyst/Controller/REST.pm index 689ca90..5460918 100644 --- a/lib/Catalyst/Controller/REST.pm +++ b/lib/Catalyst/Controller/REST.pm @@ -2,7 +2,7 @@ package Catalyst::Controller::REST; use Moose; use namespace::autoclean; -our $VERSION = '0.84'; +our $VERSION = '1.02'; $VERSION = eval $VERSION; =head1 NAME @@ -14,7 +14,7 @@ Catalyst::Controller::REST - A RESTful controller package Foo::Controller::Bar; use Moose; use namespace::autoclean; - + BEGIN { extends 'Catalyst::Controller::REST' } sub thing : Local : ActionClass('REST') { } @@ -36,16 +36,18 @@ Catalyst::Controller::REST - A RESTful controller # Answer PUT requests to "thing" sub thing_PUT { - $radiohead = $req->data->{radiohead}; - + my ( $self, $c ) = @_; + + $radiohead = $c->req->data->{radiohead}; + $self->status_created( $c, - location => $c->req->uri->as_string, + location => $c->req->uri, entity => { radiohead => $radiohead, } ); - } + } =head1 DESCRIPTION @@ -79,7 +81,7 @@ which are described below. "The HTTP POST, PUT, and OPTIONS methods will all automatically L the contents of -C<< $c->request->body >> into the C<< $c->request->data >> hashref", based on +C<< $c->request->body >> into the C<< $c->request->data >> hashref", based on the request's C header. A list of understood serialization formats is L. @@ -146,6 +148,12 @@ Uses L to generate JSON output. It is strongly advised to also have L installed. The C 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 => C If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON); @@ -217,7 +225,7 @@ Your views should have a C method like this: $c->response->body( $output ); return 1; # important } - + sub serialize { my ( $self, $data ) = @_; @@ -226,9 +234,28 @@ Your views should have a C method like this: 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 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 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 will return a +By default, L 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 config option: @@ -241,12 +268,12 @@ C. =head1 CUSTOM SERIALIZERS Implementing new Serialization formats is easy! Contributions -are most welcome! If you would like to implement a custom serializer, +are most welcome! If you would like to implement a custom serializer, you should create two new modules in the L and L namespace. Then assign your new class to the content-type's you want, and you're done. -See L and L +See L and L for more information. =head1 STATUS HELPERS @@ -325,7 +352,7 @@ Example: $self->status_created( $c, - location => $c->req->uri->as_string, + location => $c->req->uri, entity => { radiohead => "Is a good band!", } @@ -347,14 +374,8 @@ sub status_created { }, ); - my $location; - if ( ref( $p{'location'} ) ) { - $location = $p{'location'}->as_string; - } else { - $location = $p{'location'}; - } $c->response->status(201); - $c->response->header( 'Location' => $location ); + $c->response->header( 'Location' => $p{location} ); $self->_set_entity( $c, $p{'entity'} ); return 1; } @@ -362,11 +383,13 @@ sub status_created { =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", } @@ -377,9 +400,16 @@ Example: sub status_accepted { my $self = shift; my $c = shift; - my %p = Params::Validate::validate( @_, { entity => 1, }, ); + 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; } @@ -395,7 +425,7 @@ sub status_no_content { my $c = shift; $c->response->status(204); $self->_set_entity( $c, undef ); - return 1.; + return 1; } =item status_multiple_choices @@ -416,14 +446,32 @@ sub status_multiple_choices { }, ); - my $location; - if ( ref( $p{'location'} ) ) { - $location = $p{'location'}->as_string; - } else { - $location = $p{'location'}; - } $c->response->status(300); - $c->response->header( 'Location' => $location ) if exists $p{'location'}; + $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; } @@ -454,6 +502,32 @@ sub status_bad_request { 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 @@ -559,28 +633,37 @@ L. The C method uses L. The C method uses L. If you want to override either behavior, simply implement your own C and C actions -and use MRO::Compat: +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 { + sub begin : Private { my ($self, $c) = @_; ... do things before Deserializing ... - $self->maybe::next::method($c); + $c->forward('deserialize'); ... do things after Deserializing ... } + sub deserialize : ActionClass('Deserialize') {} + sub end :Private { my ($self, $c) = @_; ... do things before Serializing ... - $self->maybe::next::method($c); + $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 action class. + =back =head1 A MILD WARNING @@ -612,4 +695,6 @@ You may distribute this code under the same terms as Perl itself. =cut +__PACKAGE__->meta->make_immutable; + 1;