From f359ea0d638c2c71e8f228cf3fca7bce9945891d Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 13 Jan 2015 17:57:03 -0500 Subject: [PATCH 1/2] Sereal encoder --- lib/Message/Passing/Filter/Decoder/Sereal.pm | 74 ++++++++++++++++ lib/Message/Passing/Filter/Encoder/Sereal.pm | 92 ++++++++++++++++++++ t/sereal.t | 46 ++++++++++ 3 files changed, 212 insertions(+) create mode 100644 lib/Message/Passing/Filter/Decoder/Sereal.pm create mode 100644 lib/Message/Passing/Filter/Encoder/Sereal.pm create mode 100644 t/sereal.t diff --git a/lib/Message/Passing/Filter/Decoder/Sereal.pm b/lib/Message/Passing/Filter/Decoder/Sereal.pm new file mode 100644 index 0000000..760f8f4 --- /dev/null +++ b/lib/Message/Passing/Filter/Decoder/Sereal.pm @@ -0,0 +1,74 @@ +package Message::Passing::Filter::Decoder::Sereal; +use Moo; +use Sereal::Decoder; +use Try::Tiny; +use Message::Passing::Exception::Decoding; +use namespace::clean -except => 'meta'; + +with qw/ + Message::Passing::Role::Filter + Message::Passing::Role::HasErrorChain +/; + +has sereal_args => ( + is => 'ro', + default => sub { {} }, +); + +has _sereal => ( + is => 'lazy', + handles => [ 'decode' ], + default => sub { + my $s = shift; + return Sereal::Decoder->new( $s->sereal_args ); + }, +); + +sub filter { + my ($self, $message) = @_; + try { + $self->decode( $message ) + } + catch { + $self->error->consume(Message::Passing::Exception::Decoding->new( + exception => $_, + packed_data => $message, + )); + return; # Explicit return undef + }; +} + +1; + +=head1 NAME + +Message::Passing::Role::Filter::Decoder::Sereal + +=head1 DESCRIPTION + +Decodes string messages from Sereal into data structures. + +=head1 ATTRIBUTES + +=head1 METHODS + +=head2 new( %args ) + +Constructor. On top of the generic filter arguments, accepts an optional C, +which will be used as the arguments for the constructor of the +underlying L object. + +=head2 filter( $message ) + +Sereal-decodes the message supplied as a parameter. + +=head1 SEE ALSO + +=over + +=item L + +=item L + +=back + diff --git a/lib/Message/Passing/Filter/Encoder/Sereal.pm b/lib/Message/Passing/Filter/Encoder/Sereal.pm new file mode 100644 index 0000000..2b92e48 --- /dev/null +++ b/lib/Message/Passing/Filter/Encoder/Sereal.pm @@ -0,0 +1,92 @@ +package Message::Passing::Filter::Encoder::Sereal; +use Moo; +use MooX::Types::MooseLike::Base qw( Bool HasMethods ); +use Sereal::Encoder; +use Scalar::Util qw/ blessed /; +use Try::Tiny; +use Message::Passing::Exception::Encoding; +use namespace::clean -except => 'meta'; + +with qw/ + Message::Passing::Role::Filter + Message::Passing::Role::HasErrorChain +/; + +has sereal_args => ( + is => 'ro', + default => sub { {} }, +); + +has _sereal => ( + is => 'lazy', + handles => [ 'encode' ], + default => sub { + my $self = shift; + return Sereal::Encoder->new( $self->sereal_args ); + }, +); + +sub filter { + my ($self, $message) = @_; + try { + if (blessed $message) { # FIXME - This should be moved out of here! + if ($message->can('pack')) { + $message = $message->pack; + } + elsif ($message->can('to_hash')) { + $message = $message->to_hash; + } + } + $self->encode( $message ); + } + catch { + $self->error->consume(Message::Passing::Exception::Encoding->new( + exception => $_, + stringified_data => $message, + )); + return; # Explicitly drop the message from normal processing + } +} + +1; + +=head1 NAME + +Message::Passing::Role::Filter::Encoder::Sereal - Encodes data structures as Sereal for output + +=head1 DESCRIPTION + +This filter takes a hash ref or an object for a message, and serializes it to +L. + +Plain refs work as expected, and classes providing either a +C or C method. This means that anything based on +L or L should be correctly +serialized. + +=head1 METHODS + +=head2 new( %args ) + +Constructor. On top of the generic filter arguments, accepts an optional C, +which will be used as the arguments for the constructor of the +underlying L object. + + +=head2 filter( $message ) + +Performs the Serial encoding. + + +=head1 SEE ALSO + +=over + +=item L + +=item L + +=back + +=cut + diff --git a/t/sereal.t b/t/sereal.t new file mode 100644 index 0000000..439de29 --- /dev/null +++ b/t/sereal.t @@ -0,0 +1,46 @@ +use strict; +use warnings; +use Test::More; +use Try::Tiny; + +plan skip_all => "Sereal::Encoder or Sereal::Decoder not present" + unless eval <<'END'; + use Sereal::Decoder; + use Sereal::Encoder; + 1; +END + +use Message::Passing::Filter::Decoder::Sereal; +use Message::Passing::Filter::Encoder::Sereal; +use Message::Passing::Output::Test; +use Message::Passing::Input::Null; +use Message::Passing::Output::Null; + +my $cbct = Message::Passing::Output::Test->new; +my $cbc = Message::Passing::Input::Null->new( + output_to => Message::Passing::Filter::Encoder::Sereal->new( + output_to => Message::Passing::Filter::Decoder::Sereal->new( + output_to => $cbct, + ), + ), +); + +# Simulate dropping a message! +{ + local $cbc->output_to->{output_to} = Message::Passing::Output::Null->new; + $cbc->output_to->consume({ foo => 'bar' }); +} + +is $cbct->message_count, 0; + +subtest structure => sub { + my $struct = { a => 'foo', b => [ 1,2,3] }; + $cbc->output_to->consume( $struct ); + + is $cbct->message_count => 1, "message made it"; + is_deeply( ($cbct->messages)[-1], $struct, "content is good" ); +}; + + +done_testing; + From f7147a8b034354c2fbf115dfa56493ee2b54c6b2 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 3 Nov 2015 15:33:10 -0500 Subject: [PATCH 2/2] don't need the types --- lib/Message/Passing/Filter/Encoder/Sereal.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Message/Passing/Filter/Encoder/Sereal.pm b/lib/Message/Passing/Filter/Encoder/Sereal.pm index 2b92e48..d35a32f 100644 --- a/lib/Message/Passing/Filter/Encoder/Sereal.pm +++ b/lib/Message/Passing/Filter/Encoder/Sereal.pm @@ -1,6 +1,5 @@ package Message::Passing::Filter::Encoder::Sereal; use Moo; -use MooX::Types::MooseLike::Base qw( Bool HasMethods ); use Sereal::Encoder; use Scalar::Util qw/ blessed /; use Try::Tiny;