The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
Changes 5895
MANIFEST 210
META.yml 12
Makefile.PL 07
README 916
inc/Module/Install/AuthorTests.pm 059
lib/Catalyst/Action/Deserialize/Data/Serializer.pm 03
lib/Catalyst/Action/Deserialize/JSON.pm 03
lib/Catalyst/Action/Deserialize/View.pm 03
lib/Catalyst/Action/Deserialize/XML/Simple.pm 03
lib/Catalyst/Action/Deserialize/YAML.pm 03
lib/Catalyst/Action/Deserialize.pm 47
lib/Catalyst/Action/REST.pm 1320
lib/Catalyst/Action/Serialize/Data/Serializer.pm 03
lib/Catalyst/Action/Serialize/JSON/XS.pm 47
lib/Catalyst/Action/Serialize/JSON.pm 317
lib/Catalyst/Action/Serialize/JSONP.pm 03
lib/Catalyst/Action/Serialize/View.pm 03
lib/Catalyst/Action/Serialize/XML/Simple.pm 03
lib/Catalyst/Action/Serialize/YAML/HTML.pm 03
lib/Catalyst/Action/Serialize/YAML.pm 03
lib/Catalyst/Action/Serialize.pm 517
lib/Catalyst/Action/SerializeBase.pm 1111
lib/Catalyst/Controller/REST.pm 98
lib/Catalyst/Request/REST/ForBrowsers.pm 057
lib/Catalyst/Request/REST.pm 1615
lib/Catalyst/TraitFor/Request/REST/ForBrowsers.pm 0200
lib/Catalyst/TraitFor/Request/REST.pm 723
t/catalyst-action-serialize-accept.t 212
t/catalyst-request-rest.t 2040
t/catalyst-traitfor-request-rest-forbrowsers.t 0200
t/catalyst-traitfor-request-rest.t 0215
t/lib/Test/Catalyst/Action/REST/Controller/Override.pm 022
t/lib/Test/Catalyst/Action/REST.pm 02
t/lib/Test/Catalyst/Log.pm 021
t/lib/Test/Serialize/Controller/REST.pm 03
t/lib/Test/Serialize.pm 02
t/pod.t 90
xt/pod-spell.t 064
xt/pod.t 07
xt/version-numbers.t 023
41 files changed (This is a version diff) 3571175
@@ -1,3 +1,40 @@
+Mon  8 Feb 2010 22:17:12 GMT - Release 0.83
+  Make it possible to deserialize a request with a DELETE method. This probably
+  breaks 'strict' REST guidelines, but is useful for being able to delete multiple
+  resources from a single call by providing a batch delete method.
+
+  Remove JSONP from the list of default serializers (RT#54336)
+
+  Fix MANIFEST (RT#54408)
+
+Thu  4 Feb 2010 22:31:57 GMT - Release 0.82
+
+  Integrated Catalyst::Request::REST::ForBrowsers as
+  Catalyst::TraitFor::Request::ForBrowsers. (Dave Rolsky)
+
+  Clarified docs so that they encourage the use of the request traits, rather
+  than using Catalyst::Request::REST. (Dave Rolsky)
+
+  When Catalyst::Action::REST or Controller::REST automatically add the trait,
+  your request class will no longer end up getting set to
+  Catalyst::Request::REST. Instead, creates an anon class with the appropriate
+  role. (Dave Rolsky)
+
+  Shut up log output from the tests. (Dave Rolsky)
+
+  Added a $VERSION to every module, mostly to make sure that when people
+  install Catalyst::Request::REST::ForBrowsers, they get the version in this
+  distro. (Dave Rolsky)
+
+  Change Catalyst::Action::Serialize, Catalyst::Action::Deserialize and
+  Catalyst::Action::SerializeBase to be more Moose like.
+
+  Fix JSON and JSON::XS to encode_blessed. (fREW)
+
+  Fix Catalyst::Action::Serialize to use objects instead of classes. (fREW)
+
+  Fix doc nits. (RT#53780)
+
 Thu 14 Jan 20:56:00 GMT 2010 - Release 0.81
 
   Add a JSONP serialization type.
@@ -120,93 +157,93 @@ Thu Jan  3 17:23:58 PST 2008 (adam) - Release 0.60
   Prepped for release.
 
 Thu Jan  3 19:42:16 EST 2008
-	Fixed RT#30498 - REST controller references Catalyst without
-	  loading it first.
-	Fixed RT#32042 - Import of Params::Validate :all plays badly
-	  with subclasses that have their own validate()
-	Fixed RT#30456 - Debug messages print even with debugging disabled
+   Fixed RT#30498 - REST controller references Catalyst without
+     loading it first.
+   Fixed RT#32042 - Import of Params::Validate :all plays badly
+     with subclasses that have their own validate()
+   Fixed RT#30456 - Debug messages print even with debugging disabled
 
 Thu Jan  3 08:54:09 PST 2008
-	Fixed an issue where YAML::Syck versions 0.92 require $c->request->body to
-	  be stringified
+   Fixed an issue where YAML::Syck versions 0.92 require $c->request->body to
+     be stringified
 
 Fri Dec 21 15:23:46 EDT 2007
-	Updated the configuration specifiers to operate more in line with the way
-	Catalyst expects.  Most notably component based configuration through
-	"Controller::RestClass" now works.  "serialize" at the top level simply
-	is suggested defaults that all REST classes inherit.
+   Updated the configuration specifiers to operate more in line with the way
+   Catalyst expects.  Most notably component based configuration through
+   "Controller::RestClass" now works.  "serialize" at the top level simply
+   is suggested defaults that all REST classes inherit.
 
 Wed Jul 04 11:17:20 EDT 2007
-	Fixed 'default' serializer to set a valid Content-Type: header.  Fixes
-	RT ticket 27949.  Note that behavior has changed -- the default
-	serializer must now be specified as a content-type, not as a plugin
-	name. (dmo@roaringpenguin.com)
+   Fixed 'default' serializer to set a valid Content-Type: header.  Fixes
+   RT ticket 27949.  Note that behavior has changed -- the default
+   serializer must now be specified as a content-type, not as a plugin
+   name. (dmo@roaringpenguin.com)
 
 Thu May 24 14:01:06 PDT 2007 (adam) - Release 0.41
-	Moved a bogus $self->class to $c->component($self->class)
+   Moved a bogus $self->class to $c->component($self->class)
 
 Fri Mar  9 14:13:29 PST 2007 (adam) - Release 0.40
-	Refactored the Content-Type negotiation to live in Catalyst::Request::REST.  (drolsky)
-  	Added some useful debugging. (drolsky)
-	Added a View serializer/deserializer, which simply calls the correct
-	Catalyst view.  ('text/html' => [ 'View', 'TT' ]) (claco, adam)
+   Refactored the Content-Type negotiation to live in Catalyst::Request::REST.  (drolsky)
+   Added some useful debugging. (drolsky)
+   Added a View serializer/deserializer, which simply calls the correct
+   Catalyst view.  ('text/html' => [ 'View', 'TT' ]) (claco, adam)
 
 Wed Dec  6 00:45:02 PST 2006 (adam) - Release 0.31
-	Fixed a bug where we would report a blank content-type negotiation.
-	Added Data::Dump as a dependency.
+   Fixed a bug where we would report a blank content-type negotiation.
+   Added Data::Dump as a dependency.
 
 Tue Dec  5 13:02:08 PST 2006 (adam)
-	Made the YAML::HTML view automatically append content-type=text/html on
-	  the resulting URLs.
+   Made the YAML::HTML view automatically append content-type=text/html on
+     the resulting URLs.
 
 Sun Dec  3 12:24:16 PST 2006 (adam) - Release 0.30
-	Updated the Makefile to support optional installation of the different
-	  Serialization formats.
-	Renamed some of the test cases, since the execution order doesn't
-	  matter.
-	Fixed things so that not having a Serialization module returns 415.
-	Fixed things so that failure to Deserialize sends the proper status.
-	Refactored the Plugin loading to Catalyst::Action::SerializeBase.
-	Updated the Documentation.
-	Added a whole raft of serializers. (JSON, all the Data::Serializer
-	  supported ones, and XML::Simple)
-	Added test cases.
+   Updated the Makefile to support optional installation of the different
+     Serialization formats.
+   Renamed some of the test cases, since the execution order doesn't
+     matter.
+   Fixed things so that not having a Serialization module returns 415.
+   Fixed things so that failure to Deserialize sends the proper status.
+   Refactored the Plugin loading to Catalyst::Action::SerializeBase.
+   Updated the Documentation.
+   Added a whole raft of serializers. (JSON, all the Data::Serializer
+     supported ones, and XML::Simple)
+   Added test cases.
 
 Thu Nov 30 23:51:04 PST 2006 (adam)
-	Refactored the Catalyst::Action::REST dispatch, so that the default
-	  method is called before any _METHOD handlers.  In addition, moved
-	  the 405 Not Implemented handler to be foo_not_implemented, instead
-	  of the default sub.  (daisuke++ pointed out the inconsistency and
-	  provided a patch, and I added the foo_not_implemented support)
+   Refactored the Catalyst::Action::REST dispatch, so that the default
+     method is called before any _METHOD handlers.  In addition, moved
+     the 405 Not Implemented handler to be foo_not_implemented, instead
+     of the default sub.  (daisuke++ pointed out the inconsistency and
+     provided a patch, and I added the foo_not_implemented support)
 
-	Added in automated OPTIONS handler, which constructs the allow
-	  header for you, just like the 405 handler.  Can be overridden
-	  with a normal _METHOD sub.
+   Added in automated OPTIONS handler, which constructs the allow
+     header for you, just like the 405 handler.  Can be overridden
+     with a normal _METHOD sub.
 
-	Refactored Test::Rest, so that it uses closures to create the
-	  very similar $test->method() subs.
+   Refactored Test::Rest, so that it uses closures to create the
+     very similar $test->method() subs.
 
-	Added tests for Catalyst::Action::REST.
+   Added tests for Catalyst::Action::REST.
 
 Thu Nov 30 17:14:51 PST 2006 (adam) - Release 0.2
-	Added documentation patch from Daisuke Maki (daisuke@endeworks.jp)
-	Added dependency patch from Daisuke Maki (daisuke@endeworks.jp)
+   Added documentation patch from Daisuke Maki (daisuke@endeworks.jp)
+   Added dependency patch from Daisuke Maki (daisuke@endeworks.jp)
 
 Sun Nov 19 16:24:20 PST 2006 (adam) - Release 0.1
-	Added status_accepted (Code 202)
-	Added a first pass at documentation.
+   Added status_accepted (Code 202)
+   Added a first pass at documentation.
 
 Mon Oct 16 14:48:54 PDT 2006 (adam)
-	Added in Test Suite
-	Created Catalyst::Action::Serialize and Catalyst::Action::Deserialize
-	Added Data::Serializer actions
-	Added status_created helper method
+   Added in Test Suite
+   Created Catalyst::Action::Serialize and Catalyst::Action::Deserialize
+   Added Data::Serializer actions
+   Added status_created helper method
 
 Wed Oct 18 17:29:07 PDT 2006 (adam)
-	Added more status_ helpers
+   Added more status_ helpers
 
 Thu Oct 19 16:04:33 PDT 2006 (adam)
-	Converted error helpers to return an object instead of plain-text.  It's
-	  a more consistent model than a text/plain error message.
-	Added logging to 4xx status handlers
+   Converted error helpers to return an object instead of plain-text.  It's
+     a more consistent model than a text/plain error message.
+   Added logging to 4xx status handlers
 
@@ -2,6 +2,7 @@ Changes
 inc/Module/AutoInstall.pm
 inc/Module/Install.pm
 inc/Module/Install/AuthorRequires.pm
+inc/Module/Install/AuthorTests.pm
 inc/Module/Install/AutoInstall.pm
 inc/Module/Install/Base.pm
 inc/Module/Install/Can.pm
@@ -31,7 +32,9 @@ lib/Catalyst/Action/Serialize/YAML/HTML.pm
 lib/Catalyst/Action/SerializeBase.pm
 lib/Catalyst/Controller/REST.pm
 lib/Catalyst/Request/REST.pm
+lib/Catalyst/Request/REST/ForBrowsers.pm
 lib/Catalyst/TraitFor/Request/REST.pm
+lib/Catalyst/TraitFor/Request/REST/ForBrowsers.pm
 Makefile.PL
 MANIFEST			This list of files
 META.yml
@@ -45,7 +48,8 @@ t/catalyst-action-serialize-accept.t
 t/catalyst-action-serialize-query.t
 t/catalyst-action-serialize.t
 t/catalyst-controller-rest.t
-t/catalyst-request-rest.t
+t/catalyst-traitfor-request-rest-forbrowsers.t
+t/catalyst-traitfor-request-rest.t
 t/data-serializer.t
 t/isa.t
 t/json.t
@@ -54,17 +58,21 @@ t/lib/Test/Action/Class.pm
 t/lib/Test/Catalyst/Action/REST.pm
 t/lib/Test/Catalyst/Action/REST/Controller/Actions.pm
 t/lib/Test/Catalyst/Action/REST/Controller/Deserialize.pm
+t/lib/Test/Catalyst/Action/REST/Controller/Override.pm
 t/lib/Test/Catalyst/Action/REST/Controller/REST.pm
 t/lib/Test/Catalyst/Action/REST/Controller/Root.pm
 t/lib/Test/Catalyst/Action/REST/Controller/Serialize.pm
+t/lib/Test/Catalyst/Log.pm
 t/lib/Test/Rest.pm
 t/lib/Test/Serialize.pm
 t/lib/Test/Serialize/Controller/REST.pm
 t/lib/Test/Serialize/View/Awful.pm
 t/lib/Test/Serialize/View/Simple.pm
-t/pod.t
 t/view.t
 t/xml-simple.t
 t/yaml-html.t
 t/yaml.t
 TODO
+xt/pod-spell.t
+xt/pod.t
+xt/version-numbers.t
@@ -18,6 +18,7 @@ no_index:
   directory:
     - inc
     - t
+    - xt
 requires:
   Catalyst::Runtime: 5.80
   Class::Inspector: 1.13
@@ -34,4 +35,4 @@ requires:
 resources:
   license: http://dev.perl.org/licenses/
   repository: git://git.shadowcat.co.uk/catagits/Catalyst-Action-REST.git
-version: 0.81
+version: 0.83
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 use inc::Module::Install;
 use Module::Install::AuthorRequires;
+use Module::Install::AuthorTests;
 
 perl_version '5.8.1';
 
@@ -23,6 +24,12 @@ requires('MRO::Compat'               => '0.10');
 requires 'namespace::autoclean';
 test_requires 'Test::More' => '0.88';
 
+author_requires 'Test::Pod' => 1.14;
+author_requires 'Module::Info';
+author_requires 'File::Find::Rule';
+
+author_tests 'xt/';
+
 feature 'JSON (application/json) support',
     -default   => 0,
     'JSON'     => '2.12';
@@ -42,8 +42,8 @@ DESCRIPTION
     which brings this class together with automatic Serialization of
     requests and responses.
 
-    When you use this module, the request class will be changed to
-    Catalyst::Request::REST.
+    When you use this module, it adds the Catalyst::TraitFor::Request::REST
+    role to your request class.
 
 METHODS
     dispatch
@@ -54,6 +54,11 @@ SEE ALSO
     You likely want to look at Catalyst::Controller::REST, which implements
     a sensible set of defaults for a controller doing REST.
 
+    This class automatically adds the Catalyst::TraitFor::Request::REST role
+    to your request class. If you're writing a webapp which provides RESTful
+    responses and still needs to accommodate web browsers, you may prefer to
+    use Catalyst::TraitFor::Request::REST::ForBrowsers instead.
+
     Catalyst::Action::Serialize, Catalyst::Action::Deserialize
 
 TROUBLESHOOTING
@@ -76,21 +81,23 @@ AUTHOR
     (<http://www.marchex.com>)
 
 CONTRIBUTORS
-    Arthur Axel "fREW" Schmidt <frioux@gmail.com>
+    Tomas Doran (t0m) <bobtfish@bobtfish.net>
+
+    John Goulah
 
     Christopher Laco
 
-    Luke Saunders
+    Daisuke Maki <daisuke@endeworks.jp>
 
-    John Goulah
+    Hans Dieter Pearcey
 
-    Daisuke Maki <daisuke@endeworks.jp>
+    Dave Rolsky <autarch@urth.org>
 
-    J. Shirley <jshirley@gmail.com>
+    Luke Saunders
 
-    Hans Dieter Pearcey
+    Arthur Axel "fREW" Schmidt <frioux@gmail.com>
 
-    Tomas Doran (t0m) <bobtfish@bobtfish.net>
+    J. Shirley <jshirley@gmail.com>
 
 COPYRIGHT
     Copyright the above named AUTHOR and CONTRIBUTORS
@@ -0,0 +1,59 @@
+#line 1
+package Module::Install::AuthorTests;
+
+use 5.005;
+use strict;
+use Module::Install::Base;
+use Carp ();
+
+#line 16
+
+use vars qw{$VERSION $ISCORE @ISA};
+BEGIN {
+  $VERSION = '0.002';
+  $ISCORE  = 1;
+  @ISA     = qw{Module::Install::Base};
+}
+
+#line 42
+
+sub author_tests {
+  my ($self, @dirs) = @_;
+  _add_author_tests($self, \@dirs, 0);
+}
+
+#line 56
+
+sub recursive_author_tests {
+  my ($self, @dirs) = @_;
+  _add_author_tests($self, \@dirs, 1);
+}
+
+sub _wanted {
+  my $href = shift;
+  sub { /\.t$/ and -f $_ and $href->{$File::Find::dir} = 1 }
+}
+
+sub _add_author_tests {
+  my ($self, $dirs, $recurse) = @_;
+  return unless $Module::Install::AUTHOR;
+
+  my @tests = $self->tests ? (split / /, $self->tests) : 't/*.t';
+
+  # XXX: pick a default, later -- rjbs, 2008-02-24
+  my @dirs = @$dirs ? @$dirs : Carp::confess "no dirs given to author_tests";
+     @dirs = grep { -d } @dirs;
+
+  if ($recurse) {
+    require File::Find;
+    my %test_dir;
+    File::Find::find(_wanted(\%test_dir), @dirs);
+    $self->tests( join ' ', @tests, map { "$_/*.t" } sort keys %test_dir );
+  } else {
+    $self->tests( join ' ', @tests, map { "$_/*.t" } sort @dirs );
+  }
+}
+
+#line 107
+
+1;
@@ -6,6 +6,9 @@ use namespace::autoclean;
 extends 'Catalyst::Action';
 use Data::Serializer;
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c, $serializer ) = @_;
@@ -6,6 +6,9 @@ use namespace::autoclean;
 extends 'Catalyst::Action';
 use JSON qw( decode_json );
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c, $test ) = @_;
@@ -5,6 +5,9 @@ use namespace::autoclean;
 
 extends 'Catalyst::Action';
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     return 1;
 }
@@ -5,6 +5,9 @@ use namespace::autoclean;
 
 extends 'Catalyst::Action';
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c, $test ) = @_;
@@ -6,6 +6,9 @@ use namespace::autoclean;
 extends 'Catalyst::Action';
 use YAML::Syck;
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c, $test ) = @_;
@@ -7,13 +7,16 @@ extends 'Catalyst::Action::SerializeBase';
 use Module::Pluggable::Object;
 use MRO::Compat;
 
