Merge master up roles-saner mirror/roles-saner
Tomas Doran [Wed, 26 Aug 2009 00:52:09 +0000 (01:52 +0100)]
21 files changed:
lib/Catalyst/Action/Deserialize/Data/Serializer.pm
lib/Catalyst/Action/Deserialize/JSON.pm
lib/Catalyst/Action/Deserialize/View.pm
lib/Catalyst/Action/Deserialize/XML/Simple.pm
lib/Catalyst/Action/Deserialize/YAML.pm
lib/Catalyst/Action/REST.pm
lib/Catalyst/Action/Serialize/Data/Serializer.pm
lib/Catalyst/Action/Serialize/JSON.pm
lib/Catalyst/Action/Serialize/View.pm
lib/Catalyst/Action/Serialize/XML/Simple.pm
lib/Catalyst/Action/Serialize/YAML.pm
lib/Catalyst/Action/Serialize/YAML/HTML.pm
lib/Catalyst/Action/SerializeBase.pm
lib/Catalyst/ActionRole/DeserializeFormat.pm [new file with mode: 0644]
lib/Catalyst/ActionRole/SerializeFormat.pm [new file with mode: 0644]
lib/Catalyst/ControllerRole/SerializeConfig.pm [new file with mode: 0644]
lib/Catalyst/Request/REST.pm
lib/Catalyst/RequestRole/Deserialize.pm [new file with mode: 0644]
lib/Catalyst/RequestRole/REST.pm [new file with mode: 0644]
lib/Catalyst/ResponseRole/REST.pm [new file with mode: 0644]
t/catalyst-request-rest.t

index cbe1236..a87a015 100644 (file)
@@ -1,58 +1,19 @@
-#
-# Catalyst::Action::Deserialize::Data::Serializer.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Deserialize::Data::Serializer;
-
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::DeserializeFormat';
 use Data::Serializer;
+use namespace::clean -except => 'meta';
 
