From: Arthur Axel 'fREW' Schmidt Date: Thu, 27 Aug 2009 00:47:21 +0000 (-0500) Subject: Merge branch 'master' of git://github.com/bobtfish/catalyst-action-rest X-Git-Tag: 0.77~4 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Action-REST.git;a=commitdiff_plain;h=29739a599abe110c310ec26dc2b528f773bc579b;hp=5c4dd54f1007d731f225d9c065ded455a499dd78 Merge branch 'master' of git://github.com/bobtfish/catalyst-action-rest --- diff --git a/Changes b/Changes index 5278db8..3af8865 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,18 @@ + Fix test if CATALYST_DEBUG environment variable is set + +Fri Aug 21 21:20:52 BST 2009 (t0m) - Release 0.76 + + Added two new status response helpers (202 no content and 410 gone), + and tests - Franck Cuny + +Mon Aug 17 14:07:41 BST 2009 (t0m) - Release 0.75 + + Fix optional test failures in catalyst-action-serialize-accept.t + + Added a serializer for JSON::XS + + Made test independent of YAML::Syck bugs (dandv) + Wed Jul 22 23:49:16 BST 2009 (t0m) - Release 0.74 Switch from NEXT to MRO::Compat (agladdish). diff --git a/README b/README index fcab6bd..4011e1d 100644 --- a/README +++ b/README @@ -2,7 +2,7 @@ NAME Catalyst::Controller::REST - A RESTful controller VERSION - 0.73 + 0.75 SYNOPSIS package Foo::Controller::Bar; diff --git a/lib/Catalyst/Action/REST.pm b/lib/Catalyst/Action/REST.pm index 4a607b0..f4e0a2f 100644 --- a/lib/Catalyst/Action/REST.pm +++ b/lib/Catalyst/Action/REST.pm @@ -17,7 +17,8 @@ use Catalyst::Controller::REST; BEGIN { require 5.008001; } -our $VERSION = '0.74'; +our $VERSION = '0.76'; +$VERSION = eval $VERSION; sub new { my $class = shift; diff --git a/lib/Catalyst/Action/Serialize/JSON.pm b/lib/Catalyst/Action/Serialize/JSON.pm index 561b49b..7423e9e 100644 --- a/lib/Catalyst/Action/Serialize/JSON.pm +++ b/lib/Catalyst/Action/Serialize/JSON.pm @@ -24,7 +24,7 @@ sub execute { ) || 'rest'; my $output; eval { - $output = encode_json( $c->stash->{$stash_key} ); + $output = $self->serialize( $c->stash->{$stash_key} ); }; if ($@) { return $@; @@ -33,4 +33,9 @@ sub execute { return 1; } +sub serialize { + my $self = shift; + encode_json( shift ); +} + 1; diff --git a/lib/Catalyst/Action/Serialize/JSON/XS.pm b/lib/Catalyst/Action/Serialize/JSON/XS.pm new file mode 100644 index 0000000..b1f4850 --- /dev/null +++ b/lib/Catalyst/Action/Serialize/JSON/XS.pm @@ -0,0 +1,14 @@ +package Catalyst::Action::Serialize::JSON::XS; + +use strict; +use warnings; + +use base 'Catalyst::Action::Serialize::JSON'; +use JSON::XS qw(encode_json); + +sub serialize { + my $self = shift; + encode_json( shift ); +} + +1; diff --git a/lib/Catalyst/Action/Serialize/YAML.pm b/lib/Catalyst/Action/Serialize/YAML.pm index c943569..4879af9 100644 --- a/lib/Catalyst/Action/Serialize/YAML.pm +++ b/lib/Catalyst/Action/Serialize/YAML.pm @@ -24,7 +24,7 @@ sub execute { ) || 'rest'; my $output; eval { - $output = Dump($c->stash->{$stash_key}); + $output = $self->serialize($c->stash->{$stash_key}); }; if ($@) { return $@; @@ -33,4 +33,10 @@ sub execute { return 1; } +sub serialize { + my $self = shift; + my $data = shift; + Dump($data); +} + 1; diff --git a/lib/Catalyst/Controller/REST.pm b/lib/Catalyst/Controller/REST.pm index e6036d9..1e72127 100644 --- a/lib/Catalyst/Controller/REST.pm +++ b/lib/Catalyst/Controller/REST.pm @@ -1,6 +1,9 @@ package Catalyst::Controller::REST; +use strict; +use warnings; -our $VERSION = '0.74'; +our $VERSION = '0.76'; +$VERSION = eval $VERSION; =head1 NAME @@ -167,8 +170,8 @@ you serialize be a HASHREF, we transform outgoing data to be in the form of: 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' ], + 'text/html' => [ 'View', 'TT' ], + 'text/xml' => [ 'View', 'XML' ], Will do the trick nicely. @@ -179,7 +182,7 @@ 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: - __PACKAGE__->config->{'default'} = 'text/x-yaml'; + __PACKAGE__->config->{'default'} = 'text/x-yaml'; Would make it always fall back to the serializer plugin defined for text/x-yaml. @@ -209,8 +212,6 @@ such require you pass the current context ($c) as the first argument. =cut -use strict; -use warnings; use base 'Catalyst::Controller'; use Params::Validate qw(SCALAR OBJECT); @@ -331,6 +332,20 @@ sub status_accepted { return 1; } +=item status_no_content + +Returns a "204 NO CONTENT" response. + +=cut + +sub status_no_content { + my $self = shift; + my $c = shift; + $c->response->status(204); + $self->_set_entity( $c, undef ); + return 1.; +} + =item status_bad_request Returns a "400 BAD REQUEST" response. Takes a "message" argument @@ -383,6 +398,31 @@ sub status_not_found { return 1; } +=item gone + +Returns a "41O GONE" response. Takes a "message" argument as a scalar, +which will become the value of "error" in the serialized response. + +Example: + + $self->status_gone( + $c, + message => "The document have been deleted by foo", + ); + +=cut + +sub status_gone { + my $self = shift; + my $c = shift; + my %p = Params::Validate::validate( @_, { message => { type => SCALAR }, }, ); + + $c->response->status(410); + $c->log->debug( "Status Gone " . $p{'message'} ) if $c->debug; + $self->_set_entity( $c, { error => $p{'message'} } ); + return 1; +} + sub _set_entity { my $self = shift; my $c = shift; diff --git a/t/catalyst-action-serialize-accept.t b/t/catalyst-action-serialize-accept.t index 2e268e0..8b9408f 100644 --- a/t/catalyst-action-serialize-accept.t +++ b/t/catalyst-action-serialize-accept.t @@ -6,31 +6,32 @@ use FindBin; use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib", "$FindBin::Bin/broken"); use Test::Rest; +use Catalyst::Action::Serialize::YAML; -# Should use Data::Dumper, via YAML +# Should use Data::Dumper, via YAML my $t = Test::Rest->new('content_type' => 'text/x-yaml'); use_ok 'Catalyst::Test', 'Test::Catalyst::Action::REST'; -my $data = <serialize({lou => 'is my cat'}); { - my $req = $t->get(url => '/serialize/test'); - $req->remove_header('Content-Type'); - $req->header('Accept', 'text/x-yaml'); - my $res = request($req); + my $req = $t->get(url => '/serialize/test'); + $req->remove_header('Content-Type'); + $req->header('Accept', 'text/x-yaml'); + my $res = request($req); SKIP: { skip "can't test text/x-yaml without YAML support", - 3 if ( - not $res->is_success and - $res->content =~ m#Content-Type text/x-yaml is not supported# + 3 if ( + not $res->is_success and + $res->content =~ m#Content-Type text/x-yaml is not supported# ); - ok( $res->is_success, 'GET the serialized request succeeded' ); - is( $res->content, $data, "Request returned proper data"); - is( $res->header('Content-type'), 'text/x-yaml', '... with expected content-type') + ok( $res->is_success, 'GET the serialized request succeeded' ); + is( $res->content, $output_YAML, "Request returned proper data"); + is( $res->header('Content-type'), 'text/x-yaml', '... with expected content-type') }; } @@ -40,51 +41,51 @@ SKIP: { skip "can't test application/json without JSON support", 3 if $@; my $json = JSON->new; my $at = Test::Rest->new('content_type' => 'text/doesnt-exist'); - my $req = $at->get(url => '/serialize/test'); - $req->header('Accept', 'application/json'); - my $res = request($req); + my $req = $at->get(url => '/serialize/test'); + $req->header('Accept', 'application/json'); + my $res = request($req); ok( $res->is_success, 'GET the serialized request succeeded' ); my $ret = $json->decode($res->content); is( $ret->{lou}, 'is my cat', "Request returned proper data"); is( $res->header('Content-type'), 'application/json', 'Accept header used if content-type mapping not found') }; -# Make sure we don't get a bogus content-type when using default -# serializer (rt.cpan.org ticket 27949) +# Make sure we don't get a bogus content-type when using the default +# serializer (https://rt.cpan.org/Ticket/Display.html?id=27949) { - my $req = $t->get(url => '/serialize/test'); - $req->remove_header('Content-Type'); - $req->header('Accept', '*/*'); - my $res = request($req); - ok( $res->is_success, 'GET the serialized request succeeded' ); - is( $res->content, $data, "Request returned proper data"); - is( $res->header('Content-type'), 'text/x-yaml', '... with expected content-type') + my $req = $t->get(url => '/serialize/test'); + $req->remove_header('Content-Type'); + $req->header('Accept', '*/*'); + my $res = request($req); + ok( $res->is_success, 'GET the serialized request succeeded' ); + is( $res->content, $output_YAML, "Request returned proper data"); + is( $res->header('Content-type'), 'text/x-yaml', '... with expected content-type') } -# Make that using content_type_stash_key, an invalid value in the stash gets ignored +# Make sure that when using content_type_stash_key, an invalid value in the stash gets ignored { - my $req = $t->get(url => '/serialize/test_second?serialize_content_type=nonesuch'); - $req->remove_header('Content-Type'); - $req->header('Accept', '*/*'); - my $res = request($req); - ok( $res->is_success, 'GET the serialized request succeeded' ); - is( $res->content, $data, "Request returned proper data"); - is( $res->header('Content-type'), 'text/x-yaml', '... with expected content-type') + my $req = $t->get(url => '/serialize/test_second?serialize_content_type=nonesuch'); + $req->remove_header('Content-Type'); + $req->header('Accept', '*/*'); + my $res = request($req); + ok( $res->is_success, 'GET the serialized request succeeded' ); + is( $res->content, $output_YAML, "Request returned proper data"); + is( $res->header('Content-type'), 'text/x-yaml', '... with expected content-type') } -# Make that using content_type_stash_key, a valid value in the stash gets priority -# this also tests that application-level config is properly passed to +# Make sure that when using content_type_stash_key, a valid value in the stash gets priority. +# This also tests that application-level config is properly passed to # individual controllers; see t/lib/Test/Catalyst/Action/REST.pm { - my $req = $t->get(url => - '/serialize/test_second?serialize_content_type=text/x-data-dumper' - ); - $req->remove_header('Content-Type'); - $req->header('Accept', '*/*'); - my $res = request($req); - ok( $res->is_success, 'GET the serialized request succeeded' ); - is( $res->content, "{'lou' => 'is my cat'}", "Request returned proper data"); - is( $res->header('Content-type'), 'text/x-data-dumper', '... with expected content-type') + my $req = $t->get(url => + '/serialize/test_second?serialize_content_type=text/x-data-dumper' + ); + $req->remove_header('Content-Type'); + $req->header('Accept', '*/*'); + my $res = request($req); + ok( $res->is_success, 'GET the serialized request succeeded' ); + is( $res->content, "{'lou' => 'is my cat'}", "Request returned proper data"); + is( $res->header('Content-type'), 'text/x-data-dumper', '... with expected content-type') } 1; diff --git a/t/catalyst-controller-rest.t b/t/catalyst-controller-rest.t index 0d1d4eb..5d8f731 100644 --- a/t/catalyst-controller-rest.t +++ b/t/catalyst-controller-rest.t @@ -1,6 +1,6 @@ use strict; use warnings; -use Test::More tests => 2; +use Test::More tests => 18; use YAML::Syck; use FindBin; @@ -19,3 +19,32 @@ is_deeply( { test => 'worked', data => $data }, 'round trip (deserialize/serialize)', ); + + +ok my $res = request( $t->get( url => '/rest/test_status_created' ) ); +is $res->code, 201, "... status created"; + +ok $res = request( $t->get( url => '/rest/test_status_accepted' ) ); +is $res->code, 202, "... status accepted"; + +ok $res = request( $t->get( url => '/rest/test_status_no_content' ) ); +is $res->code, 204, "... status no content"; +is $res->content, '', '... no content'; + +ok $res = request( $t->get( url => '/rest/test_status_bad_request' ) ); +is $res->code, 400, '... status bad request'; +is_deeply Load( $res->content ), + { error => "Cannot do what you have asked!" }, + "... status bad request message"; + +ok $res = request( $t->get( url => '/rest/test_status_not_found' ) ); +is $res->code, 404, '... status not found'; +is_deeply Load( $res->content ), + { error => "Cannot find what you were looking for!" }, + "... status bad request message"; + +ok $res = request( $t->get( url => '/rest/test_status_gone' ) ); +is $res->code, 410, '... status gone'; +is_deeply Load( $res->content ), + { error => "Document have been deleted by foo" }, + "... status gone message"; diff --git a/t/catalyst-request-rest.t b/t/catalyst-request-rest.t index ac49e29..2226793 100644 --- a/t/catalyst-request-rest.t +++ b/t/catalyst-request-rest.t @@ -168,6 +168,8 @@ use HTTP::Headers; } { + local %ENV=%ENV; + $ENV{CATALYST_DEBUG} = 0; my $test = 'Test::Catalyst::Action::REST'; use_ok $test; is($test->request_class, 'Catalyst::Request::REST', diff --git a/t/lib/Test/Catalyst/Action/REST/Controller/REST.pm b/t/lib/Test/Catalyst/Action/REST/Controller/REST.pm index 9d75b4d..49d0cd1 100644 --- a/t/lib/Test/Catalyst/Action/REST/Controller/REST.pm +++ b/t/lib/Test/Catalyst/Action/REST/Controller/REST.pm @@ -6,8 +6,46 @@ use warnings; use base 'Catalyst::Controller::REST'; sub test : Local { - my ($self, $c) = @_; - $self->status_ok($c, entity => { test => 'worked', data => $c->req->data }); + my ( $self, $c ) = @_; + $self->status_ok( $c, + entity => { test => 'worked', data => $c->req->data } ); +} + +sub test_status_created : Local { + my ( $self, $c ) = @_; + $self->status_created( + $c, + location => '/rest', + entity => { status => 'created' } + ); +} + +sub test_status_accepted : Local { + my ( $self, $c ) = @_; + $self->status_accepted( $c, entity => { status => "queued", } ); +} + +sub test_status_no_content : Local { + my ( $self, $c ) = @_; + $self->status_no_content($c); +} + +sub test_status_bad_request : Local { + my ( $self, $c ) = @_; + $self->status_bad_request( $c, + message => "Cannot do what you have asked!", ); +} + +sub test_status_not_found : Local { + my ( $self, $c ) = @_; + $self->status_not_found( $c, + message => "Cannot find what you were looking for!", ); +} + +sub test_status_gone : Local { + my ( $self, $c ) = @_; + $self->status_gone( $c, + message => "Document have been deleted by foo", ); } 1; diff --git a/t/lib/Test/Serialize/Controller/REST.pm b/t/lib/Test/Serialize/Controller/REST.pm index 0499f1d..733508a 100644 --- a/t/lib/Test/Serialize/Controller/REST.pm +++ b/t/lib/Test/Serialize/Controller/REST.pm @@ -31,11 +31,11 @@ __PACKAGE__->config( sub monkey_put : Local : ActionClass('Deserialize') { my ( $self, $c ) = @_; - if ( ref($c->req->data) eq "HASH" ) { - $c->res->output( $c->req->data->{'sushi'} ); - } else { - $c->res->output(1); - } + if ( ref($c->req->data) eq "HASH" ) { + $c->res->output( $c->req->data->{'sushi'} ); + } else { + $c->res->output(1); + } } sub monkey_get : Local : ActionClass('Serialize') { @@ -44,4 +44,3 @@ sub monkey_get : Local : ActionClass('Serialize') { } 1; - diff --git a/t/lib/Test/Serialize/View/Simple.pm b/t/lib/Test/Serialize/View/Simple.pm index b0c1990..049152d 100644 --- a/t/lib/Test/Serialize/View/Simple.pm +++ b/t/lib/Test/Serialize/View/Simple.pm @@ -3,10 +3,10 @@ package Test::Serialize::View::Simple; use base qw/Catalyst::View/; sub process { - my ($self, $c) = @_; - + my ($self, $c) = @_; + $c->res->body("I am a simple view"); - return 1; + return 1; } 1;