-__PACKAGE__->mk_accessors(qw(plugins));
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
+has plugins => ( is => 'rw' );
 
 sub execute {
     my $self = shift;
     my ( $controller, $c ) = @_;
 
-    my @demethods = qw(POST PUT OPTIONS);
+    my @demethods = qw(POST PUT OPTIONS DELETE);
     my $method    = $c->request->method;
     if ( grep /^$method$/, @demethods ) {
         my ( $sclass, $sarg, $content_type ) =
@@ -38,6 +41,8 @@ sub execute {
     return 1;
 }
 
+__PACKAGE__->meta->make_immutable;
+
 =head1 NAME
 
 Catalyst::Action::Deserialize - Deserialize Data in a Request
@@ -98,5 +103,3 @@ See L<Catalyst::Action::REST> for authors.
 You may distribute this code under the same terms as Perl itself.
 
 =cut
-
-1;
@@ -10,7 +10,7 @@ use Catalyst::Controller::REST;
 
 BEGIN { require 5.008001; }
 
-our $VERSION = '0.81';
+our $VERSION = '0.83';
 $VERSION = eval $VERSION;
 
 sub new {
@@ -67,8 +67,8 @@ It is likely that you really want to look at L<Catalyst::Controller::REST>,
 which brings this class together with automatic Serialization of requests
 and responses.
 
-When you use this module, the request class will be changed to
-L<Catalyst::Request::REST>.
+When you use this module, it adds the L<Catalyst::TraitFor::Request::REST>
+role to your request class.
 
 =head1 METHODS
 
@@ -156,8 +156,13 @@ sub _return_not_implemented {
 
 =head1 SEE ALSO
 
-You likely want to look at L<Catalyst::Controller::REST>, which implements
-a sensible set of defaults for a controller doing REST.
+You likely want to look at L<Catalyst::Controller::REST>, which implements a
+sensible set of defaults for a controller doing REST.
+
+This class automatically adds the L<Catalyst::TraitFor::Request::REST> role to
+your request class.  If you're writing a webapp which provides RESTful
+responses and still needs to accommodate web browsers, you may prefer to use
+L<Catalyst::TraitFor::Request::REST::ForBrowsers> instead.
 
 L<Catalyst::Action::Serialize>, L<Catalyst::Action::Deserialize>
 
@@ -180,27 +185,29 @@ for this to run smoothly.
 
 =head1 AUTHOR
 
-Adam Jacob <adam@stalecoffee.org>, with lots of help from mst and jrockway
+Adam Jacob E<lt>adam@stalecoffee.orgE<gt>, with lots of help from mst and jrockway
 
 Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
 
 =head1 CONTRIBUTORS
 
-Arthur Axel "fREW" Schmidt <frioux@gmail.com>
+Tomas Doran (t0m) E<lt>bobtfish@bobtfish.netE<gt>
+
+John Goulah
 
 Christopher Laco
 
-Luke Saunders
+Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
 
-John Goulah
+Hans Dieter Pearcey
 
-Daisuke Maki <daisuke@endeworks.jp>
+Dave Rolsky E<lt>autarch@urth.orgE<gt>
 
-J. Shirley <jshirley@gmail.com>
+Luke Saunders
 
-Hans Dieter Pearcey
+Arthur Axel "fREW" Schmidt E<lt>frioux@gmail.comE<gt>
 
-Tomas Doran (t0m) <bobtfish@bobtfish.net>
+J. Shirley E<lt>jshirley@gmail.comE<gt>
 
 =head1 COPYRIGHT
 
@@ -6,6 +6,9 @@ use namespace::autoclean;
 extends 'Catalyst::Action';
 use Data::Serializer;
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c, $serializer ) = @_;
@@ -4,11 +4,14 @@ use Moose;
 use namespace::autoclean;
 
 extends 'Catalyst::Action::Serialize::JSON';
-use JSON::XS qw(encode_json);
+use JSON::XS ();
 
-sub serialize {
-    my $self = shift;
-    encode_json( shift );
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
+sub _build_encoder {
+   my $self = shift;
+   return JSON::XS->new->convert_blessed;
 }
 
 1;
@@ -4,7 +4,20 @@ use Moose;
 use namespace::autoclean;
 
 extends 'Catalyst::Action';
-use JSON qw(encode_json);
+use JSON ();
+
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
+has encoder => (
+   is => 'ro',
+   lazy_build => 1,
+);
+
+sub _build_encoder {
+   my $self = shift;
+   return JSON->new->utf8->convert_blessed;
+}
 
 sub execute {
     my $self = shift;
@@ -13,7 +26,7 @@ sub execute {
     my $stash_key = (
             $controller->{'serialize'} ?
                 $controller->{'serialize'}->{'stash_key'} :
-                $controller->{'stash_key'} 
+                $controller->{'stash_key'}
         ) || 'rest';
     my $output = $self->serialize( $c->stash->{$stash_key} );
     $c->response->output( $output );
@@ -22,7 +35,8 @@ sub execute {
 
 sub serialize {
     my $self = shift;
-    encode_json( shift );
+    my $data = shift;
+    $self->encoder->encode( $data );
 }
 
 1;
@@ -4,6 +4,9 @@ use namespace::autoclean;
 
 extends 'Catalyst::Action::Serialize::JSON';
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 after 'execute' => sub {
   my $self = shift;
   my ($controller, $c) = @_;
@@ -4,6 +4,9 @@ use namespace::autoclean;
 
 extends 'Catalyst::Action';
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c, $view ) = @_;
@@ -5,6 +5,9 @@ use namespace::autoclean;
 
 extends 'Catalyst::Action';
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c ) = @_;
@@ -7,6 +7,9 @@ extends 'Catalyst::Action';
 use YAML::Syck;
 use URI::Find;
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c ) = @_;
@@ -6,6 +6,9 @@ use namespace::autoclean;
 extends 'Catalyst::Action';
 use YAML::Syck;
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 sub execute {
     my $self = shift;
     my ( $controller, $c ) = @_;
@@ -7,6 +7,15 @@ extends 'Catalyst::Action::SerializeBase';
 use Module::Pluggable::Object;
 use MRO::Compat;
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
+has _encoders => (
+   is => 'ro',
+   isa => 'HashRef',
+   default => sub { {} },
+);
+
 sub execute {
     my $self = shift;
     my ( $controller, $c ) = @_;
@@ -33,12 +42,15 @@ sub execute {
     $c->log->debug(
         "Serializing with $sclass" . ( $sarg ? " [$sarg]" : '' ) ) if $c->debug;
 
+    $self->_encoders->{$sclass} ||= $sclass->new;
+    my $sobj = $self->_encoders->{$sclass};
+
     my $rc;
     eval {
         if ( defined($sarg) ) {
-            $rc = $sclass->execute( $controller, $c, $sarg );
+            $rc = $sobj->execute( $controller, $c, $sarg );
         } else {
-            $rc = $sclass->execute( $controller, $c );
+            $rc = $sobj->execute( $controller, $c );
         }
     };
     if ($@) {
@@ -50,7 +62,7 @@ sub execute {
     return 1;
 }
 
-1;
+__PACKAGE__->meta->make_immutable;
 
 =head1 NAME
 
@@ -104,7 +116,7 @@ Takes a hashref, mapping Content-Types to a given serializer plugin.
 This is the 'fall-back' Content-Type if none of the requested or acceptable
 types is found in the L</map>. It must be an entry in the L</map>.
 
-=head2 stash_key 
+=head2 stash_key
 
 Specifies the key of the stash entry holding the data that is to be serialized.
 So if the value is "rest", we will serialize the data under:
@@ -124,7 +136,7 @@ perhaps for debugging.
 
 Daisuke Maki pointed out that early versions of this Action did not play
 well with others, or generally behave in a way that was very consistent
-with the rest of Catalyst. 
+with the rest of Catalyst.
 
 =head1 SEE ALSO
 
@@ -8,14 +8,16 @@ use Module::Pluggable::Object;
 use Catalyst::Request::REST;
 use Catalyst::Utils ();
 
-sub new {
-  my $class  = shift;
-  my $config = shift;
-  Catalyst::Request::REST->_insert_self_into( $config->{class} );
-  return $class->SUPER::new($config, @_);
-}
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
+after BUILDARGS => sub {
+    my $class  = shift;
+    my $config = shift;
+    Catalyst::Request::REST->_insert_self_into( $config->{class} );
+};
 
-__PACKAGE__->mk_accessors(qw(_serialize_plugins _loaded_plugins));
+has [qw(_serialize_plugins _loaded_plugins)] => ( is => 'rw' );
 
 sub _load_content_plugins {
     my $self = shift;
@@ -153,13 +155,11 @@ sub _serialize_bad_request {
     return undef;
 }
 
-1;
+__PACKAGE__->meta->make_immutable;
 
 =head1 NAME
 
-B<Catalyst::Action::SerializeBase>
-
-Base class for Catalyst::Action::Serialize and Catlayst::Action::Deserialize.
+Catalyst::Action::SerializeBase - Base class for Catalyst::Action::Serialize and Catlayst::Action::Deserialize.
 
 =head1 DESCRIPTION
 
@@ -2,7 +2,7 @@ package Catalyst::Controller::REST;
 use Moose;
 use namespace::autoclean;
 
-our $VERSION = '0.81';
+our $VERSION = '0.83';
 $VERSION = eval $VERSION;
 
 =head1 NAME
@@ -138,7 +138,7 @@ Returns YAML generated by L<YAML::Syck>.
 =item * C<text/html> => C<YAML::HTML>
 
 This uses L<YAML::Syck> and L<URI::Find> to generate YAML with all URLs turned
-to hyperlinks.  Only useable for Serialization.
+to hyperlinks.  Only usable for Serialization.
 
 =item * C<application/json> => C<JSON>
 
@@ -150,6 +150,11 @@ deprecated and you will receive warnings in your log.
 
 If a callback=? parameter is passed, this returns javascript in the form of: $callback($serializedJSON);
 
+Note - this is disabled by default as it can be a security risk if you are unaware.
+
+The usual MIME types for this serialization format are: 'text/javascript', 'application/x-javascript',
+'application/javascript'.
+
 =item * C<text/x-data-dumper> => C<Data::Serializer>
 
 Uses the L<Data::Serializer> module to generate L<Data::Dumper> output.
@@ -272,9 +277,6 @@ __PACKAGE__->config(
         'text/x-yaml'        => 'YAML',
         'application/json'   => 'JSON',
         'text/x-json'        => 'JSON',
-        'application/x-javascript'  => 'JSONP',
-        'application/javascript'    => 'JSONP',
-        'text/javascript'    => 'JSONP',
         'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
         'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
         'text/x-data-taxi'   => [ 'Data::Serializer', 'Data::Taxi'   ],
@@ -509,9 +511,6 @@ This class provides a default configuration for Serialization.  It is currently:
          'text/x-yaml'        => 'YAML',
          'application/json'   => 'JSON',
          'text/x-json'        => 'JSON',
-         'application/x-javascript' => 'JSONP',
-         'application/javascript'   => 'JSONP',
-         'text/javascript'    => 'JSONP',
          'text/x-data-dumper' => [ 'Data::Serializer', 'Data::Dumper' ],
          'text/x-data-denter' => [ 'Data::Serializer', 'Data::Denter' ],
          'text/x-data-taxi'   => [ 'Data::Serializer', 'Data::Taxi'   ],
@@ -558,7 +557,7 @@ and use MRO::Compat:
 
 I have code in production using L<Catalyst::Controller::REST>.  That said,
 it is still under development, and it's possible that things may change
-between releases.  I promise to not break things unneccesarily. :)
+between releases.  I promise to not break things unnecessarily. :)
 
 =head1 SEE ALSO
 
@@ -0,0 +1,57 @@
+package Catalyst::Request::REST::ForBrowsers;
+use Moose;
+
+use namespace::autoclean;
+
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
+extends 'Catalyst::Request::REST';
+with 'Catalyst::TraitFor::Request::REST::ForBrowsers';
+
+__PACKAGE__->meta->make_immutable;
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Catalyst::Request::REST::ForBrowsers - A Catalyst::Request::REST subclass for dealing with browsers
+
+=head1 SYNOPSIS
+
+    package MyApp;
+
+    use Catalyst::Request::REST::ForBrowsers;
+
+    MyApp->request_class( 'Catalyst::Request::REST::ForBrowsers' );
+
+=head1 DESCRIPTION
+
+This class has been deprecated in favor of
+L<Catalyst::TraitFor::Request::REST::ForBrowsers>. Please see that class for
+details on methods and attributes.
+
+=head1 AUTHOR
+
+Dave Rolsky, C<< <autarch@urth.org> >>
+
+=head1 BUGS
+
+Please report any bugs or feature requests to
+C<bug-catalyst-request-rest-forbrowsers@rt.cpan.org>, or through the
+web interface at L<http://rt.cpan.org>.  I will be notified, and then
+you'll automatically be notified of progress on your bug as I make
+changes.
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2008-2009 Dave Rolsky, All Rights Reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
@@ -7,6 +7,9 @@ use namespace::autoclean;
 extends 'Catalyst::Request';
 with 'Catalyst::TraitFor::Request::REST';
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 # Please don't take this as a recommended way to do things.
 # The code below is grotty, badly factored and mostly here for back
 # compat..
@@ -21,18 +24,13 @@ sub _insert_self_into {
   return if $req_class->isa($class);
   my $req_class_meta = Moose->init_meta( for_class => $req_class );
   return if $req_class_meta->does_role('Catalyst::TraitFor::Request::REST');
-  if ($req_class eq 'Catalyst::Request') {
-    $app->request_class($class);
-  }
-  else {
-      my $meta = Moose::Meta::Class->create_anon_class(
-          superclasses => [$req_class],
-          roles => ['Catalyst::TraitFor::Request::REST'],
-          cache => 1
-      );
-      $meta->add_method(meta => sub { $meta });
-      $app->request_class($meta->name);
-  }
+  my $meta = Moose::Meta::Class->create_anon_class(
+      superclasses => [$req_class],
+      roles => ['Catalyst::TraitFor::Request::REST'],
+      cache => 1
+  );
+  $meta->add_method(meta => sub { $meta });
+  $app->request_class($meta->name);
 }
 
 __PACKAGE__->meta->make_immutable;
@@ -53,11 +51,12 @@ Catalyst::Request::REST - A REST-y subclass of Catalyst::Request
 =head1 DESCRIPTION
 
 This is a subclass of C<Catalyst::Request> that applies the
-L<Catalyst::TraitFor::Request::REST> which adds a few methods to
-the request object to faciliate writing REST-y code.
+L<Catalyst::TraitFor::Request::REST> role to your request class. That trait
+adds a few methods to the request object to facilitate writing REST-y code.
 
-This class is only here for backwards compatibility with applications
-already subclassing this class.
+This class is only here for backwards compatibility with applications already
+subclassing this class. New code should use
+L<Catalyst::TraitFor::Request::REST> directly.
 
 L<Catalyst::Action::REST> and L<Catalyst::Controller::REST> will arrange
 for the request trait to be applied if needed.
@@ -0,0 +1,200 @@
+package Catalyst::TraitFor::Request::REST::ForBrowsers;
+use Moose::Role;
+use namespace::autoclean;
+
+with 'Catalyst::TraitFor::Request::REST';
+
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
+has _determined_real_method => (
+    is  => 'rw',
+    isa => 'Bool',
+);
+
+has looks_like_browser => (
+    is       => 'rw',
+    isa      => 'Bool',
+    lazy     => 1,
+    builder  => '_build_looks_like_browser',
+    init_arg => undef,
+);
+
+# All this would be much less gross if Catalyst::Request used a builder to
+# determine the method. Then we could just wrap the builder.
+around method => sub {
+    my $orig = shift;
+    my $self = shift;
+
+    return $self->$orig(@_)
+        if @_ || $self->_determined_real_method;
+
+    my $method = $self->$orig();
+
+    my $tunneled;
+    if ( defined $method && uc $method eq 'POST' ) {
+        $tunneled = $self->param('x-tunneled-method')
+            || $self->header('x-http-method-override');
+    }
+
+    $self->$orig( defined $tunneled ? uc $tunneled : $method );
+
+    $self->_determined_real_method(1);
+
+    return $self->$orig();
+};
+
+{
+    my %HTMLTypes = map { $_ => 1 } qw(
+        text/html
+        application/xhtml+xml
+    );
+
+    sub _build_looks_like_browser {
+        my $self = shift;
+
+        my $with = $self->header('x-requested-with');
+        return 0
+            if $with && grep { $with eq $_ }
+                qw( HTTP.Request XMLHttpRequest );
+
+        if ( uc $self->method eq 'GET' ) {
+            my $forced_type = $self->param('content-type');
+            return 0
+                if $forced_type && !$HTMLTypes{$forced_type};
+        }
+
+        # IE7 does not say it accepts any form of html, but _does_
+        # accept */* (helpful ;)
+        return 1
+            if $self->accepts('*/*');
+
+        return 1
+            if grep { $self->accepts($_) } keys %HTMLTypes;
+
+        return 0
+            if @{ $self->accepted_content_types() };
+
+        # If the client did not specify any content types at all,
+        # assume they are a browser.
+        return 1;
+    }
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Catalyst::TraitFor::Request::REST::ForBrowsers - A request trait for REST and browsers
+
+=head1 SYNOPSIS
+
+    package MyApp;
+
+    use Catalyst::TraitFor::Request::REST::ForBrowsers;
+
+
+
+=head1 DESCRIPTION
+
+Writing REST-y apps is a good thing, but if you're also trying to support web
+browsers, you're probably going to need some hackish workarounds. This module
+provides those workarounds for you.
+
+Specifically, it lets you do two things. First, it lets you "tunnel" PUT and
+DELETE requests across a POST, since most browsers do not support PUT or
+DELETE actions (as of early 2009, at least).
+
+Second, it provides a heuristic to check if the client is a web browser,
+regardless of what content types it claims to accept. The reason for this is
+that while a browser might claim to accept the "application/xml" content type,
+it's really not going to do anything useful with it, and you're best off
+giving it HTML.
+
+=head1 METHODS
+
+This class provides the following methods:
+
+=head2 $request->method
+
+This method works just like C<< Catalyst::Request->method() >> except it
+allows for tunneling of PUT and DELETE requests via a POST.
+
+Specifically, you can provide a form element named "x-tunneled-method" which
+can override the request method for a POST. This I<only> works for a POST, not
+a GET.
+
+You can also use a header named "x-http-method-override" instead (Google uses
+this header for its APIs).
+
+=head2 $request->looks_like_browser
+
+This attribute provides a heuristic to determine whether or not the request
+I<appears> to come from a browser. You can use this however you want. I
+usually use it to determine whether or not to give the client a full HTML page
+or some sort of serialized data.
+
+This is a heuristic, and like any heuristic, it is probably wrong
+sometimes. Here is how it works:
+
+=over 4
+
+=item *
+
+If the request includes a header "X-Request-With" set to either "HTTP.Request"
+or "XMLHttpRequest", this returns false. The assumption is that if you're
+doing XHR, you don't want the request treated as if it comes from a browser.
+
+=item *
+
+If the client makes a GET request with a query string parameter
+"content-type", and that type is I<not> an HTML type, it is I<not> a browser.
+
+=item *
+
+If the client provides an Accept header which includes "*/*" as an accepted
+content type, the client is a browser. Specifically, it is IE7, which submits
+an Accept header of "*/*". IE7's Accept header does not include any html types
+like "text/html".
+
+=item *
+
+If the client provides an Accept header and accepts either "text/html" or
+"application/xhtml+xml" it is a browser.
+
+=item *
+
+If it provides an Accept header of any sort, it is I<not> a browser.
+
+=item *
+
+The default is that the client is a browser.
+
+=back
+
+This all works well for my apps, but read it carefully to make sure it meets
+your expectations before using it.
+
+=head1 AUTHOR
+
+Dave Rolsky, C<< <autarch@urth.org> >>
+
+=head1 BUGS
+
+Please report any bugs or feature requests to
+C<bug-catalyst-action-rest@rt.cpan.org>, or through the web interface at
+L<http://rt.cpan.org>. We will be notified, and then you'll automatically be
+notified of progress on your bug as I make changes.
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright 2008-2010 Dave Rolsky, All Rights Reserved.
+
+This program is free software; you can redistribute it and/or modify it under
+the same terms as Perl itself.
+
+=cut
@@ -3,13 +3,30 @@ use Moose::Role;
 use HTTP::Headers::Util qw(split_header_words);
 use namespace::autoclean;
 
+our $VERSION = '0.83';
+$VERSION = eval $VERSION;
+
 has [qw/ data accept_only /] => ( is => 'rw' );
 
-sub accepted_content_types {
+has accepted_content_types => (
+    is       => 'ro',
+    isa      => 'ArrayRef',
+    lazy     => 1,
+    builder  => '_build_accepted_content_types',
+    init_arg => undef,
+);
+
+has preferred_content_type => (
+    is       => 'ro',
+    isa      => 'Str',
+    lazy     => 1,
+    builder  => '_build_preferred_content_type',
+    init_arg => undef,
+);
+
+sub _build_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.
@@ -49,11 +66,10 @@ sub accepted_content_types {
         }
     }
 
-    return $self->{content_types} =
-        [ sort { $types{$b} <=> $types{$a} } keys %types ];
+    [ sort { $types{$b} <=> $types{$a} } keys %types ];
 }
 
-sub preferred_content_type { $_[0]->accepted_content_types->[0] }
+sub _build_preferred_content_type { $_[0]->accepted_content_types->[0] }
 
 sub accepts {
     my $self = shift;
@@ -80,7 +96,7 @@ Catalyst::TraitFor::Request::REST - A role to apply to Catalyst::Request giving
 =head1 DESCRIPTION
 
 This is a L<Moose::Role> applied to L<Catalyst::Request> that adds a few
-methods to the request object to faciliate writing REST-y code.
+methods to the request object to facilitate writing REST-y code.
 Currently, these methods are all related to the content types accepted by
 the client.
 
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use Test::More tests => 16;
+use Test::More;
 use Data::Serializer;
 use FindBin;
 
@@ -88,4 +88,14 @@ SKIP: {
     is( $res->header('Content-type'), 'text/x-data-dumper', '... with expected content-type')
 }
 
-1;
+# Make sure that the default content type you specify really gets used.
+{
+    my $req = $t->get(url => '/override/test');
+    $req->remove_header('Content-Type');
+    my $res = request($req);
+    ok( $res->is_success, 'GET the serialized request succeeded' );
+    is( $res->content, "--- \nlou: is my cat\n", "Request returned proper data");
+}
+
+done_testing;
+
@@ -1,204 +0,0 @@
-use strict;
-use warnings;
-use Test::More;
-use FindBin;
-use lib ( "$FindBin::Bin/../lib", "$FindBin::Bin/../t/lib" );
-
-use Catalyst::Request::REST;
-use HTTP::Headers;
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( {} );
-    $request->method('GET');
-    $request->content_type('text/foobar');
-
-    is_deeply( $request->accepted_content_types, [ 'text/foobar' ],
-               'content-type set in request headers is found' );
-    is( $request->preferred_content_type, 'text/foobar',
-        'preferred content type is text/foobar' );
-    ok( ! $request->accept_only, 'accept_only is false' );
-    ok( $request->accepts('text/foobar'), 'accepts text/foobar' );
-    ok( ! $request->accepts('text/html'), 'does not accept text/html' );
-}
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( { 'content-type' => 'text/fudge' } );
-    $request->method('GET');
-    $request->content_type('text/foobar');
-
-    is_deeply( $request->accepted_content_types, [ 'text/foobar', 'text/fudge' ],
-               'content-type set in request headers and type in parameters is found' );
-    is( $request->preferred_content_type, 'text/foobar',
-        'preferred content type is text/foobar' );
-    ok( ! $request->accept_only, 'accept_only is false' );
-    ok( $request->accepts('text/foobar'), 'accepts text/foobar' );
-    ok( $request->accepts('text/fudge'), 'accepts text/fudge' );
-    ok( ! $request->accepts('text/html'), 'does not accept text/html' );
-}
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( { 'content-type' => 'text/fudge' } );
-    $request->method('POST');
-    $request->content_type('text/foobar');
-
-    ok( ! $request->accepts('text/fudge'), 'content type in parameters is ignored for POST' );
-}
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( {} );
-    $request->method('GET');
-    $request->headers->header(
-        'Accept' =>
-        # From Firefox 2.0 when it requests an html page
-        'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
-    );
-
-    is_deeply( $request->accepted_content_types,
-               [ qw( text/xml application/xml application/xhtml+xml
-                     image/png
-                     text/html
-                     text/plain
-                     */*
-                   ) ],
-               'accept header is parsed properly' );
-    is( $request->preferred_content_type, 'text/xml',
-        'preferred content type is text/xml' );
-    ok( $request->accept_only, 'accept_only is true' );
-    ok( $request->accepts('text/html'), 'accepts text/html' );
-    ok( $request->accepts('image/png'), 'accepts image/png' );
-    ok( ! $request->accepts('image/svg'), 'does not accept image/svg' );
-}
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( {} );
-    $request->method('GET');
-    $request->content_type('application/json');
-    $request->headers->header(
-        'Accept' =>
-        # From Firefox 2.0 when it requests an html page
-        'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
-    );
-
-    is_deeply( $request->accepted_content_types,
-               [ qw( application/json
-                     text/xml application/xml application/xhtml+xml
-                     image/png
-                     text/html
-                     text/plain
-                     */*
-                   ) ],
-               'accept header is parsed properly, and content-type header has precedence over accept' );
-    ok( ! $request->accept_only, 'accept_only is false' );
-}
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( {} );
-    $request->method('GET');
-    $request->content_type('application/json');
-    $request->headers->header(
-        'Accept' =>
-        # From Firefox 2.0 when it requests an html page
-        'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
-    );
-
-    is_deeply( $request->accepted_content_types,
-               [ qw( application/json
-                     text/xml application/xml application/xhtml+xml
-                     image/png
-                     text/html
-                     text/plain
-                     */*
-                   ) ],
-               'accept header is parsed properly, and content-type header has precedence over accept' );
-    ok( ! $request->accept_only, 'accept_only is false' );
-}
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( {} );
-    $request->method('GET');
-    $request->content_type('text/x-json');
-    $request->headers->header(
-        'Accept' => 'text/plain,text/x-json',
-    );
-
-    is_deeply( $request->accepted_content_types,
-               [ qw( text/x-json
-                     text/plain
-                   ) ],
-               'each type appears only once' );
-}
-
-{
-    my $request = Catalyst::Request::REST->new;
-    $request->{_context} = 'MockContext';
-    $request->headers( HTTP::Headers->new );
-    $request->parameters( {} );
-    $request->method('GET');
-    $request->content_type('application/json');
-    $request->headers->header(
-        'Accept' => 'text/plain,application/json',
-    );
-
-    is_deeply( $request->accepted_content_types,
-               [ qw( application/json
-                     text/plain
-                   ) ],
-               'each type appears only once' );
-}
-
-{
-  local %ENV=%ENV;
-  $ENV{CATALYST_DEBUG} = 0;
-  my $test = 'Test::Catalyst::Action::REST';
-  use_ok $test;
-  is($test->request_class, 'Catalyst::Request::REST',
-    'Request::REST took over for Request');
-
-  my $meta = Moose::Meta::Class->create_anon_class(
-      superclasses => ['Catalyst::Request'],
-  );
-  $meta->add_method('__random_method' => sub { 42 });
-
-  $test->request_class($meta->name);
-  # FIXME - setup_finished(0) is evil!
-  eval { $test->setup_finished(0); $test->setup };
-  ok !$@, 'Can setup again';
-  isnt $test->request_class, $meta->name, 'Different request class';
-  ok $test->request_class->can('__random_method'), 'Is right class';
-  ok $test->request_class->can('data'), 'Also smells like REST subclass';
-
-  {
-    package My::Request;
-    use base 'Catalyst::Request::REST';
-  }
-  $test->request_class('My::Request');
-  eval { $test->setup_finished(0); $test->setup };
-  is $@, '', 'no error from Request::REST subclass';
-}
-
-done_testing;
-
-package MockContext;
-
-sub prepare_body { }
@@ -0,0 +1,200 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+use Catalyst::Request;
+use Catalyst::Request::REST::ForBrowsers;
+use Catalyst::TraitFor::Request::REST::ForBrowsers;
+use Moose::Meta::Class;
+use HTTP::Headers;
+
+my $anon_class = Moose::Meta::Class->create_anon_class(
+    superclasses => ['Catalyst::Request'],
+    roles        => ['Catalyst::TraitFor::Request::REST::ForBrowsers'],
+    cache        => 1,
+)->name;
+
+# We run the tests twice to make sure Catalyst::Request::REST::ForBrowsers is
+# 100% back-compatible.
+for my $class ( $anon_class, 'Catalyst::Request::REST::ForBrowsers' ) {
+    {
+        for my $method (qw( GET POST PUT DELETE )) {
+            my $req = $class->new();
+            $req->method($method);
+            $req->{_context} = 'MockContext';
+            $req->parameters( {} );
+
+            is(
+                $req->method(), $method,
+                "$method - not tunneled"
+            );
+        }
+    }
+
+    {
+        for my $method (qw( PUT DELETE )) {
+            my $req = $class->new();
+            $req->method('POST');
+            $req->{_context} = 'MockContext';
+            $req->parameters( { 'x-tunneled-method' => $method } );
+
+            is(
+                $req->method(), $method,
+                "$method - tunneled with x-tunneled-method param"
+            );
+        }
+    }
+
+    {
+        for my $method (qw( PUT DELETE )) {
+            my $req = $class->new();
+            $req->method('POST');
+            $req->{_context} = 'MockContext';
+            $req->header( 'x-http-method-override' => $method );
+
+            is(
+                $req->method(), $method,
+                "$method - tunneled with x-http-method-override header"
+            );
+        }
+    }
+
+    {
+        for my $method (qw( PUT DELETE )) {
+            my $req = $class->new();
+            $req->method('GET');
+            $req->{_context} = 'MockContext';
+            $req->parameters( { 'x-tunneled-method' => $method } );
+
+            is(
+                $req->method(), 'GET',
+                'x-tunneled-method is ignore with a GET'
+            );
+        }
+    }
+
+    {
+        my $req = $class->new();
+        $req->{_context} = 'MockContext';
+        $req->method('GET');
+        $req->parameters( {} );
+        $req->headers( HTTP::Headers->new() );
+
+        ok(
+            $req->looks_like_browser(),
+            'default is a browser'
+        );
+    }
+
+    {
+        for my $with (qw( HTTP.Request XMLHttpRequest )) {
+            my $req = $class->new();
+            $req->{_context} = 'MockContext';
+            $req->headers(
+                HTTP::Headers->new( 'X-Requested-With' => $with ) );
+
+            ok(
+                !$req->looks_like_browser(),
+                "not a browser - X-Request-With = $with"
+            );
+        }
+    }
+
+    {
+        my $req = $class->new();
+        $req->{_context} = 'MockContext';
+        $req->method('GET');
+        $req->parameters( { 'content-type' => 'text/json' } );
+        $req->headers( HTTP::Headers->new() );
+
+        ok(
+            !$req->looks_like_browser(),
+            'forced non-HTML content-type is not a browser'
+        );
+    }
+
+    {
+        my $req = $class->new();
+        $req->{_context} = 'MockContext';
+        $req->method('GET');
+        $req->parameters( { 'content-type' => 'text/html' } );
+        $req->headers( HTTP::Headers->new() );
+
+        ok(
+            $req->looks_like_browser(),
+            'forced HTML content-type is not a browser'
+        );
+    }
+
+    {
+        my $req = $class->new();
+        $req->{_context} = 'MockContext';
+        $req->method('GET');
+        $req->parameters( {} );
+        $req->headers(
+            HTTP::Headers->new( 'Accept' => 'text/xml; q=0.4, */*; q=0.2' ) );
+
+        ok(
+            $req->looks_like_browser(),
+            'if it accepts */* it is a browser'
+        );
+    }
+
+    {
+        my $req = $class->new();
+        $req->{_context} = 'MockContext';
+        $req->method('GET');
+        $req->parameters( {} );
+        $req->headers(
+            HTTP::Headers->new(
+                'Accept' => 'text/html; q=0.4, text/xml; q=0.2'
+            )
+        );
+
+        ok(
+            $req->looks_like_browser(),
+            'if it accepts text/html it is a browser'
+        );
+    }
+
+    {
+        my $req = $class->new();
+        $req->{_context} = 'MockContext';
+        $req->method('GET');
+        $req->parameters( {} );
+        $req->headers(
+            HTTP::Headers->new(
+                'Accept' => 'application/xhtml+xml; q=0.4, text/xml; q=0.2'
+            )
+        );
+
+        ok(
+            $req->looks_like_browser(),
+            'if it accepts application/xhtml+xml it is a browser'
+        );
+    }
+
+    {
+        my $req = $class->new();
+        $req->{_context} = 'MockContext';
+        $req->method('GET');
+        $req->parameters( {} );
+        $req->headers(
+            HTTP::Headers->new(
+                'Accept' => 'text/json; q=0.4, text/xml; q=0.2'
+            )
+        );
+
+        ok(
+            !$req->looks_like_browser(),
+            'provided an Accept header but does not accept html, is not a browser'
+        );
+    }
+}
+
+done_testing;
+
+package MockContext;
+
+sub prepare_body { }
@@ -0,0 +1,215 @@
+use strict;
+use warnings;
+use Test::More;
+use FindBin;
+use lib ( "$FindBin::Bin/../lib", "$FindBin::Bin/../t/lib" );
+
+use Catalyst::Request::REST;
+use Catalyst::TraitFor::Request::REST;
+use Moose::Meta::Class;
+use HTTP::Headers;
+
+my $anon_class = Moose::Meta::Class->create_anon_class(
+    superclasses => ['Catalyst::Request'],
+    roles        => ['Catalyst::TraitFor::Request::REST::ForBrowsers'],
+    cache        => 1,
+)->name;
+
+for my $class ( $anon_class, 'Catalyst::Request::REST' ) {
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( {} );
+        $request->method('GET');
+        $request->content_type('text/foobar');
+
+        is_deeply( $request->accepted_content_types, [ 'text/foobar' ],
+                   'content-type set in request headers is found' );
+        is( $request->preferred_content_type, 'text/foobar',
+            'preferred content type is text/foobar' );
+        ok( ! $request->accept_only, 'accept_only is false' );
+        ok( $request->accepts('text/foobar'), 'accepts text/foobar' );
+        ok( ! $request->accepts('text/html'), 'does not accept text/html' );
+    }
+
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( { 'content-type' => 'text/fudge' } );
+        $request->method('GET');
+        $request->content_type('text/foobar');
+
+        is_deeply( $request->accepted_content_types, [ 'text/foobar', 'text/fudge' ],
+                   'content-type set in request headers and type in parameters is found' );
+        is( $request->preferred_content_type, 'text/foobar',
+            'preferred content type is text/foobar' );
+        ok( ! $request->accept_only, 'accept_only is false' );
+        ok( $request->accepts('text/foobar'), 'accepts text/foobar' );
+        ok( $request->accepts('text/fudge'), 'accepts text/fudge' );
+        ok( ! $request->accepts('text/html'), 'does not accept text/html' );
+    }
+
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( { 'content-type' => 'text/fudge' } );
+        $request->method('POST');
+        $request->content_type('text/foobar');
+
+        ok( ! $request->accepts('text/fudge'), 'content type in parameters is ignored for POST' );
+    }
+
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( {} );
+        $request->method('GET');
+        $request->headers->header(
+            'Accept' =>
+            # From Firefox 2.0 when it requests an html page
+            'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
+        );
+
+        is_deeply( $request->accepted_content_types,
+                   [ qw( text/xml application/xml application/xhtml+xml
+                         image/png
+                         text/html
+                         text/plain
+                         */*
+                       ) ],
+                   'accept header is parsed properly' );
+        is( $request->preferred_content_type, 'text/xml',
+            'preferred content type is text/xml' );
+        ok( $request->accept_only, 'accept_only is true' );
+        ok( $request->accepts('text/html'), 'accepts text/html' );
+        ok( $request->accepts('image/png'), 'accepts image/png' );
+        ok( ! $request->accepts('image/svg'), 'does not accept image/svg' );
+    }
+
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( {} );
+        $request->method('GET');
+        $request->content_type('application/json');
+        $request->headers->header(
+            'Accept' =>
+            # From Firefox 2.0 when it requests an html page
+            'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
+        );
+
+        is_deeply( $request->accepted_content_types,
+                   [ qw( application/json
+                         text/xml application/xml application/xhtml+xml
+                         image/png
+                         text/html
+                         text/plain
+                         */*
+                       ) ],
+                   'accept header is parsed properly, and content-type header has precedence over accept' );
+        ok( ! $request->accept_only, 'accept_only is false' );
+    }
+
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( {} );
+        $request->method('GET');
+        $request->content_type('application/json');
+        $request->headers->header(
+            'Accept' =>
+            # From Firefox 2.0 when it requests an html page
+            'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
+        );
+
+        is_deeply( $request->accepted_content_types,
+                   [ qw( application/json
+                         text/xml application/xml application/xhtml+xml
+                         image/png
+                         text/html
+                         text/plain
+                         */*
+                       ) ],
+                   'accept header is parsed properly, and content-type header has precedence over accept' );
+        ok( ! $request->accept_only, 'accept_only is false' );
+    }
+
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( {} );
+        $request->method('GET');
+        $request->content_type('text/x-json');
+        $request->headers->header(
+            'Accept' => 'text/plain,text/x-json',
+        );
+
+        is_deeply( $request->accepted_content_types,
+                   [ qw( text/x-json
+                         text/plain
+                       ) ],
+                   'each type appears only once' );
+    }
+
+    {
+        my $request = Catalyst::Request::REST->new;
+        $request->{_context} = 'MockContext';
+        $request->headers( HTTP::Headers->new );
+        $request->parameters( {} );
+        $request->method('GET');
+        $request->content_type('application/json');
+        $request->headers->header(
+            'Accept' => 'text/plain,application/json',
+        );
+
+        is_deeply( $request->accepted_content_types,
+                   [ qw( application/json
+                         text/plain
+                       ) ],
+                   'each type appears only once' );
+    }
+}
+
+{
+  local %ENV=%ENV;
+  $ENV{CATALYST_DEBUG} = 0;
+  my $test = 'Test::Catalyst::Action::REST';
+  use_ok $test;
+  ok($test->request_class->meta->does_role('Catalyst::TraitFor::Request::REST'),
+    'request class does REST role');
+
+  my $meta = Moose::Meta::Class->create_anon_class(
+      superclasses => ['Catalyst::Request'],
+  );
+  $meta->add_method('__random_method' => sub { 42 });
+
+  $test->request_class($meta->name);
+  # FIXME - setup_finished(0) is evil!
+  eval { $test->setup_finished(0); $test->setup };
+  ok !$@, 'Can setup again';
+  isnt $test->request_class, $meta->name, 'Different request class';
+  ok $test->request_class->can('__random_method'), 'Is right class';
+  ok($test->request_class->meta->does_role('Catalyst::TraitFor::Request::REST'),
+    'request class still does REST role');
+
+  {
+    package My::Request;
+    use base 'Catalyst::Request::REST';
+  }
+  $test->request_class('My::Request');
+  eval { $test->setup_finished(0); $test->setup };
+  is $@, '', 'no error from Request::REST subclass';
+}
+
+done_testing;
+
+package MockContext;
+
+sub prepare_body { }
@@ -0,0 +1,22 @@
+package Test::Catalyst::Action::REST::Controller::Override;
+
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller' }
+
+__PACKAGE__->config(
+    'default'   => 'application/json',
+    'map'       => {
+        'application/json'   => 'YAML', # Yes, this is deliberate!
+    },
+);
+
+sub test :Local :ActionClass('Serialize') {
+    my ( $self, $c ) = @_;
+    $c->stash->{'rest'} = {
+        lou => 'is my cat',
+    };
+}
+
+1;
@@ -7,6 +7,7 @@ use Catalyst::Runtime '5.70';
 
 use Catalyst;
 use FindBin;
+use Test::Catalyst::Log;
 
 __PACKAGE__->config(
     name => 'Test::Catalyst::Action::REST',
@@ -16,5 +17,6 @@ __PACKAGE__->config(
     },
 );
 __PACKAGE__->setup;
+__PACKAGE__->log( Test::Catalyst::Log->new );
 
 1;
@@ -0,0 +1,21 @@
+package Test::Catalyst::Log;
+
+use strict;
+use warnings;
+
+sub new {
+    bless {}, __PACKAGE__;
+}
+
+sub is_debug { 0 }
+sub debug { }
+sub is_info { 0 }
+sub info { }
+sub is_warn { 0 }
+sub warn : method { }
+sub is_error { 0 }
+sub error { }
+sub is_fatal { 0 }
+sub fatal { }
+
+1;
@@ -27,6 +27,9 @@ __PACKAGE__->config(
         'text/view'   => [ 'View', 'Simple' ],
         'text/explodingview' => [ 'View', 'Awful' ],
         'text/broken' => 'Broken',
+        'text/javascript', => 'JSONP',
+        'application/x-javascript' => 'JSONP',
+        'application/javascript' => 'JSONP',
     },
 );
 
@@ -10,12 +10,14 @@ use namespace::autoclean;
 use Catalyst::Runtime '5.70';
 
 use Catalyst;
+use Test::Catalyst::Log;
 
 __PACKAGE__->config(
     name => 'Test::Serialize',
 );
 
 __PACKAGE__->setup;
+__PACKAGE__->log( Test::Catalyst::Log->new );
 
 1;
 
@@ -1,9 +0,0 @@
-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();
@@ -0,0 +1,64 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+eval "use Test::Spelling";
+plan skip_all => "Test::Spelling required for testing POD coverage"
+    if $@;
+
+my @stopwords;
+for (<DATA>) {
+    chomp;
+    push @stopwords, $_
+        unless /\A (?: \# | \s* \z)/msx;    # skip comments, whitespace
+}
+
+add_stopwords(@stopwords);
+set_spell_cmd('aspell list -l en');
+
+# This prevents a weird segfault from the aspell command - see
+# https://bugs.launchpad.net/ubuntu/+source/aspell/+bug/71322
+local $ENV{LC_ALL} = 'C';
+all_pod_files_spelling_ok();
+
+__DATA__
+APIs
+ActionClass
+Daisuke
+Daisuke
+Deserialize
+Deserializer
+Deserializing
+Doran
+Goulah
+JSON
+Laco
+Maki
+Maki
+Marchex
+Pearcey
+RESTful
+RESTful
+SERIALIZERS
+TT
+Wikipedia
+XHR
+XMLHttpRequest
+YAML
+conf
+deserialize
+deserialized
+deserializing
+fREW
+html
+http
+javascript
+jrockway
+mst
+namespace
+plugins
+request's
+serializer
+thusly
+wildcard
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+use Test::More;
+
+use Test::Pod 1.14;
+
+all_pod_files_ok();
@@ -0,0 +1,23 @@
+use strict;
+use warnings;
+
+use File::Find::Rule;
+use Module::Info;
+
+use Test::More qw( no_plan );
+
+my %versions;
+for my $pm_file ( File::Find::Rule->file->name( qr/\.pm$/ )->in('lib' ) ) {
+    my $mod = Module::Info->new_from_file($pm_file);
+
+    ( my $stripped_file = $pm_file ) =~ s{^lib/}{};
+
+    $versions{$stripped_file} = $mod->version;
+}
+
+my $moose_ver = $versions{'Catalyst/Action/REST.pm'};
+
+for my $module ( grep { $_ ne 'Catalyst/Action/REST.pm' } sort keys %versions ) {
+    is( $versions{$module}, $moose_ver,
+        "version for $module is the same as in Catalyst/Action/REST.pm" );
+}