Skip Menu |

This queue is for tickets about the Storable-AMF CPAN distribution.

Report information
The Basics
Id: 64917
Status: resolved
Priority: 0/
Queue: Storable-AMF

People
Owner: Nobody in particular
Requestors: adam_lounds [...] hotmail.com
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: 0.88
Fixed in: (no value)



Subject: Boolean support, TO_AMF hook
Firstly, thank you so much for this module! We are using booleans extensively for both JSON-RPC and AMF, so wanted a way to be able to roundtrip booleans in AMF. We also missed the TO_JSON functionality from JSON::XS. I made a patch to implement these, it's pretty rough but works for our internal use. * Freezes 'boolean' perl objects to AMF boolean * Freezes JSON::XS boolean objects to AMF boolean * calls TO_AMF method on an object if present as a freeze hook (code based on JSON::XS) * Thaws AMF boolean to JSON::XS boolean objects The test suite now fails on two main points: * 01 fails as booleans are not thawing to 1/0 as expected (they are JSON::XS booleans) * 01, 46, 48 and 53 fail as they need to use JSON::XS. I did not implement the changes for AMF3, only AMF0, and the creation of JSON::XS objects on thaw should really be an option. Hope this helps anyone using this wonderful module.
Subject: AMF.xs.patch
--- Storable-AMF-0.88/lib/Storable/AMF.xs 2010-11-27 20:02:57.000000000 +0000 +++ Storable-AMF-0.88_patch/lib/Storable/AMF.xs 2011-01-18 22:40:50.000000000 +0000 @@ -589,7 +589,31 @@ ++io->RV_COUNT; if (sv_isobject(one)) { - if (SvTYPE(rv) == SVt_PVHV){ + GV *to_amf = gv_fetchmethod_autoload (SvSTASH (rv), "TO_AMF", 0); + if ( to_amf ) { + dSP; + + ENTER; SAVETMPS; PUSHMARK (SP); + XPUSHs (sv_bless (sv_2mortal (newRV_inc (rv)), SvSTASH (rv))); + + // calling with G_SCALAR ensures that we always get a 1 return value + PUTBACK; + call_sv ((SV *)GvCV (to_amf), G_SCALAR); + SPAGAIN; + + // catch this surprisingly common error + if (SvROK (TOPs) && SvRV (TOPs) == rv) + croak ("%s::TO_AMF method returned same object as was passed instead of a new one", HvNAME (SvSTASH (rv))); + + rv = POPs; + PUTBACK; + + //--io->RV_COUNT; + format_one( aTHX_ io, rv); + + FREETMPS; LEAVE; + } + else if (SvTYPE(rv) == SVt_PVHV){ format_typed_object(aTHX_ io, (HV *) rv); } else if ( util_is_date( rv ) ) { @@ -597,6 +621,14 @@ io_write_double(aTHX_ io, util_date_time( rv )); io_write_s16(aTHX_ io, 0 ); } + else if (sv_isa(one, "boolean")) { + io_write_marker(aTHX_ io, MARKER0_BOOLEAN ); + io_write_u8(aTHX_ io, SvIV(SvRV(one))); + } + else if (sv_isa(one, "JSON::XS::Boolean")) { + io_write_marker(aTHX_ io, MARKER0_BOOLEAN ); + io_write_u8(aTHX_ io, SvIV(SvRV(one))); + } else { // may be i has to format as undef io_register_error(io, ERR_BAD_OBJECT); @@ -1285,7 +1317,33 @@ STATIC_INLINE SV* parse_boolean(pTHX_ struct io_struct * io){ char marker; marker = io_read_marker(io); - return newSViv(marker == '\000' ? 0 :1); + + int count; + SV *value = 0; + SAVETMPS; + + dSP; + + ENTER; SAVETMPS; PUSHMARK (SP); + PUTBACK; + if ( marker == '\000' ) { + count = call_pv("JSON::XS::false", G_SCALAR); + } + else { + count = call_pv("JSON::XS::true", G_SCALAR); + } + SPAGAIN; + if (count == 1) + value = newSVsv(POPs); + + if (count != 1 || !SvOK(value)) { + // fallback, return 0/1 + value = newSViv( marker == '\000' ? 0 : 1 ); + } + + PUTBACK; + FREETMPS; LEAVE; + return value; } inline SV * amf3_parse_one(pTHX_ struct io_struct *io);
Subject: Re: [rt.cpan.org #64917] Boolean support, TO_AMF hook
Date: Wed, 19 Jan 2011 17:05:20 +0300
To: bug-Storable-AMF [...] rt.cpan.org
From: Anatoliy Grishayev <0body1 [...] rambler.ru>
19.01.2011 12:27, adam_lounds@hotmail.com via RT пишет: Show quoted text
> Wed Jan 19 04:27:20 2011: Request 64917 was acted upon. > Transaction: Ticket created by adam_lounds@hotmail.com > Queue: Storable-AMF > Subject: Boolean support, TO_AMF hook > Broken in: 0.88 > Severity: Wishlist > Owner: Nobody > Requestors: adam_lounds@hotmail.com > Status: new > Ticket<URL: https://rt.cpan.org/Ticket/Display.html?id=64917> > > > Firstly, thank you so much for this module! > > We are using booleans extensively for both JSON-RPC and AMF, so wanted a way to be able > to roundtrip booleans in AMF. We also missed the TO_JSON functionality from JSON::XS. > > I made a patch to implement these, it's pretty rough but works for our internal use. > * Freezes 'boolean' perl objects to AMF boolean > * Freezes JSON::XS boolean objects to AMF boolean > * calls TO_AMF method on an object if present as a freeze hook (code based on JSON::XS) > * Thaws AMF boolean to JSON::XS boolean objects > > The test suite now fails on two main points: > > * 01 fails as booleans are not thawing to 1/0 as expected (they are JSON::XS booleans) > * 01, 46, 48 and 53 fail as they need to use JSON::XS. > > I did not implement the changes for AMF3, only AMF0, and the creation of JSON::XS objects > on thaw should really be an option. > > Hope this helps anyone using this wonderful module.
Okay, but I have some questions about implementation 1) Did you have a test or tests for your functionality? 2) What a 'boolean' object you are using 3) Are you never using tagged objects in AMF ( like typeid object string ) see parse_typed_object() 4) It easy add your functionality as option to thaw and freeze so It need a proper name for the option. Best Regards, Anatoly
From: adam_lounds [...] hotmail.com
Show quoted text
> Okay, but I have some questions about implementation
Show quoted text
> 1) Did you have a test or tests for your functionality?
I did write some during dev but not up to the same standard as yours Show quoted text
> 2) What a 'boolean' object you are using
The patch supports freezing of boolean.pm (http://search.cpan.org/~ingy/boolean- 0.25/lib/boolean.pm) and JSON::XS (http://search.cpan.org/~mlehmann/JSON-XS-2.3/) objects, and thaws AMF0 boolean values to JSON::XS::true or JSON::XS::false. Show quoted text
> 3) Are you never using tagged objects in AMF ( like typeid object string ) see
parse_typed_object() Not at the moment, no. As I understand it any object without TO_AMF() will fall through to the existing behaviour. make test still passes but I'm not sure if there are any tests for tagged objects? Show quoted text
> 4) It easy add your functionality as option to thaw and freeze so It > need a proper name for the option.
Sure. TBH we needed it internally & the patch was more in case anyone else needed it. I'm pretty sure that the freezing of boolean/JSON::XS objects to AMF0 boolean is transparent - without thawing to JSON::XS they will end up as 1/0 as that's how AMF0 booleans currently thaw, so I don't think that freezing of booleans requires an option. JSON::XS has a convert_blessed option which enables calling TO_JSON, mongodb has $MongoDB::BSON::use_boolean = 1 which enables thawing BSON bools as boolean.pm objects The TO_AMF support could be $Storable::AMF::convert_blessed as per JSON::XS, and thawing to JSON::XS bools could be $Storable::AMF::use_json_xs (or $Storable::AMF::use_boolean for thawing to boolean.pm bools) Also note that I did not touch the AMF3 parts of the code, so the new features do not currently work in AMF3 mode. Thanks again, -- Adam
From: adam_lounds [...] hotmail.com
Ugh. Sorry about this - the original patch doesn't work if you have multiple true/false values, as booleans cannot be referenced as AMF objects. I have refactored to avoid adding booleans to the HV_HASH and to avoid polluting HV_HASH with objects that have TO_AMF, but I'm not entirely sure it's bug-free, and the TO_AMF code feels bad in that I'm having to recurse and return.
Subject: AMF.xs.patch
--- Storable-AMF-0.88/lib/Storable/AMF.xs 2010-11-27 20:02:57.000000000 +0000 +++ Storable-AMF-0.88_patch/lib/Storable/AMF.xs 2011-01-20 11:27:06.000000000 +0000 @@ -576,45 +576,90 @@ inline void format_one(pTHX_ struct io_struct *io, SV * one){ - if (SvROK(one)){ + if ( SvROK(one) && sv_isobject(one) ) { SV * rv = (SV*) SvRV(one); - // test has stored - SV **OK = hv_fetch(io->RV_HASH, (char *)(&rv), sizeof (rv), 1); - if (SvOK(*OK)) { - format_reference(aTHX_ io, *OK); + GV *to_amf = gv_fetchmethod_autoload (SvSTASH (rv), "TO_AMF", 0); + if ( to_amf ) { + dSP; + + ENTER; SAVETMPS; PUSHMARK (SP); + XPUSHs (sv_bless (sv_2mortal (newRV_inc (rv)), SvSTASH (rv))); + PUTBACK; + + // calling with G_SCALAR ensures that we always get a 1 return value + call_sv ((SV *)GvCV (to_amf), G_SCALAR); + SPAGAIN; + + // catch this surprisingly common error + if (SvROK (TOPs) && SvRV (TOPs) == rv) + croak ("%s::TO_AMF method returned same object as was passed instead of a new one", HvNAME (SvSTASH (rv))); + + SV *newsv = POPs; + // recurse rather than carrying on as we need to freetmps etc + format_one( aTHX_ io, newsv); + PUTBACK; + FREETMPS; LEAVE; + return; } - else { - int type = SvTYPE(rv); - sv_setiv(*OK, io->RV_COUNT); - ++io->RV_COUNT; + } + + bool is_perl_ref = SvROK(one) ? 1 : 0; + bool is_perl_bool = 0; + if ( is_perl_ref && sv_isobject(one) ) { + if ( sv_isa(one, "boolean") ) { + is_perl_bool = 1; + } + else if ( sv_isa(one, "JSON::XS::Boolean") ) { + is_perl_bool = 1; + } + } - if (sv_isobject(one)) { - if (SvTYPE(rv) == SVt_PVHV){ - format_typed_object(aTHX_ io, (HV *) rv); + if ( is_perl_ref ){ + // Bools cannot be referenced + if ( is_perl_bool ) { + io_write_marker(aTHX_ io, MARKER0_BOOLEAN ); + io_write_u8(aTHX_ io, SvIV(SvRV(one))); + } + else { + SV * rv = (SV*) SvRV(one); + // test has stored + SV **OK = hv_fetch(io->RV_HASH, (char *)(&rv), sizeof (rv), 1); + if (SvOK(*OK)) { + format_reference(aTHX_ io, *OK); + } + else { + int type = SvTYPE(rv); + sv_setiv(*OK, io->RV_COUNT); + ++io->RV_COUNT; + + if (sv_isobject(one)) { + if (SvTYPE(rv) == SVt_PVHV){ + format_typed_object(aTHX_ io, (HV *) rv); + } + else if ( util_is_date( rv ) ) { + io_write_marker(aTHX_ io, MARKER0_DATE ); + io_write_double(aTHX_ io, util_date_time( rv )); + io_write_s16(aTHX_ io, 0 ); + } + else { + // may be i has to format as undef + io_register_error(io, ERR_BAD_OBJECT); + } } - else if ( util_is_date( rv ) ) { - io_write_marker(aTHX_ io, MARKER0_DATE ); - io_write_double(aTHX_ io, util_date_time( rv )); - io_write_s16(aTHX_ io, 0 ); - } - else { - // may be i has to format as undef + else if (SvTYPE(rv) == SVt_PVAV) + format_strict_array(aTHX_ io, (AV*) rv); + else if (SvTYPE(rv) == SVt_PVHV) { + io_write_marker(aTHX_ io, MARKER0_OBJECT); + format_object(aTHX_ io, (HV*) rv); + } + else if ( type != SVt_PVCV && type != SVt_PVGV ) { + format_scalar_ref(aTHX_ io, (SV*) rv); + } + else { + io->message = "bad type of object in stream"; io_register_error(io, ERR_BAD_OBJECT); } } - else if (SvTYPE(rv) == SVt_PVAV) - format_strict_array(aTHX_ io, (AV*) rv); - else if (SvTYPE(rv) == SVt_PVHV) { - io_write_marker(aTHX_ io, MARKER0_OBJECT); - format_object(aTHX_ io, (HV*) rv); - } - else if ( type != SVt_PVCV && type != SVt_PVGV ) { - format_scalar_ref(aTHX_ io, (SV*) rv); - } - else { - io->message = "bad type of object in stream"; - io_register_error(io, ERR_BAD_OBJECT); - } } } else { @@ -1285,7 +1330,33 @@ STATIC_INLINE SV* parse_boolean(pTHX_ struct io_struct * io){ char marker; marker = io_read_marker(io); - return newSViv(marker == '\000' ? 0 :1); + + int count; + SV *value = 0; + SAVETMPS; + + dSP; + + ENTER; SAVETMPS; PUSHMARK (SP); + PUTBACK; + if ( marker == '\000' ) { + count = call_pv("JSON::XS::false", G_SCALAR); + } + else { + count = call_pv("JSON::XS::true", G_SCALAR); + } + SPAGAIN; + if (count == 1) + value = newSVsv(POPs); + + if (count != 1 || !SvOK(value)) { + // fallback, return 0/1 + value = newSViv( marker == '\000' ? 0 : 1 ); + } + + PUTBACK; + FREETMPS; LEAVE; + return value; } inline SV * amf3_parse_one(pTHX_ struct io_struct *io);
Subject: 66-boolean.t
use strict; use ExtUtils::testlib; use Storable::AMF0 qw(parse_option freeze thaw new_amfdate); use Data::Dumper; use Devel::Peek; use JSON::XS; use boolean; my $total = 13; #*CORE::GLOBAL::caller = sub { CORE::caller($_[0] + $Carp::CarpLevel + 1) }; use warnings; eval "use Test::More tests=>$total;"; warn $@ if $@; my $nop = parse_option('prefer_number'); our $var; # constants ok( !is_amf_boolean ( !!1 ), 'perl bool context not converted(t)'); ok( !is_amf_boolean ( !!0 ), 'perl bool context not converted(f)'); ok( is_amf_boolean ( true ), '"boolean" true'); ok( is_amf_boolean ( false ), '"boolean" false'); ok( is_amf_boolean ( JSON::XS::true ), 'JSON::XS::true'); ok( is_amf_boolean ( JSON::XS::false ), 'JSON::XS::false'); # Vars ok( !is_amf_boolean ( $a = 4 ), 'int var'); ok( !is_amf_boolean ( $a = 4.0 ), 'double var'); ok( !is_amf_boolean ( $a = "4" ), 'str var'); ok( is_amf_boolean ( $a = JSON::XS::true ), 'JSON::XS bool var'); ok( is_amf_boolean ( $a = true ), 'boolean var'); # booleans cannot be referenced in amf my $object = { a => {a => 1}, jxb1 => JSON::XS::true, jxb2 => JSON::XS::true, c => {a => 1, jxb3 => JSON::XS::true }, }; is_deeply( amf_roundtrip($object), $object, "roundtrip multi-bool" ); is_deeply( amf_roundtrip( true ), JSON::XS::true, '"boolean" comes back as JSON::XS' ); sub is_amf_boolean{ ord( freeze( $_[0], $_[1]||0 )) == 1; } sub amf_roundtrip { my $src = shift; #use Data::Dumper; #print "src: ", Dumper($src); my $amf = freeze( $src ); #diag( "stored: $amf" ); #diag( "stored(hex): ", unpack("H*", $amf) ); my $struct = thaw($amf); #diag(Dumper($struct)); return $struct; }
From: adam_lounds [...] hotmail.com
Sorry, missed the test for TO_AMF
Subject: 67-toamf.t
use lib 't'; use strict; use warnings; use ExtUtils::testlib; use Storable::AMF0; eval 'use Test::More tests => 4;'; sub serialize{ my @values = Storable::AMF0::freeze($_[0]); if (@values != 1) { print STDERR "many returned values\n"; } return $values[0]; } package Test::ToAMF; sub new{ bless {foo => 'bar'}; } sub TO_AMF { return { %{ $_[0] }, a => 1 }; } package main; sub MyDump{ join "", map { ord >31 ? $_ : "\\x". unpack "H*", $_ } split "", $_[0]; } my $obj = Test::ToAMF->new(); my $bank = serialize($obj); my $newobj = Storable::AMF0::thaw($bank); ok(defined($bank), 'froze ok' ); ok(defined($newobj), 'thawed ok' ); my $expected = { foo => 'bar', a => 1 }; is_deeply( $newobj, $expected, 'thawed TO_AMF version'); is(ref ($newobj), 'HASH', 'TO_AMF version is unblessed' );
Subject: Re: [rt.cpan.org #64917] Boolean support, TO_AMF hook
Date: Thu, 20 Jan 2011 19:05:57 +0300
To: bug-Storable-AMF [...] rt.cpan.org
From: Anatoliy Grishayev <0body1 [...] rambler.ru>
20.01.2011 17:43, adam_lounds@hotmail.com via RT пишет: Show quoted text
> Queue: Storable-AMF > Ticket<URL: https://rt.cpan.org/Ticket/Display.html?id=64917> > > Sorry, missed the test for TO_AMF > >
Thank you for all, especially for test, I will be add your patch a little latter, maybe at this weekend. For now I am a little busy, Sorry... Anatoliy.
CC: Alberto Reggiori <areggiori [...] gmail.com>
Subject: Re: [rt.cpan.org #64917] Boolean support, TO_AMF hook
Date: Fri, 21 Jan 2011 20:39:58 +0300
To: bug-Storable-AMF [...] rt.cpan.org
From: Anatoliy Grishayev <0body1 [...] rambler.ru>
20.01.2011 19:06, Grian via RT пишет: Show quoted text
> Added boolean.pm JSON::XS support. Code similiar to your patch with > some modifications
Usage : use Storable::AMF0 0.89 qw(freeze thaw); use Storable::AMF0 0.89 qw(parse_option); # For JSON::XS support use boolean; use JSON::XS; $obj = { true => JSON::XS::true, false => boolean( 0 ), }; freeze( $obj, parse_option( "json_boolean")); thaw( $amf ); Show quoted text
> Thanks for tests, and code Anatoliy.
P.S. TO_AMF will add latter P.S.S add boolean AMF3 support.
Subject: Re: [rt.cpan.org #64917] Boolean support, TO_AMF hook
Date: Mon, 24 Jan 2011 15:56:36 +0300
To: bug-Storable-AMF [...] rt.cpan.org
From: Anatoliy Grishayev <0body1 [...] rambler.ru>
21.01.2011 20:40, Grian via RT пишет: Added experimental TO_AMF support Usage use Storable::AMF::Mapper; use Storable::AMF0 0.91 qw(freeze thaw); my $mapper = Storable::AMF::Mapper->new( to_amf=>1 ); my $amf0 = freeze( $obj, $mapper ); P.S. Need to change docs a little. Mapper for now is not object, but may change in future. Best regards. Anatoliy.
Fixed for now.