--- /dev/null
+META.yml
+Makefile
+blib
+inc
+pm_to_blib
+MANIFEST
+Makefile.old
+MANIFEST.bak
+
+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
\bCVS\b
,v$
\B\.svn\b
-\B\.git\b
+\B\.git(ignore)?\b
# Makemaker generated files and dirs.
^MANIFEST\.
^blib/
^MakeMaker-\d
^pm_to_blib$
-
+^Catalyst-Action-REST-
# Temp, old and emacs backup files.
~$
\.old$
requires('Data::Serializer' => '0.36');
requires('Class::Inspector' => '1.13');
requires('URI::Find' => undef);
+requires('MRO::Compat' => '0.10');
feature 'JSON (application/json) support',
-default => 0,
auto_include;
auto_install;
-repository('http://github.com/hdp/catalyst-action-rest');
+repository 'http://github.com/bobtfish/catalyst-action-rest';
WriteAll;
The "begin" method uses Catalyst::Action::Deserialize. The "end"
method uses Catalyst::Action::Serialize. If you want to override
either behavior, simply implement your own "begin" and "end" actions
- and use NEXT:
+ and use MRO::Compat:
my Foo::Controller::Monkey;
use base qw(Catalyst::Controller::REST);
sub begin :Private {
my ($self, $c) = @_;
- ... do things before Deserializing ...
- $self->NEXT::begin($c);
+ ... do things before Deserializing ...
+ $self->maybe::next::method($c);
... do things after Deserializing ...
}
sub end :Private {
my ($self, $c) = @_;
- ... do things before Serializing ...
- $self->NEXT::end($c);
+ ... do things before Serializing ...
+ $self->maybe::next::method($c);
... do things after Serializing ...
}
use base 'Catalyst::Action::SerializeBase';
use Module::Pluggable::Object;
+use MRO::Compat;
__PACKAGE__->mk_accessors(qw(plugins));
}
}
- $self->NEXT::execute(@_);
+ $self->maybe::next::method(@_);
return 1;
}
use base 'Catalyst::Action';
use Class::Inspector;
-use Catalyst;
use Catalyst::Request::REST;
use Catalyst::Controller::REST;
BEGIN { require 5.008001; }
-our $VERSION = '0.73';
+our $VERSION = '0.74';
sub new {
my $class = shift;
my $config = shift;
Catalyst::Request::REST->_insert_self_into( $config->{class} );
- return $class->SUPER::new($config, @_);
+ return $class->next::method($config, @_);
}
=head1 NAME
my $c = shift;
my $controller = $c->component( $self->class );
- my $method = $self->name . "_" . uc( $c->request->method );
- if ( my $action = $controller->action_for($method) ) {
+ my $rest_method = $self->name . "_" . uc( $c->request->method );
+
+ my ($code, $name);
+
+ # Common case, for foo_GET etc
+ if ( my $action = $controller->action_for($rest_method) ) {
return $c->forward( $action, $c->req->args );
- } elsif ( $controller->can($method) ) {
+ } elsif ($code = $controller->can($rest_method)) {
+ # Exceute normal action
$c->execute( $self->class, $self, @{ $c->req->args } );
- return $controller->$method( $c, @{ $c->req->args } );
- } else {
- if ( $c->request->method eq "OPTIONS" ) {
- return $self->_return_options($c);
- } else {
- my $handle_ni = $self->name . "_not_implemented";
- if ( $controller->can($handle_ni) ) {
- return $controller->$handle_ni( $c, @{ $c->req->args } );
- } else {
- return $self->_return_not_implemented($c);
- }
- }
+ $name = $rest_method;
}
-}
-sub _return_options {
- my ( $self, $c ) = @_;
+ # Generic handling for foo_OPTIONS
+ if (!$code && $c->request->method eq "OPTIONS") {
+ $name = $rest_method;
+ $code = sub { $self->_return_options($self->name, @_) };
+ }
- my @allowed = $self->_get_allowed_methods($c);
- $c->response->content_type('text/plain');
- $c->response->status(200);
- $c->response->header( 'Allow' => \@allowed );
+ # Otherwise, not implemented.
+ if (!$code) {
+ $name = $self->name . "_not_implemented";
+ $code = $controller->can($name) # User method
+ # Generic not implemented
+ || sub { $self->_return_not_implemented($self->name, @_) };
+ }
+
+ # localise stuff so we can dispatch the action 'as normal, but get
+ # different stats shown, and different code run.
+ local $self->{code} = $code;
+ local $self->{reverse} = $name;
+
+ $c->execute( $self->class, $self, @{ $c->req->args } );
}
sub _get_allowed_methods {
- my ( $self, $c ) = @_;
-
- my $controller = $self->class;
- my $methods = Class::Inspector->methods($controller);
+ my ( $self, $controller, $c, $name ) = @_;
+ my $class = ref($controller) ? ref($controller) : $controller;
+ my $methods = Class::Inspector->methods($class);
my @allowed;
foreach my $method ( @{$methods} ) {
- my $name = $self->name;
if ( $method =~ /^$name\_(.+)$/ ) {
push( @allowed, $1 );
}
}
return @allowed;
+};
+
+sub _return_options {
+ my ( $self, $method_name, $controller, $c) = @_;
+ my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
+ $c->response->content_type('text/plain');
+ $c->response->status(200);
+ $c->response->header( 'Allow' => \@allowed );
}
sub _return_not_implemented {
- my ( $self, $c ) = @_;
+ my ( $self, $method_name, $controller, $c ) = @_;
- my @allowed = $self->_get_allowed_methods($c);
+ my @allowed = $self->_get_allowed_methods($controller, $c, $method_name);
$c->response->content_type('text/plain');
$c->response->status(405);
$c->response->header( 'Allow' => \@allowed );
$c->response->body( "Method "
. $c->request->method
. " not implemented for "
- . $c->uri_for( $self->reverse ) );
+ . $c->uri_for( $method_name ) );
}
1;
=item Q: I'm getting a "415 Unsupported Media Type" error. What gives?!
+<<<<<<< HEAD:lib/Catalyst/Action/REST.pm
A: Most likely, you haven't set Content-type equal to "application/json", or one of the
accepted return formats. You can do this by setting it in your query string thusly:
?content-type=application%2Fjson (where %2F == / uri escaped).
=cut
+=======
+A: Most likely, you haven't set Content-type equal to "application/json", or
+one of the accepted return formats. You can do this by setting it in your query
+accepted return formats. You can do this by setting it in your query string
+thusly: C<< ?content-type=application%2Fjson (where %2F == / uri escaped). >>
+>>>>>>> f04ed654a172628f642bdefe8483c1e6becf9ad1:lib/Catalyst/Action/REST.pm
+B<NOTE> Apache will refuse %2F unless configured otherise.
+Make sure C<< AllowEncodedSlashes On >> is in your httpd.conf file in orde
+for this to run smoothly.
-
-=head1 MAINTAINER
-
-J. Shirley <jshirley@gmail.com>
+=back
=head1 CONTRIBUTORS
Daisuke Maki <daisuke@endeworks.jp>
+J. Shirley <jshirley@gmail.com>
+
+Hans Dieter Pearcey
+
+Tomas Doran (t0m) <bobtfish@bobtfish.net>
+
=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)
+Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
=head1 LICENSE
use base 'Catalyst::Action::SerializeBase';
use Module::Pluggable::Object;
+use MRO::Compat;
sub execute {
my $self = shift;
my ( $controller, $c ) = @_;
- $self->NEXT::execute(@_);
+ $self->maybe::next::method(@_);
return 1 if $c->req->method eq 'HEAD';
return 1 if length( $c->response->body );
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
# 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',
}
# Answer PUT requests to "thing"
- sub thing_PUT {
+ sub thing_PUT {
.. some action ..
}
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.
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
=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
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
=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.
=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
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.
Example:
$self->status_ok(
- $c,
+ $c,
entity => {
radiohead => "Is a good band!",
}
Example:
$self->status_created(
- $c,
+ $c,
location => $c->req->uri->as_string,
entity => {
radiohead => "Is a good band!",
Example:
$self->status_accepted(
- $c,
+ $c,
entity => {
status => "queued",
}
Example:
$self->status_bad_request(
- $c,
+ $c,
message => "Cannot do what you have asked!",
);
Example:
$self->status_not_found(
- $c,
+ $c,
message => "Cannot find what you were looking for!",
);
'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' ]
,
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 use NEXT:
+and use MRO::Compat:
my Foo::Controller::Monkey;
use base qw(Catalyst::Controller::REST);
sub begin :Private {
my ($self, $c) = @_;
... do things before Deserializing ...
- $self->NEXT::begin($c);
+ $self->maybe::next::method($c);
... do things after Deserializing ...
- }
+ }
sub end :Private {
my ($self, $c) = @_;
... do things before Serializing ...
- $self->NEXT::end($c);
+ $self->maybe::next::method($c);
... do things after Serializing ...
}
use strict;
use warnings;
+use Scalar::Util qw/blessed/;
use base qw/Catalyst::Request Class::Accessor::Fast/;
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);
__PACKAGE__->mk_accessors(qw(data accept_only));
-=over 4
+=over 4
=item accepted_content_types
-package Test::Catalyst::Action::REST;
-
-use FindBin;
-
-use lib ("$FindBin::Bin/../lib");
-
-use strict;
-use warnings;
-
-use Catalyst::Runtime '5.70';
-
-use Catalyst;
-
-__PACKAGE__->config( name => 'Test::Catalyst::Action::REST' );
-__PACKAGE__->setup;
-
-sub test : Local : ActionClass('REST') {
- my ( $self, $c ) = @_;
- $c->stash->{'entity'} = 'something';
-}
-
-sub test_GET : Local : ActionClass('REST') {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} .= " GET";
- $c->forward('ok');
-}
-
-sub test_POST : Local : ActionClass('REST') {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} .= " POST";
- $c->forward('ok');
-}
-
-sub test_PUT : Local : ActionClass('REST') {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} .= " PUT";
- $c->forward('ok');
-}
-
-sub test_DELETE : Local : ActionClass('REST') {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} .= " DELETE";
- $c->forward('ok');
-}
-
-sub test_OPTIONS : Local : ActionClass('REST') {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} .= " OPTIONS";
- $c->forward('ok');
-}
-
-sub notreally : Local : ActionClass('REST') {
-}
-
-sub notreally_GET {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} = "notreally GET";
- $c->forward('ok');
-}
-
-sub not_implemented : Local : ActionClass('REST') {
-}
-
-sub not_implemented_GET {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} = "not_implemented GET";
- $c->forward('ok');
-}
-
-sub not_implemented_not_implemented {
- my ( $self, $c ) = @_;
-
- $c->stash->{'entity'} = "Not Implemented Handler";
- $c->forward('ok');
-}
-
-sub not_modified : Local : ActionClass('REST') { }
-
-sub not_modified_GET {
- my ( $self, $c ) = @_;
- $c->res->status(304);
- return 1;
-}
-
-
-sub ok : Private {
- my ( $self, $c ) = @_;
-
- $c->res->content_type('text/plain');
- $c->res->body( $c->stash->{'entity'} );
-}
-
-package main;
-
use strict;
use warnings;
use Test::More tests => 18;
$res = request(
$t->$run_method(
url => '/test',
- data => { foo => 'bar' }
+ data => '',
)
);
}
--- /dev/null
+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'));
+
--- /dev/null
+package Test::Catalyst::Action::REST::Controller::Root;
+use strict;
+use warnings;
+
+use base qw/Catalyst::Controller::REST/;
+
+__PACKAGE__->config( namespace => '' );
+
+sub begin {} # Don't need serialization..
+
+sub test : Local : ActionClass('REST') {
+ my ( $self, $c ) = @_;
+ $c->stash->{'entity'} = 'something';
+}
+
+sub test_GET {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} .= " GET";
+ $c->forward('ok');
+}
+
+sub test_POST {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} .= " POST";
+ $c->forward('ok');
+}
+
+sub test_PUT {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} .= " PUT";
+ $c->forward('ok');
+}
+
+sub test_DELETE {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} .= " DELETE";
+ $c->forward('ok');
+}
+
+sub test_OPTIONS {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} .= " OPTIONS";
+ $c->forward('ok');
+}
+
+sub notreally : Local : ActionClass('REST') {
+}
+
+sub notreally_GET {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} = "notreally GET";
+ $c->forward('ok');
+}
+
+sub not_implemented : Local : ActionClass('REST') {
+}
+
+sub not_implemented_GET {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} = "not_implemented GET";
+ $c->forward('ok');
+}
+
+sub not_implemented_not_implemented {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{'entity'} = "Not Implemented Handler";
+ $c->forward('ok');
+}
+
+sub not_modified : Local : ActionClass('REST') { }
+
+sub not_modified_GET {
+ my ( $self, $c ) = @_;
+ $c->res->status(304);
+ return 1;
+}
+
+sub ok : Private {
+ my ( $self, $c ) = @_;
+
+ $c->res->content_type('text/plain');
+ $c->res->body( $c->stash->{'entity'} );
+}
+
+sub end : Private {} # Don't need serialization..
+
+1;
+
--- /dev/null
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Pod 1.14";
+plan skip_all => 'Test::Pod 1.14 required' if $@;
+plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
+
+all_pod_files_ok();