+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$
auto_include;
auto_install;
-repository('http://github.com/hdp/catalyst-action-rest');
+repository 'http://github.com/bobtfish/catalyst-action-rest';
WriteAll;
# 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
=back
-=head1 MAINTAINER
-
-Hans Dieter Pearcey
-
=head1 CONTRIBUTORS
Christopher Laco
J. Shirley <jshirley@gmail.com>
+Hans Dieter Pearcey
+
Tomas Doran (t0m) <bobtfish@bobtfish.net>
=head1 AUTHOR
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' ]
,
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 ...
}
# 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/;
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);
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
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,
);
#
# 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;
--- /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'));
+