Merge
Tomas Doran (t0m) [Sat, 25 Jul 2009 10:21:29 +0000 (11:21 +0100)]
Changes
MANIFEST.SKIP
Makefile.PL
lib/Catalyst/Action/REST.pm
lib/Catalyst/Controller/REST.pm
lib/Catalyst/Request/REST.pm
lib/Catalyst/RequestRole/REST.pm
t/isa.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 79f274a..5278db8 100644 (file)
--- 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
 
index 29069a5..32106f2 100644 (file)
@@ -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$
index b39390b..cd8b295 100644 (file)
@@ -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;
 
index 4a35ece..631df04 100644 (file)
@@ -3,24 +3,20 @@
 # Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
 # 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 <daisuke@endeworks.jp>
 
 J. Shirley <jshirley@gmail.com>
 
+Hans Dieter Pearcey
+
 Tomas Doran (t0m) <bobtfish@bobtfish.net>
 
 =head1 AUTHOR
index 80bbaa4..e6036d9 100644 (file)
@@ -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<thing_not_implemented> method.  
+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.
@@ -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<Evaluating the Accept Header>
 
 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<Catalyst::Controller::REST> 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<application/json> => C<JSON>
 
-Uses L<JSON> to generate JSON output.  It is strongly advised to also have 
+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.
 
@@ -164,13 +164,13 @@ you serialize be a HASHREF, we transform outgoing data to be in the form of:
 
 =item L<View>
 
-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<text/html> and C<text/xml> 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<http://www.w3.org/Protocols/rfc2616/rfc2616.txt>.  
+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.
 
@@ -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 ...
   }
index 2fcf9e2..b1404af 100644 (file)
@@ -3,13 +3,8 @@
 # Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
 # 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<Catalyst::Request> 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<Catalyst::Request::REST>, your application will fail with an
+error indicating a conflict the first time it tries to use
+C<Catalyst::Request::REST>'s functionality.  To fix this error, make sure your
+custom request class inherits from C<Catalyst::Request::REST>.
+
+=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 <adam@stalecoffee.org>, with lots of help from mst and jrockway
index c46a7c7..707e5e1 100644 (file)
@@ -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 (file)
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'));
+