From: Tomas Doran (t0m) Date: Sat, 25 Jul 2009 10:21:29 +0000 (+0100) Subject: Merge X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=bcf4a9367933ca918e6a0ac49945151e6e2e74e4;hp=0094cac67949c96a6c0de2c66baf321af6321467;p=catagits%2FCatalyst-Action-REST.git Merge --- diff --git a/Changes b/Changes index 79f274a..5278db8 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,19 @@ +Wed Jul 22 23:49:16 BST 2009 (t0m) - Release 0.74 + Switch from NEXT to MRO::Compat (agladdish). + Add display of additional REST actions in the stats, and also fix a warning + in Catalyst 5.80 when you forward to another action from inside an + action_FOO method (as it was confusing the stats). + + POD fixes + + Catalyst::Action::REST no longer @ISA Catalyst or Catalyst::Controller. + + Change constructor to call next::method instead of SUPER:: + + Change method used to find the application class to be more correct + Sat Jun 27 20:20:09 EDT 2009 (hdp) - Release 0.73 Packaging fixes diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 29069a5..32106f2 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -3,7 +3,7 @@ \bCVS\b ,v$ \B\.svn\b -\B\.git\b +\B\.git(ignore)?\b # Makemaker generated files and dirs. ^MANIFEST\. @@ -11,7 +11,7 @@ ^blib/ ^MakeMaker-\d ^pm_to_blib$ - +^Catalyst-Action-REST- # Temp, old and emacs backup files. ~$ \.old$ diff --git a/Makefile.PL b/Makefile.PL index b39390b..cd8b295 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -43,7 +43,7 @@ feature 'XML::Simple (text/xml) support', auto_include; auto_install; -repository('http://github.com/hdp/catalyst-action-rest'); +repository 'http://github.com/bobtfish/catalyst-action-rest'; WriteAll; diff --git a/lib/Catalyst/Action/REST.pm b/lib/Catalyst/Action/REST.pm index 4a35ece..631df04 100644 --- a/lib/Catalyst/Action/REST.pm +++ b/lib/Catalyst/Action/REST.pm @@ -3,24 +3,20 @@ # Created by: Adam Jacob, Marchex, # Created on: 10/12/2006 03:00:32 PM PDT # -# $Id$ package Catalyst::Action::REST; - -use strict; -use warnings; - -use base 'Catalyst::Action'; +use Moose; use Class::Inspector; use Moose::Util qw(does_role); -use Catalyst; use Catalyst::RequestRole::REST; use Catalyst::Controller::REST; use namespace::clean -except => 'meta'; +extends 'Catalyst::Action'; + BEGIN { require 5.008001; } -our $VERSION = '0.73'; +our $VERSION = '0.74'; =head1 NAME @@ -183,10 +179,6 @@ for this to run smoothly. =back -=head1 MAINTAINER - -Hans Dieter Pearcey - =head1 CONTRIBUTORS Christopher Laco @@ -199,6 +191,8 @@ Daisuke Maki J. Shirley +Hans Dieter Pearcey + Tomas Doran (t0m) =head1 AUTHOR diff --git a/lib/Catalyst/Controller/REST.pm b/lib/Catalyst/Controller/REST.pm index 80bbaa4..e6036d9 100644 --- a/lib/Catalyst/Controller/REST.pm +++ b/lib/Catalyst/Controller/REST.pm @@ -1,10 +1,10 @@ package Catalyst::Controller::REST; -our $VERSION = '0.73'; +our $VERSION = '0.74'; =head1 NAME -Catalyst::Controller::REST - A RESTful controller +Catalyst::Controller::REST - A RESTful controller =head1 SYNOPSIS @@ -17,11 +17,11 @@ Catalyst::Controller::REST - A RESTful controller # Answer GET requests to "thing" sub thing_GET { my ( $self, $c ) = @_; - + # Return a 200 OK, with the data in entity - # serialized in the body + # serialized in the body $self->status_ok( - $c, + $c, entity => { some => 'data', foo => 'is real bar-y', @@ -30,7 +30,7 @@ Catalyst::Controller::REST - A RESTful controller } # Answer PUT requests to "thing" - sub thing_PUT { + sub thing_PUT { .. some action .. } @@ -38,23 +38,23 @@ Catalyst::Controller::REST - A RESTful controller 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, +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'). +"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". +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 method. +C 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. @@ -69,18 +69,18 @@ contents of $c->request->body based on the requests content-type header. A list of understood serialization formats is below. If we do not have (or cannot run) a serializer for a given content-type, a 415 -"Unsupported Media Type" error is generated. +"Unsupported Media Type" error is generated. To make your Controller RESTful, simply have it - use base 'Catalyst::Controller::REST'; + use base 'Catalyst::Controller::REST'; =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: +We select the content-type based on: =over 2 @@ -95,7 +95,7 @@ If this is a GET request, you can supply a content-type query parameter. =item B Finally, if the client provided an Accept header, we will evaluate -it and use the best-ranked choice. +it and use the best-ranked choice. =back @@ -103,11 +103,11 @@ it and use the best-ranked choice. 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. +installed. In addition, each serializer has it's quirks in terms of what sorts of data structures it will properly handle. L makes -no attempt to save you from yourself in this regard. :) +no attempt to save you from yourself in this regard. :) =over 2 @@ -122,7 +122,7 @@ to hyperlinks. Only useable for Serialization. =item C => C -Uses L to generate JSON output. It is strongly advised to also have +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. @@ -164,13 +164,13 @@ you serialize be a HASHREF, we transform outgoing data to be in the form of: =item L -Uses a regular Catalyst view. For example, if you wanted to have your +Uses a regular Catalyst view. For example, if you wanted to have your C and C views rendered by TT: 'text/html' => [ 'View', 'TT' ], 'text/xml' => [ 'View', 'XML' ], - -Will do the trick nicely. + +Will do the trick nicely. =back @@ -201,7 +201,7 @@ 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. +refer to it at: L. These routines are all implemented as regular subroutines, and as such require you pass the current context ($c) as the first argument. @@ -245,7 +245,7 @@ Returns a "200 OK" response. Takes an "entity" to serialize. Example: $self->status_ok( - $c, + $c, entity => { radiohead => "Is a good band!", } @@ -271,7 +271,7 @@ and a "location" where the created object can be found. Example: $self->status_created( - $c, + $c, location => $c->req->uri->as_string, entity => { radiohead => "Is a good band!", @@ -313,7 +313,7 @@ Returns a "202 ACCEPTED" response. Takes an "entity" to serialize. Example: $self->status_accepted( - $c, + $c, entity => { status => "queued", } @@ -340,7 +340,7 @@ response. Example: $self->status_bad_request( - $c, + $c, message => "Cannot do what you have asked!", ); @@ -366,7 +366,7 @@ response. Example: $self->status_not_found( - $c, + $c, message => "Cannot find what you were looking for!", ); @@ -423,9 +423,9 @@ This class provides a default configuration for Serialization. It is currently: '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-storable' => [ 'Data::Serializer', 'Storable' ], - 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' + 'application/x-freezethaw' => [ 'Data::Serializer', 'FreezeThaw' ], 'text/x-config-general' => [ 'Data::Serializer', 'Config::General' ] , @@ -449,14 +449,14 @@ and use MRO::Compat: sub begin :Private { my ($self, $c) = @_; - ... do things before Deserializing ... - $self->maybe::next::method($c); + ... do things before Deserializing ... + $self->maybe::next::method($c); ... do things after Deserializing ... - } + } sub end :Private { my ($self, $c) = @_; - ... do things before Serializing ... + ... do things before Serializing ... $self->maybe::next::method($c); ... do things after Serializing ... } diff --git a/lib/Catalyst/Request/REST.pm b/lib/Catalyst/Request/REST.pm index 2fcf9e2..b1404af 100644 --- a/lib/Catalyst/Request/REST.pm +++ b/lib/Catalyst/Request/REST.pm @@ -3,13 +3,8 @@ # Created by: Adam Jacob, Marchex, # Created on: 10/13/2006 03:54:33 PM PDT # -# $Id: $ package Catalyst::Request::REST; - -use strict; -use warnings; - use Moose; extends qw/Catalyst::Request/; with qw/Catalyst::RequestRole::REST Catalyst::RequestRole::Deserialize/; @@ -20,7 +15,8 @@ sub _insert_self_into { my ($class, $app_class ) = @_; # the fallback to $app_class is for the (rare and deprecated) case when # people are defining actions in MyApp.pm instead of in a controller. - my $app = Catalyst::Utils::class2appclass( $app_class ) || $app_class; + my $app = (blessed($app_class) && $app_class->can('_application')) + ? $app_class->_application : Catalyst::Utils::class2appclass( $app_class ) || $app_class; my $req_class = $app->request_class; return if $req_class->isa($class); @@ -36,6 +32,136 @@ sub _insert_self_into { Catalyst::Request::REST - A REST-y subclass of Catalyst::Request +=head1 SYNOPSIS + + if ( $c->request->accepts('application/json') ) { + ... + } + + my $types = $c->request->accepted_content_types(); + +=head1 DESCRIPTION + +This is a subclass of C that adds a few methods to +the request object to faciliate writing REST-y code. Currently, these +methods are all related to the content types accepted by the client. + +Note that if you have a custom request class in your application, and it does +not inherit from C, your application will fail with an +error indicating a conflict the first time it tries to use +C's functionality. To fix this error, make sure your +custom request class inherits from C. + +=head1 METHODS + +If the request went through the Deserializer action, this method will +returned the deserialized data structure. + +=cut + +__PACKAGE__->mk_accessors(qw(data accept_only)); + +=over 4 + +=item accepted_content_types + +Returns an array reference of content types accepted by the +client. + +The list of types is created by looking at the following sources: + +=over 8 + +=item * Content-type header + +If this exists, this will always be the first type in the list. + +=item * content-type parameter + +If the request is a GET request and there is a "content-type" +parameter in the query string, this will come before any types in the +Accept header. + +=item * Accept header + +This will be parsed and the types found will be ordered by the +relative quality specified for each type. + +=back + +If a type appears in more than one of these places, it is ordered based on +where it is first found. + +=cut + +sub accepted_content_types { + my $self = shift; + + return $self->{content_types} if $self->{content_types}; + + my %types; + + # First, we use the content type in the HTTP Request. It wins all. + $types{ $self->content_type } = 3 + if $self->content_type; + + if ($self->method eq "GET" && $self->param('content-type')) { + $types{ $self->param('content-type') } = 2; + } + + # Third, we parse the Accept header, and see if the client + # takes a format we understand. + # + # This is taken from chansen's Apache2::UploadProgress. + if ( $self->header('Accept') ) { + $self->accept_only(1) unless keys %types; + + my $accept_header = $self->header('Accept'); + my $counter = 0; + + foreach my $pair ( split_header_words($accept_header) ) { + my ( $type, $qvalue ) = @{$pair}[ 0, 3 ]; + next if $types{$type}; + + unless ( defined $qvalue ) { + $qvalue = 1 - ( ++$counter / 1000 ); + } + + $types{$type} = sprintf( '%.3f', $qvalue ); + } + } + + return $self->{content_types} = + [ sort { $types{$b} <=> $types{$a} } keys %types ]; +} + +=item preferred_content_type + +This returns the first content type found. It is shorthand for: + + $request->accepted_content_types->[0] + +=cut + +sub preferred_content_type { $_[0]->accepted_content_types->[0] } + +=item accepts($type) + +Given a content type, this returns true if the type is accepted. + +Note that this does not do any wildcard expansion of types. + +=cut + +sub accepts { + my $self = shift; + my $type = shift; + + return grep { $_ eq $type } @{ $self->accepted_content_types }; +} + +=back + =head1 AUTHOR Adam Jacob , with lots of help from mst and jrockway diff --git a/lib/Catalyst/RequestRole/REST.pm b/lib/Catalyst/RequestRole/REST.pm index c46a7c7..707e5e1 100644 --- a/lib/Catalyst/RequestRole/REST.pm +++ b/lib/Catalyst/RequestRole/REST.pm @@ -7,9 +7,9 @@ use HTTP::Headers::Util qw(split_header_words); use namespace::clean -except => 'meta'; has accept_only => ( - is => 'ro', + is => 'rw', isa => 'Bool', - writer => '_set_accept_only', +# writer => '_set_accept_only', FIXME fails for me if I use this default => 0, ); @@ -44,7 +44,7 @@ sub _build_accepted_content_types { # # This is taken from chansen's Apache2::UploadProgress. if ( $self->header('Accept') ) { - $self->_set_accept_only(1) unless keys %types; + $self->accept_only(1) unless keys %types; # FIXME fails if _set_accept_only my $accept_header = $self->header('Accept'); my $counter = 0; diff --git a/t/isa.t b/t/isa.t new file mode 100644 index 0000000..14e3343 --- /dev/null +++ b/t/isa.t @@ -0,0 +1,20 @@ +use strict; +use warnings; + +use FindBin qw/$Bin/; +use lib "$Bin/lib"; + +use Test::More tests => 5; + +use Test::Catalyst::Action::REST; + +my $controller = Test::Catalyst::Action::REST->controller('Root'); +ok $controller; + +my $action = $controller->action_for('test'); +ok $action; + +isa_ok($action, 'Catalyst::Action::REST'); +ok(!$action->isa('Catalyst')); +ok(!$action->isa('Catalyst::Controller')); +