allow request/response deserialization/serialization by callback
Brian Phillips [Wed, 12 Oct 2011 17:05:41 +0000 (12:05 -0500)]
lib/Catalyst/Action/Deserialize/Callback.pm [new file with mode: 0644]
lib/Catalyst/Action/Serialize/Callback.pm [new file with mode: 0644]
lib/Catalyst/Controller/REST.pm
t/callback.t [new file with mode: 0644]
t/lib/Test/Serialize/Controller/REST.pm

diff --git a/lib/Catalyst/Action/Deserialize/Callback.pm b/lib/Catalyst/Action/Deserialize/Callback.pm
new file mode 100644 (file)
index 0000000..fa421fe
--- /dev/null
@@ -0,0 +1,47 @@
+package Catalyst::Action::Deserialize::Callback;
+
+use Moose;
+use namespace::autoclean;
+use Scalar::Util qw(openhandle);
+
+extends 'Catalyst::Action';
+
+our $VERSION = '0.91';
+$VERSION = eval $VERSION;
+
+sub execute {
+    my $self = shift;
+    my ( $controller, $c, $callbacks ) = @_;
+
+    my $rbody;
+
+    # could be a string or a FH
+    if ( my $body = $c->request->body ) {
+        if(openhandle $body) {
+            seek($body, 0, 0); # in case something has already read from it
+            while ( defined( my $line = <$body> ) ) {
+                $rbody .= $line;
+            }
+        } else {
+            $rbody = $body;
+        }
+    }
+
+    if ( $rbody ) {
+        my $rdata = eval { $callbacks->{deserialize}->( $rbody, $controller, $c ) };
+        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;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
+
diff --git a/lib/Catalyst/Action/Serialize/Callback.pm b/lib/Catalyst/Action/Serialize/Callback.pm
new file mode 100644 (file)
index 0000000..dba974a
--- /dev/null
@@ -0,0 +1,28 @@
+package Catalyst::Action::Serialize::Callback;
+
+use Moose;
+use namespace::autoclean;
+
+extends 'Catalyst::Action';
+use YAML::Syck;
+
+our $VERSION = '0.91';
+$VERSION = eval $VERSION;
+
+sub execute {
+    my $self = shift;
+    my ( $controller, $c, $callbacks ) = @_;
+
+    my $stash_key = (
+            $controller->{'serialize'} ?
+                $controller->{'serialize'}->{'stash_key'} :
+                $controller->{'stash_key'}
+        ) || 'rest';
+    my $output = $callbacks->{serialize}->( $c->stash->{$stash_key}, $controller, $c );
+    $c->response->output( $output );
+    return 1;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
index defe764..2499e83 100644 (file)
@@ -234,6 +234,25 @@ Your views should have a C<process> method like this:
       return $serialized;
   }
 
+=item * Callback
+
+For infinite flexibility, you can provide a callback for the
+deserialization/serialization steps.
+
+  __PACKAGE__->config(
+      map => {
+          'text/xml'  => [ 'Callback', { deserialize => \&parse_xml, serialize => \&render_xml } ],
+      }
+  );
+
+The C<deserialize> callback is passed a string that is the body of the
+request and is expected to return a scalar value that results from
+the deserialization.  The C<serialize> callback is passed the data
+structure that needs to be serialized and must return a string suitable
+for returning in the HTTP response.  In addition to receiving the scalar
+to act on, both callbacks are passed the controller object and the context
+(i.e. C<$c>) as the second and third arguments.
+
 =back
 
 By default, L<Catalyst::Controller::REST> will return a 
diff --git a/t/callback.t b/t/callback.t
new file mode 100644 (file)
index 0000000..434289b
--- /dev/null
@@ -0,0 +1,32 @@
+use strict;
+use warnings;
+use Test::More;
+use FindBin;
+
+use lib ("$FindBin::Bin/lib", "$FindBin::Bin/../lib");
+use Test::Rest;
+
+use_ok 'Catalyst::Test', 'Test::Serialize';
+
+my $t = Test::Rest->new('content_type' => 'text/my-csv');
+
+my $has_serializer = eval "require XML::Simple";
+
+    my $monkey_template = {
+        monkey => 'likes chicken!',
+    };
+    my $mres = request($t->get(url => '/monkey_get'));
+    ok( $mres->is_success, 'GET the monkey succeeded' );
+    my $output = { split( /,/, $mres->content ) };
+    is_deeply($output, $monkey_template, "GET returned the right data");
+
+    my $post_data = {
+        'sushi' => 'is good for monkey',
+    };
+    my $mres_post = request( $t->post( url => '/monkey_put', data => join( ',', %$post_data ) ) );
+    ok( $mres_post->is_success, "POST to the monkey succeeded");
+    is_deeply($mres_post->content, "is good for monkey", "POST data matches");
+
+1;
+
+done_testing;
index 5b7c4ec..fa1cac2 100644 (file)
@@ -30,6 +30,12 @@ __PACKAGE__->config(
         'text/javascript', => 'JSONP',
         'application/x-javascript' => 'JSONP',
         'application/javascript' => 'JSONP',
+        'text/my-csv' => [
+            'Callback', {
+                deserialize => sub { return {split /,/, shift } },
+                serialize   => sub { my $d = shift; join ',', %$d }
+            }
+        ],
     },
 );