-sub execute {
-    my $self = shift;
-    my ( $controller, $c, $serializer ) = @_;
-
-    my $sp = $serializer;
-    $sp =~ s/::/\//g;
-    $sp .= ".pm";
-    eval {
-        require $sp
-    };
-    if ($@) {
-        $c->log->debug("Could not load $serializer, refusing to serialize: $@")
-            if $c->debug;
-        return 0;
-    }
-    my $body = $c->request->body;
-    if ($body) {
-        my $rbody;
-        if ( -f $c->request->body ) {
-            open( BODY, "<", $c->request->body );
-            while ( my $line = <BODY> ) {
-                $rbody .= $line;
-            }
-            close(BODY);
-        }
-        my $dso = Data::Serializer->new( serializer => $serializer );
-        my $rdata;
-        eval {
-            $rdata = $dso->raw_deserialize($rbody);
-        };
-        if ($@) {
-            return $@;
-        }
-        $c->request->data($rdata);
-    } else {
-        $c->log->debug(
-            'I would have deserialized, but there was nothing in the body!')
-                if $c->debug;
-    }
-    return 1;
+sub deserialize {
+  my ($self, $content, $c, $serializer) = @_;
+  unless (eval "use $serializer; 1") {
+    $c->log->debug("Could not load $serializer, refusing to serialize: $@")
+      if $c->debug;
+    return 0;
+  }
+  my $d = Data::Serializer->new(serializer => $serializer);
+  return $d->raw_deserialize($content);
 }
 
 1;
index f4bf2e4..63afb9a 100644 (file)
@@ -1,43 +1,10 @@
-#
-# Catlyst::Action::Deserialize::JSON.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Deserialize::JSON;
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::DeserializeFormat';
+use JSON qw(decode_json);
+use namespace::clean -except => 'meta';
 
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
-use JSON qw( decode_json );
-
-sub execute {
-    my $self = shift;
-    my ( $controller, $c, $test ) = @_;
-
-    my $body = $c->request->body;
-    my $rbody;
-
-    if ($body) {
-        while (my $line = <$body>) {
-            $rbody .= $line;
-        }
-    }
-
-    if ( $rbody ) {
-        my $rdata = eval { decode_json( $rbody ) };
-        if ($@) {
-            return $@;
-        }
-        $c->request->data($rdata);
-    } else {
-        $c->log->debug(
-            'I would have deserialized, but there was nothing in the body!')
-            if $c->debug;
-    }
-    return 1;
-}
+sub deserialize { decode_json $_[1] }
 
 1;
index e66b6e3..42e69b8 100644 (file)
@@ -1,12 +1,9 @@
 package Catalyst::Action::Deserialize::View;
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::DeserializeFormat';
+use namespace::clean -except => 'meta';
 
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
-
-sub execute {
-    return 1;
-}
+sub deserialize { undef };
 
 1;
index e2ca6fa..2fac9fa 100644 (file)
@@ -1,51 +1,16 @@
-#
-# Catlyst::Action::Deserialize::XML::Simple.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Deserialize::XML::Simple;
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::DeserializeFormat';
+use XML::Simple;
+use namespace::clean -except => 'meta';
 
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
-
-sub execute {
-    my $self = shift;
-    my ( $controller, $c, $test ) = @_;
-
-    eval {
-        require XML::Simple;
-    };
-    if ($@) {
-        $c->log->debug("Could not load XML::Simple, refusing to deserialize: $@")
-            if $c->debug;
-        return 0;
-    }
-
-    my $body = $c->request->body;
-    if ($body) {
-        my $xs = XML::Simple->new('ForceArray' => 0,);
-        my $rdata;
-        eval {
-            $rdata = $xs->XMLin( "$body" );
-        };
-        if ($@) {
-            return $@;
-        }
-        if (exists($rdata->{'data'})) {
-            $c->request->data($rdata->{'data'});
-        } else {
-            $c->request->data($rdata);
-        }
-    } else {
-        $c->log->debug(
-            'I would have deserialized, but there was nothing in the body!')
-                if $c->debug;
-    }
-    return 1;
+sub deserialize {
+  my ($self, $content) = @_;
+  my $x = XML::Simple->new(ForceArray => 0);
+  my $data = $x->XMLin($content);
+  $data = $data->{data} if exists $data->{data};
+  return $data;
 }
 
 1;
index ea2688b..ec6063d 100644 (file)
@@ -1,39 +1,10 @@
-#
-# Catlyst::Action::Deserialize::YAML.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Deserialize::YAML;
-
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::DeserializeFormat';
 use YAML::Syck;
+use namespace::clean -except => 'meta';
 
-sub execute {
-    my $self = shift;
-    my ( $controller, $c, $test ) = @_;
-
-    my $body = $c->request->body;
-    if ($body) {
-        my $rdata;
-        eval {
-            my $body = $c->request->body;
-            $rdata = LoadFile( "$body" );
-        };
-        if ($@) {
-            return $@;
-        }
-        $c->request->data($rdata);
-    } else {
-        $c->log->debug(
-            'I would have deserialized, but there was nothing in the body!')
-            if $c->debug;
-    }
-    return 1;
-}
+sub deserialize { Load $_[1] }
 
 1;
index c9d336e..276d655 100644 (file)
@@ -3,30 +3,22 @@
 # 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 Catalyst::Request::REST;
+use Moose::Util qw(does_role);
+use Catalyst::RequestRole::REST;
 use Catalyst::Controller::REST;
+use namespace::clean -except => 'meta';
+
+extends 'Catalyst::Action';
 
 BEGIN { require 5.008001; }
 
 our $VERSION = '0.76';
 $VERSION = eval $VERSION;
 
-sub new {
-  my $class  = shift;
-  my $config = shift;
-  Catalyst::Request::REST->_insert_self_into( $config->{class} );
-  return $class->next::method($config, @_);
-}
-
 =head1 NAME
 
 Catalyst::Action::REST - Automated REST Method Dispatching
@@ -89,6 +81,9 @@ sub dispatch {
     my $self = shift;
     my $c    = shift;
 
+    Catalyst::RequestRole::REST->meta->apply($c->request)
+        unless does_role($c->request, 'Catalyst::RequestRole::REST');
+
     my $controller = $c->component( $self->class );
     my $rest_method = $self->name . "_" . uc( $c->request->method );
 
index 24d88f1..637c245 100644 (file)
@@ -1,46 +1,18 @@
-#
-# Catalyst::Action::Serialize::Data::Serializer
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-#
-# $Id$
-
 package Catalyst::Action::Serialize::Data::Serializer;
-
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::SerializeFormat';
 use Data::Serializer;
+use namespace::clean -except => 'meta';
 
-sub execute {
-    my $self = shift;
-    my ( $controller, $c, $serializer ) = @_;
-
-    my $stash_key = (
-            $controller->{'serialize'} ?
-                $controller->{'serialize'}->{'stash_key'} :
-                $controller->{'stash_key'} 
-        ) || 'rest';
-    my $sp = $serializer;
-    $sp =~ s/::/\//g;
-    $sp .= ".pm";
-    eval {
-        require $sp
-    };
-    if ($@) {
-        $c->log->info("Could not load $serializer, refusing to serialize: $@");
-        return 0;
-    }
-    my $dso = Data::Serializer->new( serializer => $serializer );
-    my $data;
-    eval {
-       $data = $dso->raw_serialize($c->stash->{$stash_key});
-    };
-    if ($@) {
-        return $@;
-    } 
-    $c->response->output( $data );
-    return 1;
+sub serialize {
+  my ($self, $data, $c, $serializer) = @_;
+  unless (eval "use $serializer; 1") {
+    $c->log->info("Could not load $serializer, refusing to serialize: $@");
+    return 0;
+  }
+  my $d = Data::Serializer->new(serializer => $serializer);
+  return $d->raw_serialize($data);
 }
 
 1;
index 7423e9e..1806b98 100644 (file)
@@ -1,41 +1,10 @@
-#
-# Catlyst::Action::Serialize::JSON.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Serialize::JSON;
-
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::SerializeFormat';
 use JSON qw(encode_json);
+use namespace::clean -except => 'meta';
 
-sub execute {
-    my $self = shift;
-    my ( $controller, $c ) = @_;
-
-    my $stash_key = (
-            $controller->{'serialize'} ?
-                $controller->{'serialize'}->{'stash_key'} :
-                $controller->{'stash_key'} 
-        ) || 'rest';
-    my $output;
-    eval {
-        $output = $self->serialize( $c->stash->{$stash_key} );
-    };
-    if ($@) {
-        return $@;
-    }
-    $c->response->output( $output );
-    return 1;
-}
-
-sub serialize {
-    my $self = shift;
-    encode_json( shift );
-}
+sub serialize { encode_json $_[1] }
 
 1;
index 630a4e7..6e6f4b8 100644 (file)
@@ -1,25 +1,18 @@
 package Catalyst::Action::Serialize::View;
-use strict;
-use warnings;
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::SerializeFormat';
+use namespace::clean -except => 'meta';
 
-use base 'Catalyst::Action';
+sub serialize {
+  my ($self, $data, $c, $view) = @_;
 
-sub execute {
-    my $self = shift;
-    my ( $controller, $c, $view ) = @_;
+  unless ($c->view($view)) {
+      $c->log->error("Could not load $view, refusing to serialize");
+      return 0;
+  }
 
-    my $stash_key = (
-            $controller->{'serialize'} ?
-                $controller->{'serialize'}->{'stash_key'} :
-                $controller->{'stash_key'} 
-        ) || 'rest';
-
-    if ( !$c->view($view) ) {
-        $c->log->error("Could not load $view, refusing to serialize");
-        return 0;
-    }
-
-    return $c->view($view)->process($c);
+  return $c->view($view)->process($c);
 }
 
 1;
index fe0b5c1..55355a8 100644 (file)
@@ -1,45 +1,14 @@
-#
-# Catlyst::Action::Serialize::XML::Simple.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Serialize::XML::Simple;
-
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
-
-sub execute {
-    my $self = shift;
-    my ( $controller, $c ) = @_;
-
-    eval {
-        require XML::Simple
-    };
-    if ($@) {
-        $c->log->debug("Could not load XML::Serializer, refusing to serialize: $@")
-            if $c->debug;
-        return 0;
-    }
-    my $xs = XML::Simple->new(ForceArray => 0,);
-
-    my $stash_key = (
-            $controller->{'serialize'} ?
-                $controller->{'serialize'}->{'stash_key'} :
-                $controller->{'stash_key'} 
-        ) || 'rest';
-    my $output;
-    eval {
-        $output = $xs->XMLout({ data => $c->stash->{$stash_key} });
-    };
-    if ($@) {
-        return $@;
-    }
-    $c->response->output( $output );
-    return 1;
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::SerializeFormat';
+use XML::Simple;
+use namespace::clean -except => 'meta';
+
+sub serialize {
+  my ($self, $data, $c) = @_;
+  my $x = XML::Simple->new(ForceArray => 0);
+  return $x->XMLout({ data => $data });
 }
 
 1;
index 4879af9..0b3d1a1 100644 (file)
@@ -1,42 +1,10 @@
-#
-# Catalyst::Action::Serialize::YAML.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Serialize::YAML;
-
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
+use Moose;
+extends 'Catalyst::Action';
+with 'Catalyst::ActionRole::SerializeFormat';
 use YAML::Syck;
+use namespace::clean -except => 'meta';
 
-sub execute {
-    my $self = shift;
-    my ( $controller, $c ) = @_;
-
-    my $stash_key = (
-            $controller->{'serialize'} ?
-                $controller->{'serialize'}->{'stash_key'} :
-                $controller->{'stash_key'} 
-        ) || 'rest';
-    my $output;
-    eval {
-        $output = $self->serialize($c->stash->{$stash_key});
-    };
-    if ($@) {
-        return $@;
-    }
-    $c->response->output( $output );
-    return 1;
-}
-
-sub serialize {
-    my $self = shift;
-    my $data = shift;
-    Dump($data);
-}
+sub serialize { Dump $_[1] }
 
 1;
index 7d6fb31..4773c0b 100644 (file)
@@ -1,52 +1,33 @@
-#
-# Catlyst::Action::Serialize::YAML::HTML.pm
-# Created by: Adam Jacob, Marchex, <adam@hjksolutions.com>
-# Created on: 10/12/2006 03:00:32 PM PDT
-#
-# $Id$
-
 package Catalyst::Action::Serialize::YAML::HTML;
-
-use strict;
-use warnings;
-
-use base 'Catalyst::Action';
-use YAML::Syck;
+use Moose;
+extends 'Catalyst::Action::Serialize::YAML';
 use URI::Find;
+use namespace::clean -except => 'meta';
 
-sub execute {
-    my $self = shift;
-    my ( $controller, $c ) = @_;
-
-    my $stash_key = (
-            $controller->{'serialize'} ?
-                $controller->{'serialize'}->{'stash_key'} :
-                $controller->{'stash_key'} 
-        ) || 'rest';
-    my $app = $c->config->{'name'} || '';
-    my $output = "<html>";
-    $output .= "<title>" . $app . "</title>";
-    $output .= "<body><pre>";
-    my $text = Dump($c->stash->{$stash_key});
-    # Straight from URI::Find
-    my $finder = URI::Find->new(
-                              sub {
-                                  my($uri, $orig_uri) = @_;
-                                  my $newuri;
-                                  if ($uri =~ /\?/) {
-                                      $newuri = $uri . "&content-type=text/html";
-                                  } else {
-                                      $newuri = $uri . "?content-type=text/html";
-                                  }
-                                  return qq|<a href="$newuri">$orig_uri</a>|;
-                              });
-    $finder->find(\$text);
-    $output .= $text;
-    $output .= "</pre>";
-    $output .= "</body>";
-    $output .= "</html>";
-    $c->response->output( $output );
-    return 1;
-}
+around serialize => sub {
+  my $next = shift;
+  my ($self, $data, $c) = @_;
+  my $yaml = $self->$next($data, $c);
+  my $app = $c->config->{name} || '';
+  my $finder = URI::Find->new(sub {
+    my($uri, $orig_uri) = @_;
+    my $newuri;
+    if ($uri =~ /\?/) {
+        $newuri = $uri . "&content-type=text/html";
+    } else {
+        $newuri = $uri . "?content-type=text/html";
+    }
+    return qq|<a href="$newuri">$orig_uri</a>|;
+  });
+  my $output = "<html>";
+  $output .= "<title>" . $app . "</title>";
+  $output .= "<body><pre>";
+  $finder->find(\$yaml);
+  $output .= $yaml;
+  $output .= "</pre>";
+  $output .= "</body>";
+  $output .= "</html>";
+  return $output;
+};
 
 1;
index 93108bb..399899d 100644 (file)
@@ -10,23 +10,25 @@ use strict;
 use warnings;
 
 use base 'Catalyst::Action';
+use Moose::Util qw(does_role);
+use Catalyst::ControllerRole::SerializeConfig;
 use Module::Pluggable::Object;
-use Catalyst::Request::REST;
+use Catalyst::RequestRole::REST;
+use Catalyst::ResponseRole::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, @_);
-}
-
 __PACKAGE__->mk_accessors(qw(_serialize_plugins _loaded_plugins));
 
 sub _load_content_plugins {
     my $self = shift;
     my ( $search_path, $controller, $c ) = @_;
 
+    Catalyst::RequestRole::REST->meta->apply($c->request)
+      unless does_role($c->request, 'Catalyst::RequestRole::REST');
+
+    Catalyst::ResponseRole::REST->meta->apply($c->response)
+      unless does_role($c->response, 'Catalyst::ResponseRole::REST');
+
     unless ( defined( $self->_loaded_plugins ) ) {
         $self->_loaded_plugins( {} );
     }
@@ -47,19 +49,12 @@ sub _load_content_plugins {
     my $sarg;
     my $map;
 
-    my $config;
+    Catalyst::ControllerRole::SerializeConfig->meta->apply($controller)
+      unless does_role($controller, 'Catalyst::ControllerRole::SerializeConfig');
+
+    my $config = $controller->serialize_config;
     
-    if ( exists $controller->{'serialize'} ) {
-        $c->log->info("Using deprecated configuration for Catalyst::Action::REST!");
-        $c->log->info("Please see perldoc Catalyst::Action::REST for the update guide");
-        $config = $controller->{'serialize'};
-        # if they're using the deprecated config, they may be expecting a
-        # default mapping too.
-        $config->{map} ||= $controller->{map};
-    } else {
-        $config = $controller;
-    }
-    $map = $config->{'map'};
+    $map = $config->{map};
 
     # pick preferred content type
     my @accepted_types; # priority order, best first
@@ -79,8 +74,10 @@ sub _load_content_plugins {
     # pick the best match that we have a serializer mapping for
     my ($content_type) = grep { $map->{$_} } @accepted_types;
 
-    return $self->_unsupported_media_type($c, $content_type)
-        if not $content_type;
+    unless ($content_type) {
+        $c->response->unsupported_media_type;
+        return;
+    }
 
     # carp about old text/x-json
     if ($content_type eq 'text/x-json') {
@@ -105,10 +102,12 @@ sub _load_content_plugins {
         $sclass .= $mc;
         #}
         if ( !grep( /^$sclass$/, @{ $self->_serialize_plugins } ) ) {
-            return $self->_unsupported_media_type($c, $content_type);
+            $c->response->unsupported_media_type($content_type);
+            return;
         }
     } else {
-        return $self->_unsupported_media_type($c, $content_type);
+        $c->response->unsupported_media_type($content_type);
+        return;
     }
     unless ( exists( $self->_loaded_plugins->{$sclass} ) ) {
         my $load_class = $sclass;
@@ -118,7 +117,8 @@ sub _load_content_plugins {
         if ($@) {
             $c->log->error(
                 "Error loading $sclass for " . $content_type . ": $!" );
-            return $self->_unsupported_media_type($c, $content_type);
+            $c->response->unsupported_media_type($content_type);
+            return;
         } else {
             $self->_loaded_plugins->{$sclass} = 1;
         }
@@ -136,29 +136,6 @@ sub _load_content_plugins {
     return $sclass, $sarg, $content_type;
 }
 
-sub _unsupported_media_type {
-    my ( $self, $c, $content_type ) = @_;
-    $c->res->content_type('text/plain');
-    $c->res->status(415);
-    if (defined($content_type) && $content_type ne "") {
-        $c->res->body(
-            "Content-Type " . $content_type . " is not supported.\r\n" );
-    } else {
-        $c->res->body(
-            "Cannot find a Content-Type supported by your client.\r\n" );
-    }
-    return undef;
-}
-
-sub _serialize_bad_request {
-    my ( $self, $c, $content_type, $error ) = @_;
-    $c->res->content_type('text/plain');
-    $c->res->status(400);
-    $c->res->body(
-        "Content-Type " . $content_type . " had a problem with your request.\r\n***ERROR***\r\n$error" );
-    return undef;
-}
-
 1;
 
 =head1 NAME
diff --git a/lib/Catalyst/ActionRole/DeserializeFormat.pm b/lib/Catalyst/ActionRole/DeserializeFormat.pm
new file mode 100644 (file)
index 0000000..aecbe3b
--- /dev/null
@@ -0,0 +1,41 @@
+package Catalyst::ActionRole::DeserializeFormat;
+use Moose::Role;
+use Moose::Util qw(does_role);
+use Catalyst::RequestRole::Deserialize;
+use namespace::clean -except => 'meta';
+
+requires 'deserialize';
+
+around execute => sub {
+  my $next = shift;
+  my ($self, $controller, $c, $arg) = @_;
+
+  Catalyst::RequestRole::Deserialize->meta->apply($c->request)
+    unless does_role($c->request, 'Catalyst::RequestRole::Deserialize');
+
+  my $content = "";
+  my $body = $c->request->body;
+  if ($body) {
+    local $_;
+    while (<$body>) { $content .= $_ }
+  }
+
+  if ($content) {
+    my $data = eval {
+      $self->deserialize(
+        $content,
+        $c,
+        $arg,
+      )
+    };
+    return $@ if $@;
+    $c->request->_set_data($data);
+  } else {
+    $c->debug && $c->log->debug(
+      'I would have deserialized, but there was nothing in the body!'
+    );
+  }
+  return 1;
+};
+
+1;
diff --git a/lib/Catalyst/ActionRole/SerializeFormat.pm b/lib/Catalyst/ActionRole/SerializeFormat.pm
new file mode 100644 (file)
index 0000000..bb17619
--- /dev/null
@@ -0,0 +1,38 @@
+package Catalyst::ActionRole::SerializeFormat;
+use Moose::Role;
+use Catalyst::ControllerRole::SerializeConfig;
+use Moose::Util qw(does_role);
+use namespace::clean -except => 'meta';
+requires 'serialize';
+
+around execute => sub {
+  my $next = shift;
+  my ($self, $controller, $c, $arg) = @_;
+
+  # XXX is ignoring the return value here correct? the original serialize
+  # actions never even called their body.
+  $self->$next($controller, $c, $arg)
+    if blessed $self;
+
+  Catalyst::ControllerRole::SerializeConfig->meta->apply($controller)
+    unless does_role($controller, 'Catalyst::ControllerRole::SerializeConfig');
+      
+  my $stash_key = $controller->serialize_config->{stash_key} || 'rest';
+
+  my $output;
+  eval {
+    $output = $self->serialize(
+      $c->stash->{$stash_key},
+      $c,
+      $arg,
+    );
+  };
+  return $@ if $@;
+  # horrible, but the best I can do given the existing magic return value
+  # conventions.
+  return $output if $output eq '0';
+  $c->response->body($output) unless $c->response->body;
+  return 1;
+};
+
+1;
diff --git a/lib/Catalyst/ControllerRole/SerializeConfig.pm b/lib/Catalyst/ControllerRole/SerializeConfig.pm
new file mode 100644 (file)
index 0000000..1626ce2
--- /dev/null
@@ -0,0 +1,34 @@
+package Catalyst::ControllerRole::SerializeConfig;
+use Moose::Role;
+use namespace::clean -except => 'meta';
+
+my @KEYS = qw(map content_type_stash_key default);
+
+has serialize_config => (
+  is         => 'ro',
+  isa        => 'HashRef',
+  init_arg   => undef,
+  lazy_build => 1,
+);
+
+sub _build_serialize_config {
+  my $self = shift;
+  my $c = $self->_application;
+  my $config;
+  if ( exists $self->{serialize} ) {
+    $c->log->info("Using deprecated configuration for Catalyst::Action::REST!");
+    $c->log->info("Please see perldoc Catalyst::Action::REST for the update guide");
+    $config = $self->{serialize};
+    # if they're using the deprecated config, they may be expecting a
+    # default mapping too.
+    $config->{map} ||= $self->{map};
+  } else {
+    # do not store a reference to itself in the controller
+    $config = {
+      map {; $_ => $self->{$_} } @KEYS
+    };
+  }
+  return $config;
+};
+
+1;
index 41d2a21..b1404af 100644 (file)
@@ -3,18 +3,13 @@
 # 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 Scalar::Util qw/blessed/;
-
-use base qw/Catalyst::Request Class::Accessor::Fast/;
+use Moose;
+extends qw/Catalyst::Request/;
+with qw/Catalyst::RequestRole::REST Catalyst::RequestRole::Deserialize/;
 
 use Catalyst::Utils;
-use HTTP::Headers::Util qw(split_header_words);
 
 sub _insert_self_into {
   my ($class, $app_class ) = @_;
diff --git a/lib/Catalyst/RequestRole/Deserialize.pm b/lib/Catalyst/RequestRole/Deserialize.pm
new file mode 100644 (file)
index 0000000..c49ade6
--- /dev/null
@@ -0,0 +1,10 @@
+package Catalyst::RequestRole::Deserialize;
+use Moose::Role;
+use namespace::clean -except => 'meta';
+
+has data => (
+  is => 'ro',
+  writer => '_set_data',
+);
+
+1;
diff --git a/lib/Catalyst/RequestRole/REST.pm b/lib/Catalyst/RequestRole/REST.pm
new file mode 100644 (file)
index 0000000..707e5e1
--- /dev/null
@@ -0,0 +1,140 @@
+package Catalyst::RequestRole::REST;
+# ABSTRACT: A REST-y role for Catalyst::Request
+use Moose::Role;
+
+use Catalyst::Utils;
+use HTTP::Headers::Util qw(split_header_words);
+use namespace::clean -except => 'meta';
+
+has accept_only => (
+  is      => 'rw',
+  isa     => 'Bool',
+#  writer  => '_set_accept_only', FIXME fails for me if I use this
+  default => 0,
+);
+
+has accepted_content_types => (
+  is         => 'ro',
+  isa        => 'ArrayRef',
+  init_arg   => undef,
+  lazy_build => 1,
+);
+
+has _accepted_content_types_hash => (
+  is         => 'ro',
+  isa        => 'HashRef',
+  init_arg   => undef,
+  lazy_build => 1,
+);
+
+sub _build_accepted_content_types {
+  my $self = shift;
+  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" &&
+    (my $ct = $self->params->{'content-type'})) {
+    $types{ $ct } = 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; # FIXME fails if _set_accept_only
+
+    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 [ sort { $types{$b} <=> $types{$a} } keys %types ];
+}
+
+sub preferred_content_type { $_[0]->accepted_content_types->[0] }
+
+sub _build__accepted_content_types_hash {
+  return { map {; $_ => 1 } @{ $_[0]->accepted_content_types } };
+}
+
+sub accepts { $_[0]->_accepted_content_types_hash->{$_[1]} }
+
+1;
+
+__END__
+
+=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>.
+
+=method 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 4
+
+=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.
+
+=method preferred_content_type
+
+This returns the first content type found. It is shorthand for:
+
+  $request->accepted_content_types->[0]
+
+=method accepts
+
+Given a content type, this returns true if the type is accepted.
+
+Note that this does not do any wildcard expansion of types.
+
+=cut
diff --git a/lib/Catalyst/ResponseRole/REST.pm b/lib/Catalyst/ResponseRole/REST.pm
new file mode 100644 (file)
index 0000000..1772f68
--- /dev/null
@@ -0,0 +1,29 @@
+package Catalyst::ResponseRole::REST;
+
+use Moose::Role;
+
+sub unsupported_media_type {
+  my ($self, $content_type) = @_;
+
+  $self->content_type('text/plain');
+  $self->status(415);
+
+  if (defined $content_type and length $content_type) {
+    $self->body("Content-Type $content_type is not supported.\r\n");
+  } else {
+    $self->body("Cannot find a Content-Type supported by your client.\r\n");
+  }
+}
+
+sub serialize_bad_request {
+  my ($self, $content_type, $error) = @_;
+  $self->content_type('text/plain');
+  $self->status(400);
+  $self->body(
+    "Content-Type $content_type had a problem with your request\r\n" .
+    "***ERROR***\r\n$error"
+  );
+  return undef;
+}
+
+1;
index 2226793..20d96af 100644 (file)
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use Test::More tests => 28;
+use Test::More tests => 26;
 use FindBin;
 use lib ( "$FindBin::Bin/../lib", "$FindBin::Bin/../t/lib" );
 
@@ -172,20 +172,8 @@ use HTTP::Headers;
   $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');
-
-  $test->request_class('Some::Other::Class');
-  eval { $test->setup_finished(0); $test->setup };
-  like $@, qr/$test has a custom request class Some::Other::Class/;
-
-  {
-    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';
+  is($test->request_class, 'Catalyst::Request',
+    'Request::REST did not take over for Request');
 }
 
 package MockContext;