├── .gitattributes ├── .gitignore ├── Changes ├── README.md ├── TODO ├── dist.ini ├── inc └── GPD │ └── Build.pm ├── lib └── Google │ └── ProtocolBuffers │ ├── Dynamic.pm │ └── Dynamic │ ├── AddPragma.pm │ ├── Introspection.pod │ ├── MakeModule.pm │ ├── Message.pod │ ├── ProtocInterface.pm │ └── ProtocPlugin.pm ├── scripts ├── benchmark.pl ├── make_plugin_interface.pl ├── profile.pl ├── profile │ ├── map.proto │ ├── person.proto │ └── transform.proto ├── protoc-gen-perl-gpd ├── protoc-gen-perl-gpd-add-pragma └── protoc │ ├── README │ ├── descriptor.pb │ └── plugin.pb ├── src ├── descriptorloader.cpp ├── descriptorloader.h ├── dynamic.cpp ├── dynamic.h ├── fieldmap.cpp ├── fieldmap.h ├── introspection.cpp ├── introspection.h ├── mapper.cpp ├── mapper.h ├── mapper_context.cpp ├── mapper_context.h ├── pb │ ├── decoder.cpp │ ├── decoder.h │ ├── gpb_mapping.cpp │ └── gpb_mapping.h ├── perl_unpollute.h ├── ppport.h ├── ref.h ├── servicedef.h ├── sourcetree.cpp ├── sourcetree.h ├── thx_member.h ├── transform.cpp ├── transform.h ├── unordered_map.h ├── upb │ ├── LICENSE │ ├── bridge.cpp │ └── bridge.h └── vectorsink.h ├── t ├── 001_load.t ├── 010_basic.t ├── 020_primitives.t ├── 021_bool.t ├── 025_bigints.t ├── 030_repeated.t ├── 032_map.t ├── 033_proto2_map.t ├── 034_map_incomplete.t ├── 035_required.t ├── 036_proto3_packed.t ├── 040_messages.t ├── 042_recursion.t ├── 045_enum.t ├── 048_edge_cases.t ├── 049_error_checking.t ├── 050_extensions.t ├── 055_oneof.t ├── 057_proto3_optional.t ├── 060_tie.t ├── 065_checked_new.t ├── 067_warnings.t ├── 070_order.t ├── 080_json.t ├── 082_wkt.t ├── 085_wkt_json.t ├── 100_type_mapping.t ├── 101_map_once.t ├── 102_service_mapping.t ├── 105_package_mapping.t ├── 107_package_prefix_mapping.t ├── 108_message_prefix_mapping.t ├── 110_mixed_mapping.t ├── 115_simplified_mapping.t ├── 140_parse_string.t ├── 145_parse_binary.t ├── 200_check_required.t ├── 205_explicit_defaults.t ├── 210_encode_defaults.t ├── 212_ignore_undef_values.t ├── 215_check_enum_values.t ├── 220_accessor_style.t ├── 225_decode_unblessed.t ├── 235_fail_ref_coercion.t ├── 241_decode_to_fieldtable.t ├── 241_encode_to_fieldtable.t ├── 241_encode_unknown_fields.t ├── 250_decode_callbacks_sanity.t ├── 250_encode_callback_sanity.t ├── 251_decode_callbacks_invalid_fields.t ├── 255_decode_callbacks_field.t ├── 260_decode_callback_map.t ├── 300_scalar_accessors.t ├── 301_boolean_accessors.t ├── 305_oneof_accessors.t ├── 310_list_accessors.t ├── 315_message_accessors.t ├── 320_map_accessors.t ├── 330_field_numbers.t ├── 335_extension_api.t ├── 350_introspection.t ├── 351_enum_introspection.t ├── 352_proto3_optional_introspection.t ├── 353_file_introspection.t ├── 355_map_introspection.t ├── 357_service_introspection.t ├── 358_custom_options.t ├── 359_options.t ├── 400_issue_12.t ├── 401_descriptor_error.t ├── 402_parsing_error.t ├── 403_issue_46.t ├── 404_issue_47.t ├── 452_protoc_generated_wkt.t ├── 605_basic_grpc.t ├── 610_stream_client_grpc.t ├── 615_stream_server_grpc.t ├── 620_stream_bidi_grpc.t ├── grpc │ ├── sayhello.pl │ ├── sayhello_stream_bidi.pl │ ├── sayhello_stream_client.pl │ └── sayhello_stream_server.pl ├── lib │ ├── DummyTiedArray.pm │ ├── DummyTiedHash.pm │ ├── DummyTiedScalar.pm │ ├── GrpcClient.pm │ ├── Test.pm │ └── generated │ │ └── Protoc │ │ └── WKT │ │ └── Scalar.pm └── proto │ ├── bigint.proto │ ├── bool.proto │ ├── defaults_proto3.proto │ ├── edge.proto │ ├── enum.proto │ ├── extensions.proto │ ├── grpc │ └── greeter.proto │ ├── hierarchical │ ├── test1.proto │ ├── test2.proto │ ├── test3.proto │ └── test4.proto │ ├── map.proto │ ├── map_proto2.proto │ ├── mapping │ ├── test1.proto │ ├── test2.proto │ ├── test3.proto │ └── wkts.proto │ ├── message.proto │ ├── message_prefix │ ├── a.proto │ ├── b.proto │ └── r.proto │ ├── oneof.proto │ ├── optional_proto3.proto │ ├── options.proto │ ├── options │ ├── builtin_options.proto │ ├── custom_options.proto │ └── custom_options_use.proto │ ├── order.proto │ ├── packed_proto3.proto │ ├── person.pb │ ├── person.proto │ ├── person3.proto │ ├── recurse.proto │ ├── repeated.proto │ ├── scalar.proto │ ├── transform │ ├── decoder.proto │ └── encoder.proto │ └── wkt │ ├── copies │ ├── LICENSE │ ├── duration.proto │ ├── timestamp.proto │ └── wrappers.proto │ ├── scalar.pb │ ├── scalar.proto │ ├── scalar_copies.pb │ ├── scalar_copies.proto │ ├── timestamp.pb │ └── timestamp.proto └── xsp ├── dynamic.xsp ├── introspection.xsp ├── main.xspt └── mapper.xsp /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pb -diff 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Build 2 | /Build.bat 3 | /dev_Build.PL 4 | /Google-ProtocolBuffers-Dynamic* 5 | /META.* 6 | /MYMETA.* 7 | /_build 8 | /.build 9 | /blib 10 | /buildtmp 11 | /pod2htm?.tmp 12 | *.o 13 | *.obj 14 | GPATH 15 | GRTAGS 16 | GTAGS 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Google::ProtocolBuffers::Dynamic 2 | Fast and complete Perl protobuf implementation using uPB and Google .proto parser 3 | 4 | ## Example usage 5 | 6 | ```perl 7 | $dynamic = Google::ProtocolBuffers::Dynamic->new; 8 | $dynamic->load_string("person.proto", <<'EOT'); 9 | syntax = "proto2"; 10 | 11 | package humans; 12 | 13 | message Person { 14 | required string name = 1; 15 | required int32 id = 2; 16 | optional string email = 3; 17 | } 18 | EOT 19 | 20 | $dynamic->map({ package => 'humans', prefix => 'Humans' }); 21 | 22 | # encoding/decoding 23 | $person = Humans::Person->decode("\x0a\x03foo\x10\x1f"); 24 | $person = Humans::Person->decode_json('{"id":31,"name":"John Doe"}'); 25 | $bytes = Humans::Person->encode($person); 26 | $bytes = Humans::Person->encode_json($person); 27 | 28 | # field accessors 29 | $person = Humans::Person->new; 30 | $person->set_id(77); 31 | $id = $person->get_id; 32 | ``` 33 | 34 | See [the full documentation](https://metacpan.org/pod/Google::ProtocolBuffers::Dynamic) on MetaCPAN. 35 | 36 | ## Description 37 | 38 | This module provides a complete Protocol Buffers implementation for Perl: it supports both proto2 and proto3 syntax, 39 | and it supports gRPC client/server generation using [Grpc::XS](https://metacpan.org/pod/Grpc::XS) as the transport (other 40 | transports can be added). 41 | 42 | Since Google C++ library is used for Protocol Buffer parsing and loading, the full protocol buffers syntax is supported. 43 | Serialization/deserialization uses uPB, because it provides a better interface for the specific task of creating a protobuf 44 | library for a dynamic language. 45 | 46 | ## Speed 47 | 48 | There is a simple benchmark script in [scripts/benchmark.pl](https://github.com/mbarbon/google-protobuf-dynamic/blob/master/scripts/benchmark.pl): 49 | it only tests a small subset of features, and the exact speed difference is going to depend heavily on the 50 | exact shape of the data and the protobuf schema. As expected, `Google::ProtocolBuffers::Dynamic` is much faster than the 51 | pure-Perl `Google::ProtocolBuffers` implementation. `JSON::XS` and `Sereal` are included only as speed reference: the feature set 52 | of Protocol Buffers, JSON and Sereal is different enough that one can't be used as a drop-in for the other. 53 | 54 | Example results, running on Perl 5.20 on an Intel i7-3537U 55 | ``` 56 | Encoder 57 | Rate protobuf_pp protobuf sereal json 58 | protobuf_pp 95467/s -- -91% -95% -95% 59 | protobuf 1071850/s 1023% -- -47% -47% 60 | sereal 2025658/s 2022% 89% -- -0% 61 | json 2029876/s 2026% 89% 0% -- 62 | 63 | Decoder 64 | Rate protobuf_pp json protobuf sereal 65 | protobuf_pp 64576/s -- -85% -90% -95% 66 | json 420872/s 552% -- -37% -67% 67 | protobuf 670690/s 939% 59% -- -47% 68 | sereal 1274310/s 1873% 203% 90% -- 69 | 70 | Encoder (arrays) 71 | Rate protobuf_pp protobuf json sereal 72 | protobuf_pp 853/s -- -97% -98% -99% 73 | protobuf 33184/s 3789% -- -15% -64% 74 | json 39009/s 4471% 18% -- -58% 75 | sereal 92839/s 10780% 180% 138% -- 76 | 77 | Decoder (arrays) 78 | Rate protobuf_pp protobuf json sereal 79 | protobuf_pp 519/s -- -95% -97% -97% 80 | protobuf 10987/s 2019% -- -28% -41% 81 | json 15175/s 2827% 38% -- -18% 82 | sereal 18618/s 3491% 69% 23% -- 83 | ``` 84 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Implement: 2 | - serialize fields sorted by field number 3 | (will break current oneof serialization check) 4 | - simple extension option (Google::ProtocolBuffers compatibility) 5 | - implement unknown fields (needs uPB support) 6 | - prepare for the different sematics of proto3 7 | - enum unrecognized values are always passed through 8 | - unknown fields are always discarded 9 | - any 10 | - remove from fast path 11 | - enum validation 12 | - oneof handling 13 | - required handling 14 | - default value handling 15 | - lazy fields 16 | - custom options? 17 | - generic extension methods perform a linear scan on the extension list 18 | - better parameter checking/error handling in protoc plugin 19 | - check duplicate oneof fields in check methods? 20 | - check required fields in check methods? 21 | - proto3 well-known types 22 | - any 23 | - struct 24 | - fieldmask 25 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = Google-ProtocolBuffers-Dynamic 2 | version = 0.43 3 | author = Mattia Barbon 4 | license = Perl_5 5 | copyright_holder = Mattia Barbon 6 | copyright_year = 2015-2016 7 | 8 | [NextRelease] 9 | format = %-9v %{yyyy-MM-dd HH:mm:ss zzz}d 10 | [@Filter] 11 | -bundle = @Git 12 | -remove = Git::Push 13 | commit_msg = Preparing release %v 14 | tag_format = Google-ProtocolBuffers-Dynamic-%v 15 | tag_message = 16 | [MetaJSON] 17 | [@Filter] 18 | -bundle = @Basic 19 | -remove = MakeMaker 20 | -remove = ConfirmRelease 21 | -remove = UploadToCPAN 22 | [FakeRelease] 23 | [PruneFiles] 24 | filename = dev_Build.PL 25 | filename = _build 26 | filename = buildtmp 27 | [Prereqs] 28 | perl = 5.008 29 | autodie = 0 30 | [Prereqs / ConfigureRequires] 31 | Alien::uPB::Core=0.20 32 | Alien::ProtoBuf=0.08 33 | Module::Build::WithXSpp=0.14 34 | [Prereqs / BuildRequires] 35 | ExtUtils::Typemaps::Default=1.05 36 | ExtUtils::ParseXS=2.30 37 | [Prereqs / TestRequires] 38 | ; authordep Test::Pod = 1.43 39 | Test::Warn = 0 40 | Test::Exception = 0 41 | Test::Differences = 0 42 | [ModuleBuild] 43 | mb_class = GPD::Build 44 | [OurPkgVersion] 45 | [PodWeaver] 46 | [PodSyntaxTests] 47 | [GithubMeta] 48 | user = mbarbon 49 | repo = google-protobuf-dynamic 50 | -------------------------------------------------------------------------------- /inc/GPD/Build.pm: -------------------------------------------------------------------------------- 1 | package GPD::Build; 2 | 3 | use strict; 4 | use warnings; 5 | use parent 'Module::Build::WithXSpp'; 6 | 7 | use Alien::ProtoBuf; 8 | use Alien::uPB::Core; 9 | use Getopt::Long qw( :config pass_through ); 10 | 11 | # yes, doing this in a module is ugly; OTOH it's a private module 12 | GetOptions( 13 | 'g' => \my $DEBUG, 14 | ); 15 | 16 | sub new { 17 | my $class = shift; 18 | 19 | if (!Alien::ProtoBuf->atleast_version('3.0.0')) { 20 | warn sprintf "Protocol Buffers library version 3.0.0 required, %s installed\n", Alien::ProtoBuf->version; 21 | exit 0; 22 | } 23 | 24 | my $debug_flag = $DEBUG ? ' -g' : ''; 25 | my $self = $class->SUPER::new( 26 | @_, 27 | extra_typemap_modules => { 28 | 'ExtUtils::Typemaps::STL::String' => '0', 29 | }, 30 | extra_linker_flags => [Alien::uPB::Core->libs, Alien::ProtoBuf->libs], 31 | extra_compiler_flags => [$debug_flag, Alien::uPB::Core->cflags, Alien::ProtoBuf->cflags, Alien::ProtoBuf->cxxflags, "-DPERL_NO_GET_CONTEXT"], 32 | script_files => [qw( 33 | scripts/protoc-gen-perl-gpd 34 | scripts/protoc-gen-perl-gpd-add-pragma 35 | )], 36 | ); 37 | 38 | return $self; 39 | } 40 | 41 | 1; 42 | -------------------------------------------------------------------------------- /lib/Google/ProtocolBuffers/Dynamic/AddPragma.pm: -------------------------------------------------------------------------------- 1 | package Google::ProtocolBuffers::Dynamic::AddPragma; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | # protoc insertion points example 7 | 8 | sub generate_codegen_request { 9 | my ($class, $request) = @_; 10 | my $response = Google::ProtocolBuffers::Dynamic::ProtocInterface::CodeGeneratorResponse->new; 11 | 12 | my ($output_file, $pragma); 13 | for my $parameter (split /,/, $request->get_parameter) { 14 | my ($key, $value) = split /=/, $parameter; 15 | 16 | if ($key eq 'package') { 17 | my $package = join '::', map ucfirst, split /\./, $value; 18 | ($output_file = $package) =~ s{::}{/}g; 19 | } elsif ($key eq 'pragma') { 20 | $pragma = join '::', split /\./, $value; 21 | } 22 | } 23 | 24 | $response->add_file( 25 | Google::ProtocolBuffers::Dynamic::ProtocInterface::CodeGeneratorResponse::File->new({ 26 | # the filename needs to match the one produced by the perl-gpd plugin 27 | name => "$output_file.pm", 28 | insertion_point => 'after_pragmas', 29 | content => "use $pragma;", 30 | }), 31 | ); 32 | 33 | return $response; 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /lib/Google/ProtocolBuffers/Dynamic/ProtocPlugin.pm: -------------------------------------------------------------------------------- 1 | package Google::ProtocolBuffers::Dynamic::ProtocPlugin; 2 | 3 | use strict; 4 | use warnings; 5 | use Google::ProtocolBuffers::Dynamic::ProtocInterface; 6 | 7 | sub import { 8 | my ($class, %args) = @_; 9 | 10 | if (@ARGV && grep /^(?:--help|-h)$/, @ARGV) { 11 | my $message = 'This is a protoc plugin and should not be used directly. Use --man for documentation'; 12 | 13 | if ('podusage' eq ($args{help} // '')) { 14 | require Pod::Usage; 15 | 16 | Pod::Usage::pod2usage( 17 | -message => $message . "\n", 18 | -exitval => 1, 19 | ); 20 | } else { 21 | print STDERR $message, "\n\n"; 22 | 23 | exit 1; 24 | } 25 | } elsif (@ARGV && grep /^--man$/, @ARGV) { 26 | require Pod::Usage; 27 | 28 | Pod::Usage::pod2usage( 29 | -exitval => 0, 30 | -verbose => 2, 31 | ); 32 | } 33 | 34 | my $generator = $args{run}; 35 | eval "require $generator; 1" 36 | or die "Error loding plugin '$generator': $@"; 37 | 38 | binmode STDIN; 39 | binmode STDOUT; 40 | 41 | my $input = do { 42 | local $/; 43 | readline STDIN; 44 | }; 45 | my $codegen_request = Google::ProtocolBuffers::Dynamic::ProtocInterface::CodeGeneratorRequest->decode($input); 46 | my $codegen_response; 47 | 48 | eval { 49 | my $code = $generator->generate_codegen_request($codegen_request); 50 | 51 | $codegen_response = Google::ProtocolBuffers::Dynamic::ProtocInterface::CodeGeneratorResponse->encode($code); 52 | 53 | 1; 54 | } or do { 55 | my $error = $@ || "Zombie error"; 56 | 57 | $codegen_response = Google::ProtocolBuffers::Dynamic::ProtocInterface::CodeGeneratorResponse->encode({ 58 | error => $error, 59 | }); 60 | }; 61 | 62 | print STDOUT $codegen_response; 63 | } 64 | 65 | 1; 66 | -------------------------------------------------------------------------------- /scripts/make_plugin_interface.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use Google::ProtocolBuffers::Dynamic::MakeModule; 6 | 7 | Google::ProtocolBuffers::Dynamic::MakeModule->generate_to( 8 | package => 'Google::ProtocolBuffers::Dynamic::ProtocInterface', 9 | mappings => [ 10 | { 11 | package => 'google.protobuf', 12 | prefix => 'Google::ProtocolBuffers::Dynamic::ProtocInterface', 13 | options => { encode_defaults => 1 }, 14 | }, 15 | { 16 | package => 'google.protobuf.compiler', 17 | prefix => 'Google::ProtocolBuffers::Dynamic::ProtocInterface', 18 | options => { encode_defaults => 1 }, 19 | }, 20 | ], 21 | descriptor_files => [qw(scripts/protoc/descriptor.pb scripts/protoc/plugin.pb)], 22 | file => 'lib/Google/ProtocolBuffers/Dynamic/ProtocInterface.pm', 23 | ); 24 | 25 | exit 0; 26 | -------------------------------------------------------------------------------- /scripts/profile/map.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package profile; 4 | 5 | message StringInt32Map { 6 | map string_int32_map = 1; 7 | } 8 | 9 | message StringStringMap { 10 | map string_string_map = 1; 11 | } 12 | 13 | message Maps { 14 | repeated StringInt32Map string_int32_maps = 1; 15 | repeated StringStringMap string_string_maps = 2; 16 | } 17 | -------------------------------------------------------------------------------- /scripts/profile/person.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package profile; 4 | 5 | message Person { 6 | string name = 1; 7 | int32 id = 2; 8 | string email = 3; 9 | } 10 | 11 | message PersonArray { 12 | repeated Person persons = 1; 13 | } 14 | -------------------------------------------------------------------------------- /scripts/profile/transform.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package profile; 4 | 5 | message Any { 6 | sint64 int64_value = 1; 7 | string string_value = 2; 8 | repeated Any array_value = 3; 9 | map map_value = 4; 10 | } 11 | 12 | message Values { 13 | map values = 1; 14 | } 15 | -------------------------------------------------------------------------------- /scripts/protoc-gen-perl-gpd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use Google::ProtocolBuffers::Dynamic::ProtocPlugin 3 | help => 'podusage', 4 | run => 'Google::ProtocolBuffers::Dynamic::MakeModule'; 5 | 6 | exit 0; 7 | 8 | __END__ 9 | 10 | =head1 NAME 11 | 12 | protoc-gen-perl-gpd - protoc plugin for Google::ProtocolBuffers::Dynamic 13 | 14 | =head1 SYNOPSIS 15 | 16 | protoc --perl-gpd_out=package=Foo.Bar:lib foo.proto 17 | protoc --perl-gpd_out=package=Foo.Bar,check_enum_values:lib foo.proto 18 | protoc --perl-gpd_out=package=Foo.Bar,no_check_enum_values:lib foo.proto 19 | 20 | =head1 DESCRIPTION 21 | 22 | This F plugin generates wrapper modules for protobuf messages, 23 | to avoid the need of shipping F<.proto> files with a 24 | module/distribution. 25 | 26 | Note that since protoc command-line parsing reserves the colon as a 27 | separator between options list and output directory, the dot is used 28 | as a package separator for both protobuf and Perl packages. 29 | 30 | =head1 OPTIONS 31 | 32 | =head2 package=PACKAGE 33 | 34 | Specifies the output package/file (C creates a module 35 | named F under the output directory). 36 | 37 | If no explicit mapping is specified, protobuf packages are 38 | automatically mapped to Perl packages under the main package (e.g. protobuf 39 | package C will be mapped to C). 40 | 41 | =head2 map_package=PACKAGE,prefix=PREFIX[,OPTIONS] 42 | 43 | Equivalent to the C mapping of L. 44 | 45 | The C sub-option is mandatory. 46 | 47 | C is a comma-separated list, see L for 48 | available options. 49 | 50 | =head2 pb_prefix=PACKAGE,prefix=PREFIX[,OPTIONS] 51 | 52 | Equivalent to the C mapping of L. 53 | 54 | The C sub-option is mandatory. 55 | 56 | C is a comma-separated list, see L for 57 | available options. 58 | 59 | =head2 map_message=MESSAGE,[,OPTIONS] 60 | 61 | Equivalent to either the C or C 62 | mapping of L. 63 | 64 | Either C or C sub-option is mandatory. 65 | 66 | C is a comma-separated list, see L for 67 | available options. 68 | 69 | =head2 Mapping options 70 | 71 | The following options map one to one to the C key of 72 | L. When specified before the 73 | first mapping, they are applied to all mappings. 74 | 75 | Boolean options: C, C, C, 76 | C, C, C, C, 77 | C. When specified they set the option value 78 | to 1, when prefixed with C (e.g. C) they set the 79 | option value to 0. 80 | 81 | String options: C, C and C 82 | set the corresponding option to the specified value 83 | 84 | =head1 INSERTION POINTS 85 | 86 | Generated code uses insertion points to enable customization via 87 | C plugins. 88 | 89 | The 90 | L 91 | offers more details on what insertion points are and how they can be 92 | used. L and 93 | L source code is a simple 94 | example of a C plugin that uses insertion points. 95 | 96 | Currently available insertion points: 97 | 98 | =head2 after_pragmas 99 | 100 | Inserts code after the C at the top of the module. 101 | 102 | =head2 after_loading 103 | 104 | Inserts code after serialized descriptors have been loaded, but beforre mapping. 105 | 106 | =head2 after_mapping 107 | 108 | Inserts code after mapping options have been applied. 109 | 110 | =cut 111 | -------------------------------------------------------------------------------- /scripts/protoc-gen-perl-gpd-add-pragma: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use Google::ProtocolBuffers::Dynamic::ProtocPlugin 3 | help => 'podusage', 4 | run => 'Google::ProtocolBuffers::Dynamic::AddPragma'; 5 | 6 | exit 0; 7 | 8 | __END__ 9 | 10 | =head1 NAME 11 | 12 | protoc-gen-perl-gpd-add-pragma - protoc plugin demonstrating insertion points 13 | 14 | =head1 SYNOPSIS 15 | 16 | protoc --perl-gpd_out=: --perl-gpd-add-pragma_out=package=,pragma=Sub.StrictDecl: message.proto 17 | 18 | =head1 DESCRIPTION 19 | 20 | This C plugin is mostly a demonstration of insertion points, 21 | but can also be used to add additional pragmas as the top of the Perl 22 | module generated by C. 23 | 24 | To see how insertion points can be used, check the source code of 25 | L. 26 | 27 | =cut 28 | -------------------------------------------------------------------------------- /scripts/protoc/README: -------------------------------------------------------------------------------- 1 | Serialized descriptors for protoc plugin interface, used for 2 | bootstrapping the protoc plugin 3 | 4 | If needed, they can be regenerated with: 5 | 6 | $ cd /path/to/google/protobuf 7 | $ protoc -o descriptor.pb -Isrc src/google/protobuf/descriptor.proto 8 | $ protoc -o plugin.pb -Isrc src/google/protobuf/compiler/plugin.proto 9 | $ cd /path/to/google-protobuf-dynamic 10 | $ perl scripts/make_plugin_interface.pl -------------------------------------------------------------------------------- /scripts/protoc/descriptor.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbarbon/google-protobuf-dynamic/7ced3f1340d35c20c2ea29baef8ff19357ae7834/scripts/protoc/descriptor.pb -------------------------------------------------------------------------------- /scripts/protoc/plugin.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbarbon/google-protobuf-dynamic/7ced3f1340d35c20c2ea29baef8ff19357ae7834/scripts/protoc/plugin.pb -------------------------------------------------------------------------------- /src/descriptorloader.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_DESCRIPTORLOADER_INCLUDED 2 | #define _GPD_XS_DESCRIPTORLOADER_INCLUDED 3 | 4 | #include "sourcetree.h" 5 | 6 | #include 7 | 8 | namespace gpd { 9 | 10 | // alternative SourceTreeDescriptorDatabase for protobuf 3.6 or older 11 | class SourceTreeDescriptorDatabaseWithFallback : public google::protobuf::DescriptorDatabase { 12 | public: 13 | SourceTreeDescriptorDatabaseWithFallback(google::protobuf::compiler::SourceTree *source_tree, 14 | google::protobuf::DescriptorDatabase *fallback_database); 15 | 16 | // forwarded SourceTreeDescriptorDatabase methods 17 | void RecordErrorsTo(google::protobuf::compiler::MultiFileErrorCollector* error_collector) { 18 | database.RecordErrorsTo(error_collector); 19 | } 20 | 21 | google::protobuf::DescriptorPool::ErrorCollector* GetValidationErrorCollector() { 22 | return database.GetValidationErrorCollector(); 23 | } 24 | 25 | // DescriptorDatabase implementation 26 | bool FindFileByName(const std::string& filename, 27 | google::protobuf::FileDescriptorProto* output); 28 | 29 | bool FindFileContainingSymbol(const std::string& symbol_name, 30 | google::protobuf::FileDescriptorProto* output) { 31 | return false; 32 | } 33 | 34 | bool FindFileContainingExtension(const std::string& containing_type, 35 | int field_number, 36 | google::protobuf::FileDescriptorProto* output) { 37 | return false; 38 | } 39 | 40 | private: 41 | google::protobuf::compiler::SourceTree *source_tree; 42 | google::protobuf::compiler::SourceTreeDescriptorDatabase database; 43 | google::protobuf::DescriptorDatabase *fallback_database; 44 | }; 45 | 46 | // a reimplementation of Importer, with a different DescriptorPool 47 | class DescriptorLoader { 48 | class CollectMultiFileErrors : public google::protobuf::compiler::MultiFileErrorCollector { 49 | public: 50 | virtual void AddError(const std::string &filename, int line, int column, const std::string &message); 51 | virtual void AddWarning(const std::string &filename, int line, int column, const std::string &message); 52 | 53 | void maybe_croak(); 54 | 55 | private: 56 | std::string errors; 57 | }; 58 | 59 | public: 60 | DescriptorLoader(); 61 | ~DescriptorLoader(); 62 | 63 | const google::protobuf::FileDescriptor *load_proto(const std::string &filename); 64 | const std::vector load_serialized(const char *buffer, size_t length); 65 | 66 | inline const google::protobuf::DescriptorPool *pool() const { 67 | return &merged_pool; 68 | } 69 | 70 | void map_disk_path(const std::string &virtual_path, const std::string &disk_path) { 71 | disk_source_tree.MapPath(virtual_path, disk_path); 72 | } 73 | 74 | void add_memory_file(const std::string &file_name, const char *data, size_t length) { 75 | memory_source_tree.AddFile(file_name, data, length); 76 | } 77 | 78 | void maybe_croak() { 79 | multifile_error_collector.maybe_croak(); 80 | } 81 | 82 | private: 83 | CollectMultiFileErrors multifile_error_collector; 84 | OverlaySourceTree overlay_source_tree; 85 | MemorySourceTree memory_source_tree; 86 | google::protobuf::DescriptorPoolDatabase generated_database; 87 | google::protobuf::compiler::DiskSourceTree disk_source_tree; 88 | #if GOOGLE_PROTOBUF_VERSION >= 3007000 89 | google::protobuf::compiler::SourceTreeDescriptorDatabase source_database; 90 | #else 91 | SourceTreeDescriptorDatabaseWithFallback source_database; 92 | #endif 93 | google::protobuf::SimpleDescriptorDatabase binary_database; 94 | google::protobuf::MergedDescriptorDatabase merged_source_binary_database; 95 | google::protobuf::DescriptorPool merged_pool; 96 | }; 97 | 98 | } 99 | #endif 100 | -------------------------------------------------------------------------------- /src/fieldmap.cpp: -------------------------------------------------------------------------------- 1 | #include "fieldmap.h" 2 | 3 | #include 4 | 5 | using namespace std; 6 | 7 | namespace { 8 | vector find_packed_fields(vector &all_fields) { 9 | sort(all_fields.begin(), all_fields.end()); 10 | 11 | int last_included = -1; 12 | for (int i = 0, max = all_fields.size(); i < max; ++i) { 13 | if (all_fields[i] < 70) { 14 | last_included = i; 15 | continue; 16 | } 17 | // the load factor also counts the unused "0" entry 18 | int load_factor = (i + 1) * 100 / (all_fields[i] + 1); 19 | if (load_factor > 75) { 20 | last_included = i; 21 | } 22 | } 23 | 24 | all_fields.resize(last_included + 1); 25 | 26 | return all_fields; 27 | } 28 | } 29 | 30 | void gpd::FieldMapImpl::add(pTHX_ SV *name, unsigned int number, void *field) { 31 | PerlString key; 32 | 33 | key.fill(aTHX_ name); 34 | by_name[key] = field; 35 | by_number[number] = field; 36 | } 37 | 38 | void gpd::FieldMapImpl::optimize_lookup() { 39 | vector all_ids; 40 | 41 | for (typename NumberMap::const_iterator it = by_number.begin(), en = by_number.end(); it != en; ++it) 42 | all_ids.push_back(it->first); 43 | 44 | vector packed_ids = find_packed_fields(all_ids); 45 | 46 | if (packed_ids.size() > 0) { 47 | top_packed_field = packed_ids.back(); 48 | packed_by_number.resize(top_packed_field + 1); 49 | 50 | for (std::vector::const_iterator it = packed_ids.begin(), en = packed_ids.end(); it != en; ++it) { 51 | packed_by_number[*it] = by_number[*it]; 52 | by_number.erase(*it); 53 | } 54 | } 55 | } 56 | 57 | void *gpd::FieldMapImpl::find_by_name(pTHX_ SV *name) const { 58 | PerlString key; 59 | 60 | key.fill(aTHX_ name); 61 | return find_by_name(key); 62 | } 63 | 64 | void *gpd::FieldMapImpl::find_by_name(pTHX_ HE *he) const { 65 | PerlString key; 66 | 67 | key.fill(aTHX_ he); 68 | return find_by_name(key); 69 | } 70 | 71 | void *gpd::FieldMapImpl::find_by_name(pTHX_ const char *name, STRLEN namelen) const { 72 | PerlString key; 73 | 74 | key.fill(aTHX_ name, namelen); 75 | return find_by_name(key); 76 | } 77 | -------------------------------------------------------------------------------- /src/fieldmap.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_FIELDMAP_INCLUDED 2 | #define _GPD_XS_FIELDMAP_INCLUDED 3 | 4 | #include "unordered_map.h" 5 | 6 | #include "EXTERN.h" 7 | #include "perl.h" 8 | #include "perl_unpollute.h" 9 | 10 | #include 11 | 12 | namespace gpd { 13 | 14 | class PerlString { 15 | public: 16 | const char *buffer; 17 | STRLEN len; 18 | U32 hash; 19 | 20 | bool operator==(const PerlString &other) const { 21 | return buffer == other.buffer || 22 | (len == other.len && memcmp(buffer, other.buffer, len) == 0); 23 | } 24 | 25 | void fill(pTHX_ const char *buffer_, STRLEN len_) { 26 | buffer = buffer_; 27 | len = len_; 28 | PERL_HASH(hash, buffer, len); 29 | } 30 | 31 | // sv needs to be a shared SV 32 | void fill(pTHX_ SV *sv) { 33 | buffer = SvPV(sv, len); 34 | hash = SvSHARED_HASH(sv); 35 | } 36 | 37 | void fill(pTHX_ HE *he) { 38 | if (HeKLEN(he) == HEf_SVKEY) { 39 | buffer = SvPV(HeKEY_sv(he), len); 40 | } else { 41 | buffer = HeKEY(he); 42 | // if the key is marked as UTF-8 and contains non-ASCII characters, 43 | // it will not be there anyway in the lookup 44 | len = abs(HeKLEN(he)); 45 | } 46 | hash = HeHASH(he); 47 | if (UNLIKELY(!hash)) { 48 | PERL_HASH(hash, buffer, len); 49 | } 50 | } 51 | }; 52 | 53 | } 54 | 55 | UMS_NS_START 56 | 57 | template<> 58 | struct hash { 59 | size_t operator()(const gpd::PerlString &s) const { 60 | return s.hash; 61 | } 62 | }; 63 | 64 | UMS_NS_END 65 | 66 | namespace gpd { 67 | 68 | template class FieldMap; 69 | 70 | class FieldMapImpl { 71 | template friend class FieldMap; 72 | 73 | typedef UMS_NS::unordered_map NameMap; 74 | typedef UMS_NS::unordered_map NumberMap; 75 | typedef std::vector NumberVector; 76 | 77 | FieldMapImpl() { 78 | top_packed_field = 0; 79 | } 80 | 81 | void *find_by_name(pTHX_ SV *name) const; 82 | void *find_by_name(pTHX_ HE *he) const; 83 | void *find_by_name(pTHX_ const char *name, STRLEN namelen) const; 84 | 85 | void *find_by_name(const PerlString &key) const { 86 | NameMap::const_iterator it = by_name.find(key); 87 | 88 | return it == by_name.end() ? NULL : it->second; 89 | } 90 | 91 | void *find_by_number(unsigned int number) const { 92 | if (number <= top_packed_field) { 93 | return packed_by_number[number]; 94 | } else { 95 | typename NumberMap::const_iterator it = by_number.find(number); 96 | 97 | return it != by_number.end() ? it->second : NULL; 98 | } 99 | } 100 | 101 | void add(pTHX_ SV *name, unsigned int number, void *field); 102 | 103 | void optimize_lookup(); 104 | 105 | unsigned int top_packed_field; 106 | NameMap by_name; 107 | NumberMap by_number; 108 | NumberVector packed_by_number; 109 | }; 110 | 111 | template 112 | class FieldMap { 113 | public: 114 | void add(pTHX_ SV *name, unsigned int number, T *field) { 115 | impl.add(aTHX_ name, number, field); 116 | } 117 | 118 | void optimize_lookup() { 119 | impl.optimize_lookup(); 120 | } 121 | 122 | // name needs to be a shared SV 123 | T *find_by_name(pTHX_ SV *name) const { 124 | return static_cast(impl.find_by_name(aTHX_ name)); 125 | } 126 | 127 | T *find_by_name(pTHX_ HE *he) const { 128 | return static_cast(impl.find_by_name(aTHX_ he)); 129 | } 130 | 131 | T *find_by_name(pTHX_ const char *name, STRLEN namelen) const { 132 | return static_cast(impl.find_by_name(aTHX_ name, namelen)); 133 | } 134 | 135 | T *find_by_number(unsigned int number) const { 136 | return static_cast(impl.find_by_number(number)); 137 | } 138 | 139 | private: 140 | FieldMapImpl impl; 141 | }; 142 | 143 | } 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /src/mapper_context.cpp: -------------------------------------------------------------------------------- 1 | #include "mapper_context.h" 2 | 3 | using namespace gpd; 4 | using namespace std; 5 | 6 | void MapperContext::clear() { 7 | nextId = 1; 8 | next_level = level_storage.begin(); 9 | levels.clear(); 10 | } 11 | 12 | MapperContext::Item &MapperContext::push_level(Kind kind) { 13 | // implemented this way to reuse memory as much as possible 14 | if (next_level == level_storage.end()) { 15 | nextId++; 16 | level_storage.push_back(Item(kind, nextId, &nextId)); 17 | return level_storage.back(); 18 | } 19 | 20 | next_level->kind = kind; 21 | next_level->id = nextId++; 22 | 23 | return *(next_level++); 24 | } 25 | 26 | void MapperContext::fill_context(const ExternalItem * const **items, int *size) { 27 | levels.clear(); 28 | 29 | for (LevelStorage::iterator it = level_storage.begin(); it != next_level; ++it) { 30 | levels.push_back(&*it); 31 | } 32 | 33 | *items = &levels[0]; 34 | *size = levels.size(); 35 | } 36 | -------------------------------------------------------------------------------- /src/mapper_context.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_MAPPER_CONTEXT_INCLUDED 2 | #define _GPD_XS_MAPPER_CONTEXT_INCLUDED 3 | 4 | #include "EXTERN.h" 5 | #include "perl.h" 6 | #include "ppport.h" 7 | #include "perl_unpollute.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace gpd { 13 | 14 | class MapperContext { 15 | public: 16 | enum Kind { 17 | Array = 1, 18 | Hash = 2, 19 | Message = 3, 20 | }; 21 | 22 | struct ArrayItem { 23 | AV *array; 24 | unsigned int index; 25 | }; 26 | 27 | struct HashItem { 28 | HV *hash; 29 | SV *svkey; 30 | const char *keybuf; 31 | STRLEN keylen; 32 | }; 33 | 34 | struct ExternalItem { 35 | int kind; 36 | unsigned int id; 37 | union { 38 | ArrayItem array_item; 39 | HashItem hash_item; 40 | }; 41 | 42 | ExternalItem(Kind _kind, unsigned int _id) : 43 | kind(_kind), id(_id) { 44 | } 45 | }; 46 | 47 | struct Item : public ExternalItem { 48 | unsigned int *nextId; 49 | 50 | Item(Kind _kind, unsigned int _id, unsigned int *_nextId) : 51 | ExternalItem(_kind, _id), nextId(_nextId) { 52 | } 53 | 54 | void set_hash_key(SV *key) { 55 | hash_item.svkey = key; 56 | id = (*nextId)++; 57 | } 58 | 59 | void set_hash_key(const char *buffer, STRLEN len) { 60 | hash_item.keybuf = buffer; 61 | hash_item.keylen = len; 62 | id = (*nextId)++; 63 | } 64 | 65 | void set_hash_key(HE *entry) { 66 | if (HeKLEN(entry) == HEf_SVKEY) { 67 | set_hash_key(HeKEY_sv(entry)); 68 | } else { 69 | set_hash_key(HeKEY(entry), HeKLEN(entry)); 70 | } 71 | } 72 | 73 | void set_array_index(int index) { 74 | array_item.index = index; 75 | id = (*nextId)++; 76 | } 77 | }; 78 | 79 | void clear(); 80 | 81 | Item &push_level(AV *array) { 82 | Item &level = push_level(Array); 83 | 84 | level.array_item.array = array; 85 | 86 | return level; 87 | } 88 | 89 | Item &push_level(HV *hash, Kind kind) { 90 | return push_hash_level(hash, kind); 91 | } 92 | 93 | Item &push_level(SV *transformed, Kind kind) { 94 | return push_hash_level(NULL, kind); 95 | } 96 | 97 | void pop_level() { next_level--; } 98 | 99 | void fill_context(const ExternalItem * const **items, int *size); 100 | 101 | private: 102 | Item &push_level(Kind kind); 103 | 104 | Item &push_hash_level(HV *hash, Kind kind) { 105 | Item &level = push_level(kind); 106 | 107 | level.hash_item.hash = hash; 108 | level.hash_item.svkey = NULL; 109 | level.hash_item.keybuf = NULL; 110 | 111 | return level; 112 | } 113 | 114 | typedef std::list LevelStorage; 115 | 116 | unsigned int nextId; 117 | LevelStorage level_storage; 118 | LevelStorage::iterator next_level; 119 | std::vector levels; 120 | }; 121 | 122 | } 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /src/pb/gpb_mapping.cpp: -------------------------------------------------------------------------------- 1 | #include "pb/gpb_mapping.h" 2 | #include "pb/decoder.h" 3 | 4 | #include 5 | 6 | namespace gpdp = gpd::pb; 7 | namespace gp = google::protobuf; 8 | using namespace std; 9 | 10 | namespace { 11 | void populate_field(gpdp::DescriptorSet *descriptor_set, gpdp::Descriptor *gpd_descriptor, const gp::FieldDescriptor *field, const gp::DescriptorPool *descriptor_pool) { 12 | bool repeated = field->label() == gp::FieldDescriptor::LABEL_REPEATED; 13 | 14 | if (field->type() == gp::FieldDescriptor::TYPE_MESSAGE) { 15 | const gpdp::Descriptor *field_message = map_pb_descriptor(descriptor_set, field->message_type(), descriptor_pool); 16 | 17 | gpd_descriptor->add_field(field->number(), repeated, field_message); 18 | } else { 19 | gpd_descriptor->add_field(field->number(), gpdp::FieldType(field->type()), repeated); 20 | } 21 | } 22 | 23 | void populate_descriptor(gpdp::DescriptorSet *descriptor_set, gpdp::Descriptor *gpd_descriptor, const gp::Descriptor *descriptor, const gp::DescriptorPool *descriptor_pool) { 24 | for (int i = 0, max = descriptor->field_count(); i < max; ++i) { 25 | const gp::FieldDescriptor *field = descriptor->field(i); 26 | 27 | populate_field(descriptor_set, gpd_descriptor, field, descriptor_pool); 28 | } 29 | 30 | vector extensions; 31 | descriptor_pool->FindAllExtensions(descriptor, &extensions); 32 | for (vector::const_iterator it = extensions.begin(), en = extensions.end(); it != en; ++it) { 33 | populate_field(descriptor_set, gpd_descriptor, *it, descriptor_pool); 34 | } 35 | } 36 | } 37 | 38 | const gpdp::Descriptor *gpdp::map_pb_descriptor(gpdp::DescriptorSet *descriptor_set, const gp::Descriptor *descriptor, const gp::DescriptorPool *descriptor_pool) { 39 | string message_name = descriptor->full_name(); 40 | const gpdp::Descriptor *gpd_descriptor = descriptor_set->get_descriptor(message_name); 41 | if (!gpd_descriptor) { 42 | gpdp::Descriptor *new_gpd_descriptor = new gpdp::Descriptor(); 43 | descriptor_set->add_descriptor(message_name, new_gpd_descriptor); 44 | 45 | populate_descriptor(descriptor_set, new_gpd_descriptor, descriptor, descriptor_pool); 46 | gpd_descriptor = new_gpd_descriptor; 47 | } 48 | 49 | return gpd_descriptor; 50 | } 51 | -------------------------------------------------------------------------------- /src/pb/gpb_mapping.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_PB_GPB_MAPPING_INCLUDED 2 | #define _GPD_XS_PB_GPB_MAPPING_INCLUDED 3 | 4 | namespace google { 5 | namespace protobuf { 6 | class Descriptor; 7 | class DescriptorPool; 8 | } 9 | } 10 | 11 | namespace gpd { 12 | namespace pb { 13 | 14 | class DescriptorSet; 15 | class Descriptor; 16 | 17 | const gpd::pb::Descriptor *map_pb_descriptor(gpd::pb::DescriptorSet *descriptor_set, const google::protobuf::Descriptor *descriptor, const google::protobuf::DescriptorPool *descriptor_pool); 18 | 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/perl_unpollute.h: -------------------------------------------------------------------------------- 1 | #undef New 2 | #undef Move 3 | #undef do_open 4 | #undef do_close 5 | #undef seed 6 | -------------------------------------------------------------------------------- /src/ref.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_REF_INCLUDED 2 | #define _GPD_XS_REF_INCLUDED 3 | 4 | namespace gpd { 5 | 6 | class Refcounted { 7 | public: 8 | void ref() const; 9 | void unref() const; 10 | 11 | protected: 12 | Refcounted(); 13 | virtual ~Refcounted(); 14 | 15 | private: 16 | int count; 17 | }; 18 | 19 | inline Refcounted::Refcounted() : count(1) { } 20 | inline Refcounted::~Refcounted() { } 21 | 22 | inline void Refcounted::ref() const { 23 | Refcounted *self = const_cast(this); 24 | 25 | ++self->count; 26 | } 27 | 28 | inline void Refcounted::unref() const { 29 | Refcounted *self = const_cast(this); 30 | 31 | if (!--self->count) 32 | delete self; 33 | } 34 | 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /src/servicedef.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_DYNAMIC_SERVICE_DESC 2 | #define _GPD_XS_DYNAMIC_SERVICE_DESC 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // uPB does not have service_def/method_def yet, and adding it here 10 | // is much quicker than implementing it in uPB 11 | namespace gpd { 12 | struct ServiceDef; 13 | 14 | struct MethodDef { 15 | MethodDef(const std::string &name, 16 | const std::string &full_name, 17 | const ServiceDef *containing_service, 18 | const upb::MessageDef *input_type, 19 | const upb::MessageDef *output_type, 20 | bool client_streaming, 21 | bool server_streaming) : 22 | _name(name), 23 | _full_name(full_name), 24 | _containing_service(containing_service), 25 | _input_type(input_type), 26 | _output_type(output_type), 27 | _client_streaming(client_streaming), 28 | _server_streaming(server_streaming) 29 | { } 30 | 31 | const std::string &name() const { return _name; } 32 | const std::string &full_name() const { return _full_name; } 33 | const ServiceDef *containing_service() const { return _containing_service; } 34 | const upb::MessageDef *input_type() const { return _input_type; } 35 | const upb::MessageDef *output_type() const { return _output_type; } 36 | bool client_streaming() const { return _client_streaming; } 37 | bool server_streaming() const { return _server_streaming; } 38 | 39 | private: 40 | std::string _name; 41 | std::string _full_name; 42 | 43 | const ServiceDef *_containing_service; 44 | const upb::MessageDef *_input_type; 45 | const upb::MessageDef *_output_type; 46 | 47 | bool _client_streaming, _server_streaming; 48 | }; 49 | 50 | struct ServiceDef { 51 | ServiceDef(const std::string &full_name) : 52 | _full_name(full_name) 53 | { } 54 | 55 | void add_method(const MethodDef &method) { 56 | _methods.push_back(method); 57 | } 58 | 59 | const std::string &full_name() const { return _full_name; } 60 | const std::vector &methods() { return _methods; } 61 | 62 | private: 63 | std::string _full_name; 64 | std::vector _methods; 65 | }; 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/sourcetree.cpp: -------------------------------------------------------------------------------- 1 | #include "sourcetree.h" 2 | 3 | #include 4 | 5 | using namespace gpd; 6 | using namespace std; 7 | using namespace UMS_NS; 8 | using namespace google::protobuf; 9 | using namespace google::protobuf::io; 10 | using namespace google::protobuf::compiler; 11 | 12 | OverlaySourceTree::OverlaySourceTree(MemorySourceTree *_memory, SourceTree *_fallback) { 13 | memory = _memory; 14 | fallback = _fallback; 15 | } 16 | 17 | ZeroCopyInputStream *OverlaySourceTree::Open(const string &filename) { 18 | ZeroCopyInputStream *mem = memory->Open(filename); 19 | if (mem) 20 | return mem; 21 | 22 | return fallback->Open(filename); 23 | } 24 | 25 | string OverlaySourceTree::GetLastErrorMessage() { 26 | return fallback->GetLastErrorMessage(); 27 | } 28 | 29 | void MemorySourceTree::AddFile(const string &filename, const char *data, size_t len) { 30 | sources[filename].assign(data, len); 31 | } 32 | 33 | ZeroCopyInputStream *MemorySourceTree::Open(const string &filename) { 34 | unordered_map::iterator item = sources.find(filename); 35 | if (item == sources.end()) 36 | return NULL; 37 | 38 | return new ArrayInputStream(item->second.data(), item->second.size()); 39 | } 40 | -------------------------------------------------------------------------------- /src/sourcetree.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_SOURCETREE_INCLUDED 2 | #define _GPD_XS_SOURCETREE_INCLUDED 3 | 4 | #include 5 | 6 | #include "unordered_map.h" 7 | 8 | namespace gpd { 9 | 10 | class MemorySourceTree; 11 | 12 | class OverlaySourceTree : public google::protobuf::compiler::SourceTree { 13 | public: 14 | OverlaySourceTree(MemorySourceTree *memory, google::protobuf::compiler::SourceTree *fallback); 15 | 16 | virtual google::protobuf::io::ZeroCopyInputStream *Open(const std::string &filename); 17 | virtual std::string GetLastErrorMessage(); 18 | 19 | private: 20 | MemorySourceTree *memory; 21 | google::protobuf::compiler::SourceTree *fallback; 22 | }; 23 | 24 | class MemorySourceTree : public google::protobuf::compiler::SourceTree { 25 | public: 26 | void AddFile(const std::string &filename, const char *data, size_t len); 27 | virtual google::protobuf::io::ZeroCopyInputStream *Open(const std::string &filename); 28 | 29 | private: 30 | UMS_NS::unordered_map sources; 31 | }; 32 | 33 | } 34 | #endif 35 | -------------------------------------------------------------------------------- /src/thx_member.h: -------------------------------------------------------------------------------- 1 | #ifndef _DEVEL_STATPROFILER_THX_MEMBER 2 | #define _DEVEL_STATPROFILER_THX_MEMBER 3 | 4 | // Defines macros for use in classes that keep a Perl threading 5 | // context around. 6 | // 7 | // Example: 8 | // class Foo { 9 | // public: 10 | // Foo(pTHX) { 11 | // SET_THX_MEMBER 12 | // } 13 | // void bar() { 14 | // // use aTHX here implicitly or explicitly 15 | // } 16 | // private: 17 | // DECL_THX_MEMBER 18 | // } 19 | 20 | #include 21 | #include 22 | #include "perl_unpollute.h" 23 | 24 | #ifdef MULTIPLICITY 25 | # define DECL_THX_MEMBER tTHX my_perl; 26 | # define SET_THX_MEMBER this->my_perl = aTHX; 27 | #else 28 | # define DECL_THX_MEMBER 29 | # define SET_THX_MEMBER 30 | #endif 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/unordered_map.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_UNORDERED_MAP_INCLUDED 2 | #define _GPD_XS_UNORDERED_MAP_INCLUDED 3 | 4 | #if defined(__clang__) || (defined(__GNUC__) && __cplusplus >= 201103L) 5 | 6 | #include 7 | #include 8 | 9 | #define UMS_NS std 10 | #define UMS_NS_START namespace std { 11 | #define UMS_NS_END } 12 | 13 | #else 14 | 15 | #include 16 | #include 17 | 18 | #define UMS_NS std::tr1 19 | #define UMS_NS_START namespace std { namespace tr1 { 20 | #define UMS_NS_END } } 21 | 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/upb/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2009-2011, Google Inc. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of Google Inc. nor the names of any other 14 | contributors may be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED 18 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 20 | EVENT SHALL GOOGLE INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 23 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 24 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /src/vectorsink.h: -------------------------------------------------------------------------------- 1 | #ifndef _GPD_XS_SINK_INCLUDED 2 | #define _GPD_XS_SINK_INCLUDED 3 | 4 | #include 5 | 6 | namespace gpd { 7 | 8 | // this is pretty much the same as upb_bufsink in uPB, but it does not 9 | // rely on an environment for memory allocation (so the buffer is 10 | // retained) 11 | class VectorSink { 12 | public: 13 | VectorSink() { 14 | SetHandler(&handler_); 15 | input_.Reset(&handler_, this); 16 | 17 | length = 0; 18 | capacity = 100; 19 | buffer = (char *) malloc(capacity); 20 | } 21 | 22 | upb::BytesSink* input() { return &input_; } 23 | 24 | const char *data() { 25 | return buffer; 26 | } 27 | 28 | size_t size() { 29 | return length; 30 | } 31 | 32 | private: 33 | static void SetHandler(upb::BytesHandler* handler) { 34 | upb_byteshandler_setstartstr(handler, &VectorSink::StartString, NULL); 35 | upb_byteshandler_setstring(handler, &VectorSink::StringBuf, NULL); 36 | } 37 | 38 | static void* StartString(void *c, const void *hd, size_t size) { 39 | VectorSink *sink = static_cast(c); 40 | sink->length = 0; 41 | return c; 42 | } 43 | 44 | static size_t StringBuf(void* c, const void* hd, const char* buf, size_t n, 45 | const upb::BufferHandle* h) { 46 | VectorSink *sink = static_cast(c); 47 | 48 | while (sink->length + n > sink->capacity) { 49 | sink->capacity *= 2; 50 | sink->buffer = (char *) realloc(sink->buffer, sink->capacity); 51 | } 52 | 53 | memcpy(sink->buffer + sink->length, buf, n); 54 | sink->length += n; 55 | 56 | return n; 57 | } 58 | 59 | upb::BytesHandler handler_; 60 | upb::BytesSink input_; 61 | 62 | char *buffer; 63 | size_t length, capacity; 64 | }; 65 | 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /t/001_load.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Test::More tests => 1; 7 | 8 | use_ok('Google::ProtocolBuffers::Dynamic'); 9 | -------------------------------------------------------------------------------- /t/010_basic.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("person.proto"); 5 | $d->map_message("test.Person", "Person"); 6 | $d->map_message("test.PersonArray", "PersonArray"); 7 | $d->resolve_references(); 8 | 9 | my $p_bytes = "\x0a\x03foo\x10\x1f"; 10 | my $p = Person->decode($p_bytes); 11 | my $pj = Person->decode_json('{"id":31,"name":"foo"}'); 12 | 13 | decode_eq_or_diff('Person', $p_bytes, Person->new({ id => 31, name => 'foo' })); 14 | eq_or_diff($pj, Person->new({ id => 31, name => 'foo' })); 15 | 16 | my $pa_bytes = "\x0a\x07\x0a\x03foo\x10\x1f" . 17 | "\x0a\x06\x0a\x02ba\x10\x20"; 18 | my $pa = PersonArray->decode($pa_bytes); 19 | my $paj = PersonArray->decode_json('{"persons":[{"name":"foo","id":31},{"name":"ba","id":32}]}'); 20 | 21 | decode_eq_or_diff('PersonArray', $pa_bytes, PersonArray->new({ 22 | persons => [ 23 | Person->new({ id => 31, name => 'foo' }), 24 | Person->new({ id => 32, name => 'ba' }), 25 | ], 26 | })); 27 | eq_or_diff($paj, PersonArray->new({ 28 | persons => [ 29 | Person->new({ id => 31, name => 'foo' }), 30 | Person->new({ id => 32, name => 'ba' }), 31 | ], 32 | })); 33 | 34 | eq_or_diff(Person->encode($p), "\x0a\x03foo\x10\x1f"); 35 | eq_or_diff(PersonArray->encode($pa), "\x0a\x07\x0a\x03foo\x10\x1f" . 36 | "\x0a\x06\x0a\x02ba\x10\x20"); 37 | 38 | eq_or_diff(Person->encode_json($p), '{"name":"foo","id":31}'); 39 | eq_or_diff(PersonArray->encode_json($pa), '{"persons":[{"name":"foo","id":31},{"name":"ba","id":32}]}'); 40 | 41 | decode_throws_ok( 42 | 'Person', "\x0a\x02", 43 | qr/Deserialization failed: Unexpected EOF inside delimited string/, 44 | qr/Deserialization failed: Truncated length-delimited field/, 45 | ); 46 | 47 | done_testing(); 48 | -------------------------------------------------------------------------------- /t/020_primitives.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("scalar.proto"); 5 | $d->map_message("test.Basic", "Basic", { explicit_defaults => 1 }); 6 | $d->map_message("test.Default", "Default", { explicit_defaults => 1 }); 7 | $d->resolve_references(); 8 | 9 | my %values = ( 10 | double_f => [0.125, "\x09\x00\x00\x00\x00\x00\x00\xc0?"], 11 | float_f => [0.125, "\x15\x00\x00\x00\x3e"], 12 | int32_f => [2147483647, "\x18\xff\xff\xff\xff\x07"], 13 | int64_f => [maybe_bigint('4294967296'), "\x20\x80\x80\x80\x80\x10"], 14 | uint32_f => [4294967295, "\x28\xff\xff\xff\xff\x0f"], 15 | uint64_f => [maybe_bigint('1099511627776'), "\x30\x80\x80\x80\x80\x80\x20"], 16 | bool_f => [1, "\x38\x01"], 17 | string_f => ["\x{101f}", "\x42\x03\xe1\x80\x9f"], 18 | bytes_f => ["\xe1\x80\x9f", "\x4a\x03\xe1\x80\x9f"], 19 | enum_f => [2, "\x50\x02"], 20 | sint32_f => [-6, "\x58\x0b"], 21 | sint64_f => [-5, "\x60\x09"], 22 | fixed32_f => [3, "\x6d\x03\x00\x00\x00"], 23 | sfixed32_f => [-3, "\x75\xfd\xff\xff\xff"], 24 | fixed64_f => [4, "\x79\x04\x00\x00\x00\x00\x00\x00\x00"], 25 | sfixed64_f => [-4, "\x81\x01\xfc\xff\xff\xff\xff\xff\xff\xff"], 26 | ); 27 | 28 | my %default_defaults = ( 29 | double_f => '0', 30 | float_f => '0', 31 | int32_f => 0, 32 | int64_f => 0, 33 | uint32_f => 0, 34 | uint64_f => 0, 35 | bool_f => '', 36 | string_f => '', 37 | bytes_f => '', 38 | enum_f => 1, 39 | fixed32_f => 0, 40 | fixed64_f => 0, 41 | sfixed32_f => 0, 42 | sfixed64_f => 0, 43 | sint32_f => 0, 44 | sint64_f => 0, 45 | ); 46 | bless \%default_defaults, 'Basic'; 47 | 48 | my %test_defaults = ( 49 | double_f => 1.0, 50 | float_f => 2.0, 51 | int32_f => 3, 52 | int64_f => 4, 53 | uint32_f => 5, 54 | uint64_f => 6, 55 | bool_f => 1, 56 | string_f => "a string", 57 | bytes_f => "some bytes", 58 | enum_f => 3, 59 | fixed32_f => 9, 60 | fixed64_f => 11, 61 | sfixed32_f => -10, 62 | sfixed64_f => -12, 63 | sint32_f => -7, 64 | sint64_f => -8, 65 | ); 66 | bless \%test_defaults, 'Default'; 67 | 68 | for my $field (sort keys %values) { 69 | my ($value, $encoded) = @{$values{$field}}; 70 | my $bytes = Basic->encode({ $field => $value }); 71 | 72 | my $tied = { $field => undef }; 73 | tie_scalar($tied->{$field}, $value); 74 | my $tied_bytes = Basic->encode($tied); 75 | 76 | eq_or_diff($bytes, $encoded, 77 | "$field - encoded value"); 78 | eq_or_diff($tied_bytes, $encoded, 79 | "$field - encoded tied value"); 80 | decode_eq_or_diff('Basic', $bytes, Basic->new({ %default_defaults, $field => $value }), 81 | "$field - round trip"); 82 | eq_or_diff(tied_fetch_count($tied), { $field => 1 }, "$field - tied fetch count"); 83 | } 84 | 85 | eq_or_diff(Basic->encode({bool_f => ''}), "", "bool false"); 86 | eq_or_diff(Default->encode({bool_f => ''}), "\x38\x00", "bool false"); 87 | decode_eq_or_diff('Basic', '',, \%default_defaults, "implicit defaults"); 88 | decode_eq_or_diff('Default', '', \%test_defaults, "explicit defaults"); 89 | 90 | done_testing(); 91 | -------------------------------------------------------------------------------- /t/021_bool.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $encoded_true = "\x38\x01"; 4 | my $encoded_false = "\x38\x00"; 5 | my $encoded_default = ""; 6 | my $encoded_outer = "\x42\x02" . $encoded_true; 7 | 8 | { 9 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 10 | $d->load_file("bool.proto"); 11 | $d->map_message("test.Bool", "PerlBool", { explicit_defaults => 1, boolean_values => 'perl', decode_blessed => 0 }); 12 | $d->resolve_references(); 13 | 14 | decode_eq_or_diff('PerlBool', $encoded_true, { bool_f => 1 }); 15 | decode_eq_or_diff('PerlBool', $encoded_false, { bool_f => '' }); 16 | decode_eq_or_diff('PerlBool', $encoded_default, { bool_f => '' }); 17 | } 18 | 19 | { 20 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 21 | $d->load_file("bool.proto"); 22 | $d->map_message("test.Bool", "NumericBool", { explicit_defaults => 1, boolean_values => 'numeric', decode_blessed => 0 }); 23 | $d->resolve_references(); 24 | 25 | decode_eq_or_diff('NumericBool', $encoded_true, { bool_f => 1 }); 26 | decode_eq_or_diff('NumericBool', $encoded_false, { bool_f => 0 }); 27 | decode_eq_or_diff('NumericBool', $encoded_default, { bool_f => 0 }); 28 | } 29 | 30 | SKIP: { 31 | skip 'JSON module not installed' unless eval { require JSON; 1 }; 32 | 33 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 34 | $d->load_file("bool.proto"); 35 | $d->map_message("test.Bool", "JSONBool", { explicit_defaults => 1, boolean_values => 'json', decode_blessed => 0 }); 36 | $d->map_message("test.OuterBool", "JSONOuterBool", { decode_blessed => 0 }); 37 | $d->resolve_references(); 38 | 39 | decode_eq_or_diff('JSONBool', $encoded_true, { bool_f => JSON::true() }); 40 | decode_eq_or_diff('JSONBool', $encoded_false, { bool_f => JSON::false() }); 41 | decode_eq_or_diff('JSONBool', $encoded_default, { bool_f => JSON::false() }); 42 | decode_eq_or_diff('JSONOuterBool', $encoded_outer, { bool => { bool_f => JSON::true() } }, 'correct mapper object is used during bool decoding'); 43 | } 44 | 45 | { 46 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 47 | $d->load_file("bool.proto"); 48 | 49 | throws_ok( 50 | sub { $d->map_message("test.Bool", "OopsBool", { boolean_values => 'nope' }) }, 51 | qr/Invalid value 'nope' for 'boolean_values' option/ 52 | ); 53 | } 54 | 55 | done_testing(); 56 | -------------------------------------------------------------------------------- /t/025_bigints.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("bigint.proto"); 5 | $d->map_message("test.BigInts", "BigInts", { use_bigints => 1 }); 6 | $d->resolve_references(); 7 | 8 | { 9 | my $encoded = "\x08\xff\xff\xff\xff\x7f\x10\xff\xff\xff\xff\x7f"; 10 | my $decoded = BigInts->new({ 11 | int64_f => Math::BigInt->new('0x7ffffffff'), 12 | uint64_f => Math::BigInt->new('0x7ffffffff'),, 13 | }); 14 | 15 | decode_eq_or_diff('BigInts', $encoded, $decoded); 16 | eq_or_diff(BigInts->encode($decoded), $encoded); 17 | } 18 | 19 | { 20 | my $encoded = "\x08\xff\xff\xff\x7f\x10\xff\xff\xff\x7f"; 21 | my $decoded = BigInts->new({ 22 | int64_f => 0xfffffff, 23 | uint64_f => 0xfffffff, 24 | }); 25 | 26 | decode_eq_or_diff('BigInts', $encoded, $decoded); 27 | eq_or_diff(BigInts->encode($decoded), $encoded); 28 | } 29 | 30 | done_testing(); 31 | -------------------------------------------------------------------------------- /t/030_repeated.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("repeated.proto"); 5 | $d->map_message("test.Repeated", "Repeated"); 6 | $d->map_message("test.Packed", "Packed"); 7 | $d->resolve_references(); 8 | 9 | my %values = ( 10 | double_f => [[0.125, 0.5, 0.0], "\x09\x00\x00\x00\x00\x00\x00\xc0?\x09\x00\x00\x00\x00\x00\x00\xe0?\x09\x00\x00\x00\x00\x00\x00\x00\x00"], 11 | float_f => [[0.125, 0.5, 0.0], "\x15\x00\x00\x00\x3e\x15\x00\x00\x00\x3f\x15\x00\x00\x00\x00"], 12 | int32_f => [[2147483647, 1, 0], "\x18\xff\xff\xff\xff\x07\x18\x01\x18\x00"], 13 | int64_f => [[maybe_bigint('4294967296'), 1, 0], "\x20\x80\x80\x80\x80\x10\x20\x01\x20\x00"], 14 | uint32_f => [[4294967295, 1, 0], "\x28\xff\xff\xff\xff\x0f\x28\x01\x28\x00"], 15 | uint64_f => [[maybe_bigint('1099511627776'), 1, 0], "\x30\x80\x80\x80\x80\x80\x20\x30\x01\x30\x00"], 16 | bool_f => [[1, ''], "\x38\x01\x38\x00"], 17 | string_f => [["\x{101f}", "\x{101e}", ""], "\x42\x03\xe1\x80\x9f\x42\x03\xe1\x80\x9e\x42\x00"], 18 | bytes_f => [["\xe1\x80\x9f", "\xe1\x80\x9e", ""], "\x4a\x03\xe1\x80\x9f\x4a\x03\xe1\x80\x9e\x4a\x00"], 19 | enum_f => [[2, 3], "\x50\x02\x50\x03"], 20 | ); 21 | 22 | my %packed_values = ( 23 | double_f => [[0.125, 0.5], "\x0a\x10\x00\x00\x00\x00\x00\x00\xc0?\x00\x00\x00\x00\x00\x00\xe0?"], 24 | float_f => [[0.125, 0.5], "\x12\x08\x00\x00\x00\x3e\x00\x00\x00\x3f"], 25 | int32_f => [[2147483647, 1], "\x1a\x06\xff\xff\xff\xff\x07\x01"], 26 | int64_f => [[maybe_bigint('4294967296'), 1], "\x22\x06\x80\x80\x80\x80\x10\x01"], 27 | uint32_f => [[4294967295, 1], "\x2a\x06\xff\xff\xff\xff\x0f\x01"], 28 | uint64_f => [[maybe_bigint('1099511627776'), 1], "\x32\x07\x80\x80\x80\x80\x80\x20\x01"], 29 | bool_f => [[1, ''], "\x3a\x02\x01\x00"], 30 | enum_f => [[2, 3], "\x52\x02\x02\x03"], 31 | ); 32 | 33 | for my $field (sort keys %values) { 34 | my ($values, $encoded) = @{$values{$field}}; 35 | my $bytes = Repeated->encode({ $field => $values }); 36 | 37 | my $array = [(undef) x @$values]; 38 | my $tied = { $field => [] }; 39 | tie_scalar($array->[$_], $values->[$_]) for 0 .. $#$array; 40 | tie_array($tied->{$field}, $array); 41 | my $tied_bytes = Repeated->encode($tied); 42 | 43 | eq_or_diff($bytes, $encoded, 44 | "$field - encoded value"); 45 | eq_or_diff($tied_bytes, $encoded, 46 | "$field - encoded value with tied elements"); 47 | decode_eq_or_diff('Repeated', $bytes, Repeated->new({ $field => $values }), 48 | "$field - round trip"); 49 | eq_or_diff(tied_fetch_count($tied), { $field => { 50 | count => scalar @$values, 51 | inner => [(1) x scalar @$values], 52 | } }, "$field - tied fetch count"); 53 | } 54 | 55 | for my $field (sort keys %packed_values) { 56 | my ($values, $encoded) = @{$packed_values{$field}}; 57 | my $bytes = Packed->encode({ $field => $values }); 58 | 59 | eq_or_diff($bytes, $encoded, 60 | "$field - packed value"); 61 | decode_eq_or_diff('Packed', $bytes, Packed->new({ $field => $values }), 62 | "$field - round trip"); 63 | } 64 | 65 | # unusual, but the spec excplicitly mentions them 66 | decode_eq_or_diff('Repeated', "\x18\x01\x38\x01\x18\x02\x38\x00", Repeated->new({ 67 | int32_f => [1, 2], 68 | bool_f => [1, ''], 69 | }), "non-contiguous repeated fields"); 70 | decode_eq_or_diff('Packed', "\x1a\x02\x01\x02\x1a\x02\x03\x04", Packed->new({ 71 | int32_f => [1, 2, 3, 4], 72 | }), "packed repeated field in multiple chunks"); 73 | 74 | # "A packed repeated field containing zero elements does not appear in the 75 | # encoded message." 76 | eq_or_diff( 77 | Packed->new({ int32_f => [] })->encode, 78 | '', # expect empty 79 | "empty packed repeated field"); 80 | 81 | done_testing(); 82 | -------------------------------------------------------------------------------- /t/032_map.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("map.proto"); 5 | $d->map_message("test.Maps", "Maps"); 6 | $d->map_message("test.Item", "Item"); 7 | $d->map_message("test.StringMap", "StringMap"); 8 | $d->resolve_references(); 9 | 10 | my %values = ( 11 | string_int32_map => [ 12 | { 13 | "a" => 1, 14 | "b" => 7, 15 | "" => 0, 16 | }, 17 | { 18 | a => "\x0a\x05\x0a\x01a\x10\x01", 19 | b => "\x0a\x05\x0a\x01b\x10\x07", 20 | "" => "\x0a\x04\x0a\x00\x10\x00" 21 | }, 22 | ], 23 | int32_string_map => [ 24 | { 25 | 2 => "a", 26 | 3 => "b", 27 | 0 => "", 28 | }, 29 | { 30 | 2 => "\x12\x05\x08\x02\x12\x01a", 31 | 3 => "\x12\x05\x08\x03\x12\x01b", 32 | 0 => "\x12\x04\x08\x00\x12\x00", 33 | }, 34 | ], 35 | int32_bool_map => [ 36 | { 37 | 2 => "", 38 | 3 => 1, 39 | 0 => "", 40 | }, 41 | { 42 | 2 => "\x1a\x04\x08\x02\x10\x00", 43 | 3 => "\x1a\x04\x08\x03\x10\x01", 44 | 0 => "\x1a\x04\x08\x00\x10\x00", 45 | }, 46 | ], 47 | bool_int32_map => [ 48 | { 49 | 1 => 0, 50 | '' => 2, 51 | }, 52 | { 53 | 1 => "\x22\x04\x08\x01\x10\x00", 54 | '' => "\x22\x04\x08\x00\x10\x02", 55 | }, 56 | ], 57 | int32_enum_map => [ 58 | { 59 | 2 => 1, 60 | 3 => 2, 61 | 0 => 0, 62 | }, 63 | { 64 | 2 => "\x2a\x04\x08\x02\x10\x01", 65 | 3 => "\x2a\x04\x08\x03\x10\x02", 66 | 0 => "\x2a\x04\x08\x00\x10\x00", 67 | }, 68 | ], 69 | int32_message_map => [ 70 | { 71 | 2 => Item->new({ one_value => 7 }), 72 | 3 => Item->new({ another_value => "X" }), 73 | 0 => Item->new, 74 | }, 75 | { 76 | 2 => "\x32\x06\x08\x02\x12\x02\x08\x07", 77 | 3 => "\x32\x07\x08\x03\x12\x03\x12\x01X", 78 | 0 => "\x32\x04\x08\x00\x12\x00", 79 | }, 80 | ], 81 | string_string_map_map => [ 82 | { 83 | 'x' => StringMap->new({ string_int32_map => { 'b' => 2 } }), 84 | }, 85 | { 86 | x => "\x3a\x0c\x0a\x01x\x12\x07\x0a\x05\x0a\x01b\x10\x02", 87 | }, 88 | ], 89 | 'uint32_bool_map' => [ 90 | { 91 | 1 => '', 92 | (1 << 31) => 1, 93 | }, 94 | { 95 | 1 => "\x42\x04\x08\x01\x10\x00", 96 | (1 << 31) => "\x42\x08\x08\x80\x80\x80\x80\x08\x10\x01", 97 | }, 98 | ], 99 | 'uint64_bool_map' => [ 100 | { 101 | 1 => '', 102 | (1 << 63) => 1, 103 | }, 104 | { 105 | 1 => "\x4a\x04\x08\x01\x10\x00", 106 | (1 << 63) => "\x4a\x0d\x08\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01\x10\x01", 107 | }, 108 | ], 109 | ); 110 | 111 | sub encode { 112 | my ($map, $parts) = @_; 113 | 114 | return join('', map $parts->{$_}, keys %$map); 115 | } 116 | 117 | for my $field (sort keys %values) { 118 | my ($values, $encoded) = @{$values{$field}}; 119 | my $bytes = Maps->encode({ $field => $values }); 120 | 121 | eq_or_diff($bytes, encode($values, $encoded), 122 | "$field - encoded value"); 123 | decode_eq_or_diff('Maps', $bytes, Maps->new({ $field => $values }), 124 | "$field - round trip"); 125 | } 126 | 127 | done_testing(); 128 | -------------------------------------------------------------------------------- /t/034_map_incomplete.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("map.proto"); 5 | $d->map_message("test.Maps", "Maps"); 6 | $d->map_message("test.Item", "Item"); 7 | $d->map_message("test.StringMap", "StringMap"); 8 | $d->resolve_references(); 9 | 10 | my %values = ( 11 | string_int32_map => [ 12 | { 13 | "a" => 1, 14 | "b" => 0, 15 | "" => 0, 16 | }, 17 | { 18 | a => "\x0a\x05\x0a\x01a\x10\x01", 19 | b => "\x0a\x03\x0a\x01b", 20 | "" => "\x0a\x04\x0a\x00\x10\x00" 21 | }, 22 | ], 23 | int32_string_map => [ 24 | { 25 | 2 => "a", 26 | 3 => "", 27 | 0 => "", 28 | }, 29 | { 30 | 2 => "\x12\x05\x08\x02\x12\x01a", 31 | 3 => "\x12\x02\x08\x03", 32 | 0 => "\x12\x04\x08\x00\x12\x00", 33 | }, 34 | ], 35 | int32_bool_map => [ 36 | { 37 | 2 => "", 38 | 3 => 1, 39 | 0 => "", 40 | }, 41 | { 42 | 2 => "\x1a\x02\x08\x02", 43 | 3 => "\x1a\x04\x08\x03\x10\x01", 44 | 0 => "\x1a\x04\x08\x00\x10\x00", 45 | }, 46 | ], 47 | bool_int32_map => [ 48 | { 49 | 1 => 0, 50 | '' => 2, 51 | }, 52 | { 53 | 1 => "\x22\x02\x08\x01", 54 | '' => "\x22\x04\x08\x00\x10\x02", 55 | }, 56 | ], 57 | int32_enum_map => [ 58 | { 59 | 2 => 1, 60 | 3 => 2, 61 | 0 => 0, 62 | }, 63 | { 64 | 2 => "\x2a\x04\x08\x02\x10\x01", 65 | 3 => "\x2a\x04\x08\x03\x10\x02", 66 | 0 => "\x2a\x02\x08\x00", 67 | }, 68 | ], 69 | ); 70 | 71 | sub encode { 72 | my ($map, $parts) = @_; 73 | 74 | return join('', map $parts->{$_}, sort keys %$map); 75 | } 76 | 77 | for my $field (sort keys %values) { 78 | my ($values, $encoded) = @{$values{$field}}; 79 | my $bytes = Maps->encode({ $field => $values }); 80 | 81 | decode_eq_or_diff('Maps', encode($values, $encoded), Maps->decode($bytes), 82 | "$field - decode missing map values as default"); 83 | } 84 | 85 | done_testing(); 86 | -------------------------------------------------------------------------------- /t/035_required.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("person.proto"); 5 | $d->map_message("test.Person", "Person"); 6 | $d->resolve_references(); 7 | 8 | { 9 | my $encoded = "\x0a\x03foo\x10\x1f\x1a\x0cfoo\@test.com"; 10 | my $decoded = Person->new({ 11 | id => 31, 12 | name => 'foo', 13 | email => 'foo@test.com', 14 | }); 15 | 16 | decode_eq_or_diff('Person', $encoded, $decoded); 17 | eq_or_diff(Person->encode($decoded), $encoded); 18 | } 19 | 20 | { 21 | my $encoded = "\x0a\x03foo"; 22 | my $decoded = Person->new({ 23 | name => 'foo', 24 | }); 25 | 26 | decode_throws_ok( 27 | 'Person', $encoded, 28 | qr/Deserialization failed: Missing required field test.Person.id/, 29 | qr/Deserialization failed: Missing required field test.Person.id/, 30 | ); 31 | 32 | throws_ok( 33 | sub { Person->encode($decoded) }, 34 | qr/Serialization failed: Missing required field test.Person.id/, 35 | ); 36 | } 37 | 38 | done_testing(); 39 | -------------------------------------------------------------------------------- /t/036_proto3_packed.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("packed_proto3.proto"); 5 | $d->map_package("test", "Test"); 6 | $d->resolve_references(); 7 | 8 | eq_or_diff(Test::Packed->encode({ int32_f => [6, 7, 8] }), "\x1a\x03\x06\x07\x08"); 9 | eq_or_diff(Test::Unpacked->encode({ int32_f => [6, 7, 8] }), "\x18\x06\x18\x07\x18\x08"); 10 | 11 | done_testing(); 12 | -------------------------------------------------------------------------------- /t/040_messages.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("message.proto"); 5 | $d->map_message("test.Inner", "Inner", { explicit_defaults => 1 }); 6 | $d->map_message("test.OuterWithMessage", "OuterWithMessage", { explicit_defaults => 1 }); 7 | $d->map_message("test.OuterWithGroup", "OuterWithGroup", { explicit_defaults => 1 }); 8 | $d->resolve_references(); 9 | 10 | { 11 | my $encoded = "\x0a\x02\x08\x02\x12\x02\x08\x03\x12\x02\x08\x04"; 12 | my $decoded = OuterWithMessage->new({ 13 | optional_inner => Inner->new({ 14 | value => 2, 15 | other => 0, 16 | }), 17 | repeated_inner => [ 18 | Inner->new({ value => 3, other => 0 }), 19 | Inner->new({ value => 4, other => 0 }), 20 | ], 21 | }); 22 | my $for_encode = { 23 | optional_inner => { 24 | value => 2, 25 | }, 26 | repeated_inner => [ 27 | { value => 3 }, 28 | { value => 4 }, 29 | ], 30 | }; 31 | 32 | my $tied; 33 | { 34 | my ($v2, $v3, $v4) = (2, 3, 4); 35 | my $value_2 = { value => undef }; 36 | my $value_3 = { value => undef }; 37 | my $value_4 = { value => undef }; 38 | my $repeated = [undef, undef]; 39 | my $value = { 40 | optional_inner => undef, 41 | repeated_inner => undef, 42 | }; 43 | 44 | tie_scalar($value_2->{value}, $v2); 45 | tie_scalar($value_3->{value}, $v3); 46 | tie_scalar($value_4->{value}, $v4); 47 | tie_scalar($repeated->[0], $value_3); 48 | tie_scalar($repeated->[1], $value_4); 49 | tie_scalar($value->{optional_inner}, $value_2); 50 | tie_scalar($value->{repeated_inner}, $repeated); 51 | tie_scalar($tied, $value); 52 | }; 53 | 54 | decode_eq_or_diff('OuterWithMessage', $encoded, $decoded); 55 | eq_or_diff(OuterWithMessage->encode($for_encode), $encoded); 56 | eq_or_diff(OuterWithMessage->encode($tied), $encoded); 57 | eq_or_diff(tied_fetch_count($tied), { 58 | count => 1, 59 | inner => { 60 | optional_inner => { 61 | count => 1, 62 | inner => { 63 | value => 1, 64 | }, 65 | }, 66 | repeated_inner => { 67 | count => 1, 68 | inner => [ 69 | { 70 | count => 1, 71 | inner => { 72 | value => 1, 73 | }, 74 | }, 75 | { 76 | count => 1, 77 | inner => { 78 | value => 1, 79 | }, 80 | }, 81 | ], 82 | }, 83 | }, 84 | }); 85 | } 86 | 87 | { 88 | my $encoded = "\x0b\x08\x02\x0c\x0b\x08\x03\x0c"; 89 | my $decoded = OuterWithGroup->new({ 90 | inner => [ 91 | OuterWithGroup::Inner->new({ value => 2 }), 92 | OuterWithGroup::Inner->new({ value => 3 }), 93 | ], 94 | }); 95 | 96 | eq_or_diff(OuterWithGroup->decode_upb($encoded), $decoded); 97 | eq_or_diff(OuterWithGroup->encode($decoded), $encoded); 98 | } 99 | 100 | # unusual, but the spec explicitly mentions this 101 | { 102 | my $encoded = "\x0a\x02\x08\x02\x0a\x02\x10\x07"; 103 | my $reencoded = "\x0a\x04\x08\x02\x10\x07"; 104 | my $decoded = OuterWithMessage->new({ 105 | optional_inner => Inner->new({ 106 | value => 2, 107 | other => 7, 108 | }), 109 | }); 110 | 111 | decode_eq_or_diff('OuterWithMessage', $encoded, $decoded); 112 | eq_or_diff(OuterWithMessage->encode($decoded), $reencoded); 113 | } 114 | 115 | done_testing(); 116 | -------------------------------------------------------------------------------- /t/042_recursion.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("recurse.proto"); 5 | $d->map_message("test.List", "List"); 6 | $d->resolve_references(); 7 | 8 | { 9 | my $encoded = "\x08\x02"; 10 | my $decoded = List->new({ 11 | value => 2, 12 | }); 13 | 14 | decode_eq_or_diff('List', $encoded, $decoded); 15 | eq_or_diff(List->encode($decoded), $encoded); 16 | } 17 | 18 | { 19 | my $encoded = "\x08\x02\x12\x02\x08\x01"; 20 | my $decoded = List->new({ 21 | value => 2, 22 | next => List->new({ 23 | value => 1, 24 | }), 25 | }); 26 | 27 | decode_eq_or_diff('List', $encoded, $decoded); 28 | eq_or_diff(List->encode($decoded), $encoded); 29 | } 30 | 31 | done_testing(); 32 | -------------------------------------------------------------------------------- /t/045_enum.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("enum.proto"); 5 | $d->map_message("test.MessageBefore", "MessageBefore", { explicit_defaults => 1}); 6 | $d->map_message("test.MessageAfter", "MessageAfter", { explicit_defaults => 1}); 7 | $d->resolve_references(); 8 | 9 | decode_eq_or_diff( 10 | 'MessageAfter', MessageBefore->encode({ value => 2, array => [1, 2] }), 11 | MessageAfter->new({ value => 2, array => [1, 2] }), 12 | "sanity check for the tests below", 13 | ); 14 | 15 | decode_eq_or_diff( 16 | 'MessageBefore', MessageAfter->encode({ value => 3, array => [3, 2, 1] }), 17 | MessageBefore->new({ value => 1, array => [1, 2, 1] }), 18 | "unknown enum value uses default in deserialization", 19 | ); 20 | 21 | throws_ok( 22 | sub { MessageBefore->encode({ value => 3 }) }, 23 | qr/Invalid enumeration value 3 for field 'test.MessageBefore.value'/, 24 | "unknown enum value croaks in serialization" 25 | ); 26 | 27 | throws_ok( 28 | sub { MessageBefore->encode({ array => [3, 2] }) }, 29 | qr/Invalid enumeration value 3 for field 'test.MessageBefore.array'/, 30 | "unknown enum value croaks in serialization" 31 | ); 32 | 33 | done_testing; 34 | -------------------------------------------------------------------------------- /t/048_edge_cases.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("edge.proto"); 5 | $d->map_package("test", "Test1", { decode_blessed => 0 }); 6 | $d->resolve_references(); 7 | 8 | # inner message 9 | { 10 | # empty message 11 | decode_eq_or_diff('Test1::EmptyFields', "\x0a\x00", { empty_message => {} }, 'empty string'); 12 | # wrong wire type for the field 13 | decode_eq_or_diff('Test1::EmptyFields', "\x08\x00", {}, 'string field, varint wire type'); 14 | decode_eq_or_diff('Test1::EmptyFields', "\x09\x00\x00\x00\x00\x00\x00\x00\x00", {}, 'string field, fixed64 wire type'); 15 | decode_eq_or_diff('Test1::EmptyFields', "\x0d\x00\x00\x00\x00", {}, 'string field, fixed32 wire type'); 16 | } 17 | 18 | # string 19 | { 20 | # empty string 21 | decode_eq_or_diff('Test1::EmptyFields', "\x32\x00", { some_string => '' }, 'empty message'); 22 | # wrong wire type for the field 23 | decode_eq_or_diff('Test1::EmptyFields', "\x30\x00", {}, 'message field, varint wire type'); 24 | decode_eq_or_diff('Test1::EmptyFields', "\x31\x00\x00\x00\x00\x00\x00\x00\x00", {}, 'message field, fixed64 wire type'); 25 | decode_eq_or_diff('Test1::EmptyFields', "\x35\x00\x00\x00\x00", {}, 'message field, fixed32 wire type'); 26 | } 27 | 28 | # packed varint 29 | { 30 | # correct field 31 | decode_eq_or_diff('Test1::EmptyFields', "\x18\x00", { packed_int32 => [0] }, 'varint field, varint wire type'); 32 | decode_eq_or_diff('Test1::EmptyFields', "\x1a\x01\x00", { packed_int32 => [0] }, 'varint field, packed field'); 33 | # wrong wire type for the field 34 | decode_eq_or_diff('Test1::EmptyFields', "\x19\x00\x00\x00\x00\x00\x00\x00\x00", {}, 'varint field, fixed64 wire type'); 35 | decode_eq_or_diff('Test1::EmptyFields', "\x1d\x00\x00\x00\x00", {}, 'varint field, fixed32 wire type'); 36 | } 37 | 38 | # packed fixed32 39 | { 40 | # correct field 41 | decode_eq_or_diff('Test1::EmptyFields', "\x25\x00\x00\x00\x00", { packed_fixed32 => [0] }, 'fixed32 field, fixed32 wire type'); 42 | decode_eq_or_diff('Test1::EmptyFields', "\x22\x04\x00\x00\x00\x00", { packed_fixed32 => [0] }, 'fixed32 field, packed field'); 43 | # wrong wire type for the field 44 | decode_eq_or_diff('Test1::EmptyFields', "\x20\x00", {}, 'fixed32 field, varint wire type'); 45 | decode_eq_or_diff('Test1::EmptyFields', "\x21\x00\x00\x00\x00\x00\x00\x00\x00", {}, 'fixed32 field, fixed64 wire type'); 46 | } 47 | 48 | # packed fixed64 49 | { 50 | # correct field 51 | decode_eq_or_diff('Test1::EmptyFields', "\x29\x00\x00\x00\x00\x00\x00\x00\x00", { packed_fixed64 => [0] }, 'fixed64 field, fixed64 wire type'); 52 | decode_eq_or_diff('Test1::EmptyFields', "\x2a\x08\x00\x00\x00\x00\x00\x00\x00\x00", { packed_fixed64 => [0] }, 'fixed64 field, packed field'); 53 | # wrong wire type for the field 54 | decode_eq_or_diff('Test1::EmptyFields', "\x28\x00", {}, 'fixed64 field, varint wire type'); 55 | decode_eq_or_diff('Test1::EmptyFields', "\x2d\x00\x00\x00\x00", {}, 'fixed64 field, fixed32 wire type'); 56 | } 57 | 58 | # unknown fields 59 | { 60 | decode_eq_or_diff('Test1::UnknownFields', "\x08\x01\x10\x02\x18\x04\xc0\x3e\x03", { field2 => 2, field1000 => 3 }, 'unknown fields are skipped'); 61 | } 62 | 63 | done_testing(); 64 | -------------------------------------------------------------------------------- /t/050_extensions.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("extensions.proto"); 5 | $d->map_message("test.Message1", "BaseMessage"); 6 | $d->map_message("test.Message2", "ExtensionMessage"); 7 | $d->resolve_references(); 8 | 9 | { 10 | my $encoded = "\x08\x02\xa0\x06\x03\xb0\x06\x04"; 11 | my $decoded = BaseMessage->new({ 12 | value => 2, 13 | '[test.value]' => 4, 14 | '[test.extension1]' => 3, 15 | }); 16 | 17 | decode_eq_or_diff('BaseMessage', $encoded, $decoded); 18 | eq_or_diff(BaseMessage->encode($decoded), $encoded); 19 | 20 | is(BaseMessage::VALUE_FIELD_NUMBER(), 1); 21 | is(ExtensionMessage::VALUE_FIELD_NUMBER(), 1); 22 | is(BaseMessage::TEST_EXTENSION1_FIELD_NUMBER(), 100); 23 | is(BaseMessage::TEST_VALUE_FIELD_NUMBER(), 102); 24 | is(BaseMessage::TEST_MESSAGE2_EXTENSION2_FIELD_NUMBER(), 101); 25 | 26 | is(BaseMessage::TEST_EXTENSION1_KEY(), '[test.extension1]'); 27 | is(BaseMessage::TEST_VALUE_KEY(), '[test.value]'); 28 | is(BaseMessage::TEST_MESSAGE2_EXTENSION2_KEY(), '[test.Message2.extension2]'); 29 | } 30 | 31 | { 32 | my $encoded = "\x08\x02\xa0\x06\x04\xaa\x06\x02\x08\x05"; 33 | my $decoded = BaseMessage->new({ 34 | value => 2, 35 | '[test.extension1]' => 4, 36 | '[test.Message2.extension2]' => ExtensionMessage->new({ 37 | value => 5, 38 | }), 39 | }); 40 | 41 | decode_eq_or_diff('BaseMessage', $encoded, $decoded); 42 | eq_or_diff(BaseMessage->encode($decoded), $encoded); 43 | } 44 | 45 | done_testing(); 46 | -------------------------------------------------------------------------------- /t/055_oneof.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("oneof.proto"); 5 | $d->map_message("test.OneOf1", "OneOf1"); 6 | $d->resolve_references(); 7 | 8 | { 9 | my $encoded = "\x10\x03\x1a\x03foo"; 10 | my $decoded = OneOf1->new({ 11 | value1 => 3, 12 | value2 => 'foo', 13 | }); 14 | 15 | decode_eq_or_diff('OneOf1', $encoded, $decoded); 16 | eq_or_diff(OneOf1->encode($decoded), $encoded); 17 | } 18 | 19 | { 20 | my $encoded = "\x08\x04\x10\x03"; 21 | my $decoded = OneOf1->new({ 22 | value1 => 3, 23 | value3 => 4, 24 | }); 25 | 26 | decode_eq_or_diff('OneOf1', $encoded, $decoded); 27 | eq_or_diff(OneOf1->encode($decoded), $encoded); 28 | } 29 | 30 | { 31 | my $encoded = "\x10\x03\x1a\x03foo\x08\x04"; 32 | my $decoded = OneOf1->new({ 33 | value1 => 3, 34 | value3 => 4, 35 | }); 36 | 37 | decode_eq_or_diff('OneOf1', $encoded, $decoded); 38 | } 39 | 40 | { 41 | my $encoded = "\x10\x03\x08\x04\x1a\x03foo"; 42 | my $decoded = OneOf1->new({ 43 | value1 => 3, 44 | value2 => 'foo', 45 | }); 46 | 47 | decode_eq_or_diff('OneOf1', $encoded, $decoded); 48 | } 49 | 50 | { 51 | my $encoded = "\x08\x04\x10\x03"; 52 | my $decoded = OneOf1->new({ 53 | value1 => 3, 54 | value2 => 'foo', 55 | value3 => 4, 56 | }); 57 | 58 | eq_or_diff(OneOf1->encode($decoded), $encoded); 59 | } 60 | 61 | done_testing(); 62 | -------------------------------------------------------------------------------- /t/057_proto3_optional.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test 'proto3_optional'; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("optional_proto3.proto"); 5 | $d->map_message("test.Optional1", "Optional1"); 6 | $d->resolve_references(); 7 | 8 | my %values = ( 9 | double => ["\x09\x00\x00\x00\x00\x00\x00\xc0?\x89\x02\x00\x00\x00\x00\x00\x00\xd0?", 0.125, 0.25, "\x09\x00\x00\x00\x00\x00\x00\x00\x00", "\x89\x02\x00\x00\x00\x00\x00\x00\x00\x00", 0.0], 10 | float => ["\x15\x00\x00\x00>\x95\x02\x00\x00\x80>", 0.125, 0.25, "\x15\x00\x00\x00\x00", "\x95\x02\x00\x00\x00\x00", 0.0], 11 | int32 => ["\x18\x07\x98\x02\x08", 7, 8, "\x18\x00", "\x98\x02\x00", 0], 12 | int64 => ["\x20\x07\xa0\x02\x08", 7, 8, "\x20\x00", "\xa0\x02\x00", 0], 13 | uint32 => ["\x28\x07\xa8\x02\x08", 7, 8, "\x28\x00", "\xa8\x02\x00", 0], 14 | uint64 => ["\x30\x07\xb0\x02\x08", 7, 8, "\x30\x00", "\xb0\x02\x00", 0], 15 | bool => ["\x38\x01\xb8\x02\x01", 1, 1, "\x38\x00", "\xb8\x02\x00", ''], 16 | string => ["\x42\x02ab\xc2\x02\x02bc", 'ab', 'bc', "\x42\x00", "\xc2\x02\x00", ''], 17 | bytes => ["\x4a\x02ab\xca\x02\x02bc", 'ab', 'bc', "\x4a\x00", "\xca\x02\x00", ''], 18 | enum => ["\x50\x01\xd0\x02\x02", 1, 2, "\x50\x00", "\xd0\x02\x00", 0], 19 | ); 20 | 21 | for my $type (sort keys %values) { 22 | my ($encoded_nondefault, $v_f, $v_pf, $encoded_f0, $encoded_pf0, $v_0) = @{$values{$type}}; 23 | 24 | { 25 | my $decoded = Optional1->new({ 26 | "${type}_f" => $v_f, 27 | "${type}_pf"=> $v_pf, 28 | }); 29 | 30 | decode_eq_or_diff('Optional1', $encoded_nondefault, $decoded, "$type - non-default values both present in decoded value"); 31 | eq_or_diff(Optional1->encode($decoded), $encoded_nondefault, "$type - non-default value both present in encoded bytes"); 32 | } 33 | 34 | { 35 | my $original = Optional1->new({ 36 | "${type}_f" => $v_0, 37 | "${type}_pf"=> $v_0, 38 | }); 39 | 40 | eq_or_diff(Optional1->encode($original), $encoded_pf0, "$type - default value only encoded for optional fields"); 41 | } 42 | } 43 | 44 | done_testing(); 45 | -------------------------------------------------------------------------------- /t/060_tie.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("scalar.proto"); 5 | $d->load_file("repeated.proto"); 6 | $d->load_file("map.proto"); 7 | $d->load_file("message.proto"); 8 | $d->map_message("test.Basic", "Basic", { check_enum_values => 0 }); 9 | $d->map_message("test.Repeated", "Repeated"); 10 | $d->map_message("test.Inner", "Inner"); 11 | $d->map_message("test.OuterWithMessage", "OuterWithMessage"); 12 | $d->map_message("test.StringMap", "StringMap"); 13 | $d->map_message("test.Item", "MapItem"); 14 | $d->map_message("test.Maps", "Maps"); 15 | $d->resolve_references(); 16 | 17 | { 18 | my $tied = tied_hash(); 19 | 20 | eq_or_diff(Basic->encode($tied), ""); 21 | eq_or_diff(tied_fetch_count($tied), { 22 | count => 0, 23 | inner => {}, 24 | }); 25 | } 26 | 27 | { 28 | my $tied = tied_hash( 29 | bool_f => 1, 30 | int32_f => 2, 31 | ); 32 | 33 | eq_or_diff(Basic->encode($tied), "\x18\x02\x38\x01"); 34 | eq_or_diff(tied_fetch_count($tied), { 35 | count => 2, 36 | inner => { 37 | bool_f => -1, 38 | int32_f => -1, 39 | }, 40 | }); 41 | } 42 | 43 | { 44 | my $tied = { 45 | bool_f => tied_array(0, 1, 1), 46 | }; 47 | 48 | eq_or_diff(Repeated->encode($tied), "\x38\x00\x38\x01\x38\x01"); 49 | eq_or_diff(tied_fetch_count($tied), { 50 | bool_f => { 51 | count => 3, 52 | inner => [-1, -1, -1], 53 | }, 54 | }); 55 | } 56 | 57 | { 58 | my $tied = { 59 | optional_inner => tied_hash(value => 3), 60 | repeated_inner => tied_array( 61 | tied_hash(value => 4), 62 | tied_hash(value => 5), 63 | ), 64 | }; 65 | 66 | eq_or_diff(OuterWithMessage->encode($tied), 67 | "\x0a\x02\x08\x03\x12\x02\x08\x04\x12\x02\x08\x05"); 68 | eq_or_diff(tied_fetch_count($tied), { 69 | optional_inner => { 70 | count => 1, 71 | inner => { 72 | value => -1, 73 | }, 74 | }, 75 | repeated_inner => { 76 | count => 2, 77 | inner => [ 78 | { 79 | count => 1, 80 | inner => { 81 | value => -1, 82 | }, 83 | }, 84 | { 85 | count => 1, 86 | inner => { 87 | value => -1, 88 | }, 89 | }, 90 | ], 91 | }, 92 | }); 93 | } 94 | 95 | { 96 | my $tied = { 97 | string_int32_map => tied_hash(a => 19), 98 | }; 99 | 100 | eq_or_diff(Maps->encode($tied), "\x0a\x05\x0a\x01a\x10\x13"); 101 | eq_or_diff(tied_fetch_count($tied), { 102 | string_int32_map => { 103 | count => 3, # firstkey + nextkey + fetch 104 | inner => { 105 | a => -1, 106 | }, 107 | } 108 | }); 109 | } 110 | 111 | done_testing(); 112 | -------------------------------------------------------------------------------- /t/065_checked_new.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("person.proto"); 5 | $d->map_message("test.Person", "Person"); 6 | $d->map_message("test.PersonArray", "PersonArray"); 7 | $d->resolve_references(); 8 | 9 | lives_ok(sub { Person->new_and_check({ id => 31, name => 'foo' }) }); 10 | lives_ok(sub { Person->new_and_check({ id => 32 }) }); 11 | lives_ok(sub { PersonArray->new_and_check({ persons => [] }) }); 12 | lives_ok(sub { PersonArray->new_and_check({ persons => [ 13 | { id => 34, name => 'foo' }, { id => 35, name => 'bar' }, 14 | ] }) }); 15 | 16 | throws_ok( 17 | sub { Person->new_and_check({ id => 33, neam => 'foo' }) }, 18 | qr/Check failed: Unknown field 'neam' during check/, 19 | ); 20 | 21 | =for TODO 22 | 23 | Check required fields 24 | 25 | throws_ok( 26 | sub { Person->new_and_check({ name => 'foo' }) }, 27 | qr/Check failed: Unknown field 'neam' during check/, 28 | ); 29 | 30 | =cut 31 | 32 | throws_ok( 33 | sub { PersonArray->new_and_check({ persons => {} }) }, 34 | qr/Not an array reference when encoding field 'test.PersonArray.persons'/, 35 | ); 36 | 37 | throws_ok( 38 | sub { PersonArray->new_and_check({ persons => [undef] }) }, 39 | qr/Not a hash reference when checking a test.Person value/, 40 | ); 41 | 42 | throws_ok( 43 | sub { PersonArray->new_and_check({ persons => [{ id => 34, neam => 'foo' }] }) }, 44 | qr/Check failed: Unknown field 'neam' during check/, 45 | ); 46 | 47 | done_testing(); 48 | -------------------------------------------------------------------------------- /t/067_warnings.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("person.proto"); 5 | $d->map_message("test.Person", "Person"); 6 | $d->map_message("test.PersonArray", "PersonArray"); 7 | $d->load_file("map_proto2.proto"); 8 | $d->map_message("test.Maps", "Maps", { implicit_maps => 1 }); 9 | $d->map_message("test.Item", "Item"); 10 | $d->resolve_references(); 11 | 12 | my $d1 = Google::ProtocolBuffers::Dynamic->new('t/proto'); 13 | $d1->load_file("map_proto2.proto"); 14 | $d1->map_message("test.Maps", "NoMaps", { implicit_maps => 0 }); 15 | $d1->map_message("test.Item", "NoItem"); 16 | $d1->resolve_references(); 17 | 18 | my $uninit = "Use of uninitialized value in subroutine entry"; 19 | 20 | warning_is( 21 | sub { Person->encode({ name => undef, id => 3 }) }, 22 | qq[While encoding field 'name': $uninit], 23 | ); 24 | 25 | warning_is( 26 | sub { PersonArray->encode({ persons => [{ id => 1, name => 'a' }, { name => undef, id => 3 }] }) }, 27 | qq[While encoding field 'persons.[1].name': $uninit], 28 | ); 29 | 30 | warning_is( 31 | sub { PersonArray->encode({ persons => [{ id => 1, name => 'a' }, { name => undef, id => 3 }] }) }, 32 | qq[While encoding field 'persons.[1].name': $uninit], 33 | ); 34 | 35 | warning_is( 36 | sub { Maps->encode({ string_int32_map => { foo => undef } }) }, 37 | qq[While encoding field 'string_int32_map.{foo}': $uninit], 38 | ); 39 | 40 | warning_is( 41 | sub { Maps->encode({ int32_message_map => { 3 => { one_value => undef } } }) }, 42 | qq[While encoding field 'int32_message_map.{3}.one_value': $uninit], 43 | ); 44 | 45 | for my $method (decoder_functions) { 46 | my $method_desc = "($method)"; 47 | 48 | warning_is( 49 | sub { Maps->$method(NoMaps->encode({ string_int32_map => [{ value => 1}] })) }, 50 | qq[Incomplete map entry: missing key], 51 | $method_desc, 52 | ); 53 | 54 | warning_is( 55 | sub { Maps->$method(NoMaps->encode({ string_int32_map => [{}] })) }, 56 | qq[Incomplete map entry: missing key], 57 | $method_desc, 58 | ); 59 | } 60 | 61 | done_testing(); 62 | -------------------------------------------------------------------------------- /t/070_order.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("order.proto"); 5 | $d->map_message("test.OrderedFields", "OrderedFields"); 6 | $d->map_message("test.DisorderedFields", "DisorderedFields"); 7 | $d->map_message("test.MixedOneof", "MixedOneof"); 8 | $d->map_message("test.InterleavedOneof", "InterleavedOneof"); 9 | $d->resolve_references(); 10 | 11 | my $sc = { 12 | field_1 => { 13 | field => 6, 14 | }, 15 | field_2 => 7, 16 | field_3 => 8, 17 | field_4 => { 18 | field => 9, 19 | }, 20 | }; 21 | 22 | eq_or_diff(OrderedFields->encode($sc), "\x0a\x02\x08\x06\x10\x07\x18\x08\x22\x02\x08\x09"); 23 | eq_or_diff(DisorderedFields->encode($sc), "\x0a\x02\x08\x06\x10\x07\x18\x08\x22\x02\x08\x09"); 24 | 25 | my $oo1 = { 26 | field_1 => 6, 27 | field_2 => 7, 28 | field_3 => 8, 29 | field_4 => 9, 30 | }; 31 | my $oo2 = { 32 | field_2 => 7, 33 | field_3 => 8, 34 | field_4 => 9, 35 | }; 36 | 37 | eq_or_diff(MixedOneof->encode($oo1), "\x08\x06\x10\x07\x20\x09"); 38 | eq_or_diff(MixedOneof->encode($oo2), "\x10\x07\x18\x08\x20\x09"); 39 | 40 | my $io1 = { 41 | field_1 => 6, 42 | field_2 => 7, 43 | field_3 => 8, 44 | field_4 => 9, 45 | field_5 => 10, 46 | field_6 => 11, 47 | }; 48 | 49 | eq_or_diff(InterleavedOneof->encode($io1), "\x08\x06\x10\x07\x18\x08\x30\x0b"); 50 | 51 | done_testing(); 52 | -------------------------------------------------------------------------------- /t/082_wkt.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("wkt/scalar.proto"); 5 | $d->map_message("test.Basic", "Test::Basic"); 6 | $d->map_wkts({ decode_blessed => 0 }); 7 | $d->resolve_references(); 8 | 9 | my %scalar_values = ( 10 | timestamp_f => [{ seconds => 15, nanos => 17 }, "\x0a\x04\x08\x0f\x10\x11"], 11 | duration_f => [{ seconds => 15, nanos => 17 }, "\x12\x04\x08\x0f\x10\x11"], 12 | double_f => [{ value => 0.125 }, "\x1a\x09\x09\x00\x00\x00\x00\x00\x00\xc0?"], 13 | float_f => [{ value => 0.125 }, "\x22\x05\x0d\x00\x00\x00\x3e"], 14 | int64_f => [{ value => maybe_bigint('4294967296') }, "\x2a\x06\x08\x80\x80\x80\x80\x10"], 15 | uint64_f => [{ value => maybe_bigint('1099511627776') }, "\x32\x07\x08\x80\x80\x80\x80\x80\x20"], 16 | int32_f => [{ value => 2147483647 }, "\x3a\x06\x08\xff\xff\xff\xff\x07"], 17 | uint32_f => [{ value => 4294967295 }, "\x42\x06\x08\xff\xff\xff\xff\x0f"], 18 | bool_f => [{ value => 1 }, "\x4a\x02\x08\x01"], 19 | string_f => [{ value => "abc" }, "\x52\x05\x0a\x03abc"], 20 | bytes_f => [{ value => "abc" }, "\x5a\x05\x0a\x03abc"], 21 | ); 22 | 23 | { 24 | for my $field (sort keys %scalar_values) { 25 | my ($value, $encoded) = @{$scalar_values{$field}}; 26 | my $bytes = Test::Basic->encode({ $field => $value }); 27 | 28 | eq_or_diff($bytes, $encoded, 29 | "scalar $field - encoded value"); 30 | decode_eq_or_diff('Test::Basic', $bytes, Test::Basic->new({ $field => $value }), 31 | "scalar $field - round trip"); 32 | } 33 | } 34 | 35 | done_testing(); 36 | -------------------------------------------------------------------------------- /t/085_wkt_json.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("wkt/scalar.proto"); 5 | $d->map_message("test.Basic", "Test::Basic"); 6 | $d->map_wkts({ decode_blessed => 0 }); 7 | $d->resolve_references(); 8 | 9 | my %scalar_values = ( 10 | timestamp_f => [{ seconds => 123, nanos => 777888999 }, '{"timestampF":"1970-01-01T00:02:03.777888999Z"}'], 11 | duration_f => [{ seconds => 123, nanos => 777888999 }, '{"durationF":"123.777888999s"}'], 12 | double_f => [{ value => 1.25 }, '{"doubleF":1.25}'], 13 | float_f => [{ value => 2.125 }, '{"floatF":2.125}'], 14 | int64_f => [{ value => maybe_bigint('4294967296') }, '{"int64F":"4294967296"}'], 15 | uint64_f => [{ value => maybe_bigint('1099511627776') }, '{"uint64F":"1099511627776"}'], 16 | int32_f => [{ value => 2147483647 }, '{"int32F":2147483647}'], 17 | uint32_f => [{ value => 4294967295 }, '{"uint32F":4294967295}'], 18 | bool_f => [{ value => 1 }, '{"boolF":true}'], 19 | string_f => [{ value => "abc" }, '{"stringF":"abc"}'], 20 | bytes_f => [{ value => "abc" }, '{"bytesF":"YWJj"}'], 21 | ); 22 | 23 | { 24 | for my $field (sort keys %scalar_values) { 25 | my ($value, $encoded) = @{$scalar_values{$field}}; 26 | my $bytes = Test::Basic->encode_json({ $field => $value }); 27 | my $decoded = Test::Basic->decode_json($bytes); 28 | 29 | eq_or_diff($bytes, $encoded, 30 | "scalar $field - encoded value"); 31 | eq_or_diff($decoded, Test::Basic->new({ $field => $value }), 32 | "scalar $field - round trip"); 33 | } 34 | } 35 | 36 | done_testing(); 37 | -------------------------------------------------------------------------------- /t/100_type_mapping.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 5 | 6 | $d->load_file("test1.proto"); 7 | $d->map_message("test1.Message1", "Test1::FirstMessage"); 8 | $d->map_message("test1.Message2", "Test1::AnotherMessage"); 9 | $d->map_message("test1.Message3", "Test1::CompositeMessage"); 10 | $d->map_message("test1.Message4.Message5", "Test1::InnerMessage"); 11 | $d->map_message("test1.Message4", "Test1::OuterMessage"); 12 | $d->map_enum('test1.Enum', 'Test1::Enumeration'); 13 | $d->resolve_references(); 14 | 15 | eq_or_diff(Test1::FirstMessage->decode("\x08\x01"), Test1::FirstMessage->new({ 16 | test1_message1 => 1, 17 | }), "simple message 2"); 18 | eq_or_diff(Test1::AnotherMessage->decode("\x08\x01"), Test1::AnotherMessage->new({ 19 | test1_message2 => 1, 20 | }), "simple message 2"); 21 | eq_or_diff(Test1::CompositeMessage->decode("\x0a\x02\x08\x01\x12\x02\x08\x01"), Test1::CompositeMessage->new({ 22 | test1_message3_message1 => Test1::FirstMessage->new({ 23 | test1_message1 => 1, 24 | }), 25 | test1_message3_message2 => Test1::AnotherMessage->new({ 26 | test1_message2 => 1, 27 | }), 28 | }), "composite message"); 29 | eq_or_diff(Test1::OuterMessage->decode("\x12\x02\x08\x0f"), Test1::OuterMessage->new({ 30 | inner => Test1::InnerMessage->new({ 31 | value => 15, 32 | }), 33 | }), "inner message"); 34 | 35 | is(Test1::Enumeration::VALUE1(), 2, 'enum value'); 36 | is(Test1::Enumeration::VALUE2(), 7, 'enum value'); 37 | is(Test1::Enumeration::VALUE3(), 12, 'enum value'); 38 | 39 | is(Test1::OuterMessage::Enum::VALUE1(), 3, 'inner enum value'); 40 | is(Test1::OuterMessage::Enum::VALUE2(), 2, 'inner enum value'); 41 | is(Test1::OuterMessage::Enum::VALUE3(), 1, 'inner enum value'); 42 | } 43 | 44 | { 45 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 46 | 47 | $d->load_file("test1.proto"); 48 | $d->map_message("test1.Message1", "Test2::FirstMessage"); 49 | 50 | throws_ok( 51 | sub { $d->map_message("test1.Message1", "Test2::FirstMessageDup") }, 52 | qr/Message 'test1\.Message1' has already been mapped/, 53 | "duplicate message mapping", 54 | ); 55 | 56 | throws_ok( 57 | sub { $d->map_message("test1.Message2", "Test2::FirstMessage") }, 58 | qr/Package 'Test2::FirstMessage' is being remapped/, 59 | "duplicate package mapping", 60 | ); 61 | 62 | throws_ok( 63 | sub { $d->map_enum('test1.Enum', 'Test2::FirstMessage') }, 64 | qr/Package 'Test2::FirstMessage' is being remapped/, 65 | "enum/message clash", 66 | ); 67 | 68 | $d->map_enum('test1.Enum', 'Test2::Enumeration'); 69 | 70 | throws_ok( 71 | sub { $d->map_enum('test1.Enum', 'Test2::OtherEnum') }, 72 | qr/test1.Enum' has already been mapped/, 73 | "duplicate enum mapping", 74 | ); 75 | } 76 | 77 | done_testing(); 78 | -------------------------------------------------------------------------------- /t/101_map_once.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my ($dummy_file, $dummy_line); 4 | 5 | { 6 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 7 | 8 | $d->load_file("test1.proto"); 9 | dummy($d); 10 | 11 | is(Test1::Message->message_descriptor->full_name, 'test1.Message2'); 12 | } 13 | 14 | { 15 | my ($this_file, $this_line); 16 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 17 | 18 | $d->load_file("test1.proto"); 19 | throws_ok(sub { 20 | $d->map_message("test1.Message2", "Test1::Message"); BEGIN { ($this_file, $this_line) = (__FILE__, __LINE__) } 21 | }, qr/Package 'Test1::Message' is being remapped from $this_file line $this_line but has already been mapped from $dummy_file line $dummy_line/, 'duplicate mapping from different GPD object'); 22 | 23 | is(Test1::Message->message_descriptor->full_name, 'test1.Message2', 24 | 'old mapping is still in effect'); 25 | } 26 | 27 | my ($mapping1_file, $mapping1_line, $mapping2_file, $mapping2_line); 28 | 29 | { 30 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 31 | 32 | $d->load_file("test1.proto"); 33 | warning_is(sub { 34 | $d->map({ package => 'test1', prefix => 'Test1_1' }); BEGIN { ($mapping1_file, $mapping1_line) = (__FILE__, __LINE__) } 35 | }, undef); 36 | } 37 | 38 | { 39 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 40 | 41 | $d->load_file("test1.proto"); 42 | throws_ok(sub { 43 | $d->map({ package => 'test1', prefix => 'Test1_1' }); BEGIN { ($mapping2_file, $mapping2_line) = (__FILE__, __LINE__) } 44 | }, qr/Package 'Test1_1::Message1' is being remapped from $mapping2_file line $mapping2_line but has already been mapped from $mapping1_file line $mapping1_line/, 'better file/line numbers from ->map'); 45 | } 46 | 47 | { 48 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 49 | 50 | $d->load_file("wkts.proto"); 51 | 52 | warning_is(sub { 53 | $d->map({ package => 'wkts', prefix => 'WKTs_1' }) 54 | }, undef); 55 | } 56 | 57 | { 58 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 59 | 60 | $d->load_file("wkts.proto"); 61 | 62 | warning_is(sub { 63 | $d->map({ package => 'wkts', prefix => 'WKTs_2' }); 64 | }, undef); 65 | } 66 | 67 | done_testing(); 68 | 69 | sub dummy { 70 | $_[0]->map_message("test1.Message2", "Test1::Message"); BEGIN { ($dummy_file, $dummy_line) = (__FILE__, __LINE__) } 71 | } 72 | -------------------------------------------------------------------------------- /t/102_service_mapping.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 5 | 6 | $d->load_file("test3.proto"); 7 | $d->map_message("test1.Message1", "Test1::FirstMessage"); 8 | $d->map_message("test2.Message1", "Test2::FirstMessage"); 9 | $d->map_service("test3.Service1", "Test3::AService", { client_services => 'noop' }); 10 | $d->resolve_references(); 11 | 12 | is(Test3::AService->service_descriptor->full_name, 'test3.Service1', 'explicit service mapping'); 13 | } 14 | 15 | done_testing(); 16 | -------------------------------------------------------------------------------- /t/105_package_mapping.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 5 | 6 | $d->load_file("test1.proto"); 7 | $d->map_package('test1', 'Test1'); 8 | $d->resolve_references(); 9 | 10 | eq_or_diff(Test1::Message1->decode("\x08\x01"), Test1::Message1->new({ 11 | test1_message1 => 1, 12 | }), "simple message 2"); 13 | eq_or_diff(Test1::Message2->decode("\x08\x01"), Test1::Message2->new({ 14 | test1_message2 => 1, 15 | }), "simple message 2"); 16 | eq_or_diff(Test1::Message3->decode("\x0a\x02\x08\x01\x12\x02\x08\x01"), Test1::Message3->new({ 17 | test1_message3_message1 => Test1::Message1->new({ 18 | test1_message1 => 1, 19 | }), 20 | test1_message3_message2 => Test1::Message2->new({ 21 | test1_message2 => 1, 22 | }), 23 | }), "composite message"); 24 | eq_or_diff(Test1::Message4->decode("\x12\x02\x08\x0f"), Test1::Message4->new({ 25 | inner => Test1::Message4::Message5->new({ 26 | value => 15, 27 | }), 28 | }), "inner message"); 29 | 30 | is(Test1::Enum::VALUE1(), 2, 'enum value'); 31 | is(Test1::Enum::VALUE2(), 7, 'enum value'); 32 | is(Test1::Enum::VALUE3(), 12, 'enum value'); 33 | 34 | is(Test1::Message4::Enum::VALUE1(), 3, 'inner enum value'); 35 | is(Test1::Message4::Enum::VALUE2(), 2, 'inner enum value'); 36 | is(Test1::Message4::Enum::VALUE3(), 1, 'inner enum value'); 37 | } 38 | 39 | { 40 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 41 | 42 | $d->load_file("test1.proto"); 43 | $d->load_file("test2.proto"); 44 | $d->map_package('test1', 'Test2'); 45 | $d->map_package('test2', 'Test3'); 46 | $d->resolve_references(); 47 | 48 | eq_or_diff(Test3::Message3->decode("\x0a\x02\x08\x01\x12\x02\x08\x01"), Test3::Message3->new({ 49 | test1_message3_message1 => Test3::Message1->new({ 50 | test2_message1 => 1, 51 | }), 52 | test1_message3_message2 => Test2::Message2->new({ 53 | test1_message2 => 1, 54 | }), 55 | }), "composite message - multiple packages"); 56 | } 57 | 58 | { 59 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 60 | 61 | $d->load_file("test1.proto"); 62 | $d->load_file("test2.proto"); 63 | $d->map_package('test1', 'Test4'); 64 | 65 | throws_ok( 66 | sub { $d->map_package('test2', 'Test4') }, 67 | qr/Package 'Test4::Message1' is being remapped/, 68 | "duplicated message", 69 | ); 70 | 71 | lives_ok( 72 | sub { $d->map_package('test1', 'Test4') }, 73 | "mapping a package multiple times is a no-op", 74 | ); 75 | } 76 | 77 | done_testing(); 78 | -------------------------------------------------------------------------------- /t/107_package_prefix_mapping.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/hierarchical'); 4 | 5 | $d->load_file("test1.proto"); 6 | $d->load_file("test2.proto"); 7 | $d->load_file("test3.proto"); 8 | $d->load_file("test4.proto"); 9 | $d->map_package_prefix('test.sub2', 'Test1::Sub2'); 10 | $d->map_package_prefix('test', 'Test1'); 11 | $d->resolve_references(); 12 | 13 | eq_or_diff(Test1::Foo->decode("\x08\x01"), Test1::Foo->new({ 14 | foo => 1, 15 | }), "exact mapping"); 16 | eq_or_diff(Test1::Sub2::Foo->decode("\x08\x01"), Test1::Sub2::Foo->new({ 17 | foo => 1, 18 | }), "exact mapping of subpackage"); 19 | eq_or_diff(Test1::sub1::Foo->decode("\x08\x01"), Test1::sub1::Foo->new({ 20 | foo => 1, 21 | }), "prefix mapping"); 22 | eq_or_diff(Test1::sub1::sub2::Foo->decode("\x08\x01"), Test1::sub1::sub2::Foo->new({ 23 | foo => 1, 24 | }), "multi-level prefix mapping"); 25 | 26 | done_testing(); 27 | -------------------------------------------------------------------------------- /t/108_message_prefix_mapping.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/message_prefix'); 4 | 5 | $d->load_file("a.proto"); 6 | $d->load_file("b.proto"); 7 | $d->load_file("r.proto"); 8 | $d->map_message_prefix('a.A', 'Test'); 9 | $d->map_message('r.Foo', 'Foo'); 10 | $d->map_message_prefix('r.Foo', 'Test'); 11 | $d->resolve_references(); 12 | 13 | eq_or_diff(Test::a::A->decode("\x0a\x02\x08\x01"), 14 | Test::a::A->new( 15 | { 16 | foo => Test::b::B->new( 17 | { 18 | bar => 1, 19 | } 20 | ) 21 | } 22 | ), 23 | "exact matching"); 24 | 25 | eq_or_diff(Foo->decode("\x0A\x04\x0A\x02\x10\x01\x10\x01"), 26 | Foo->new( 27 | { 28 | bar => Test::r::Bar->new( 29 | { 30 | foo => Foo->new( 31 | { 32 | x => 1, 33 | } 34 | ), 35 | } 36 | ), 37 | x => 1, 38 | } 39 | ), 40 | "message recursion check"); 41 | 42 | done_testing(); 43 | -------------------------------------------------------------------------------- /t/110_mixed_mapping.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 5 | 6 | $d->load_file("test1.proto"); 7 | $d->map_message('test1.Message1', 'Test1::FirstMessage'); 8 | $d->map_enum('test1.Enum', 'Test1::Enumeration'); 9 | $d->map_package('test1', 'Test1'); 10 | $d->resolve_references(); 11 | 12 | eq_or_diff(Test1::FirstMessage->decode("\x08\x01"), Test1::FirstMessage->new({ 13 | test1_message1 => 1, 14 | }), "simple message 2"); 15 | eq_or_diff(Test1::Message3->decode("\x0a\x02\x08\x01\x12\x02\x08\x01"), Test1::Message3->new({ 16 | test1_message3_message1 => Test1::FirstMessage->new({ 17 | test1_message1 => 1, 18 | }), 19 | test1_message3_message2 => Test1::Message2->new({ 20 | test1_message2 => 1, 21 | }), 22 | }), "composite message"); 23 | 24 | is(Test1::Enumeration::VALUE1(), 2, 'enum value'); 25 | is(Test1::Enumeration::VALUE2(), 7, 'enum value'); 26 | is(Test1::Enumeration::VALUE3(), 12, 'enum value'); 27 | } 28 | 29 | { 30 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 31 | 32 | $d->load_file("test1.proto"); 33 | $d->map_package('test1', 'Test2'); 34 | 35 | throws_ok( 36 | sub { $d->map_message('test1.Message1', 'Test2::FirstMessage') }, 37 | qr/Message 'test1\.Message1' has already been mapped/, 38 | "duplicate mapping", 39 | ); 40 | } 41 | 42 | done_testing(); 43 | -------------------------------------------------------------------------------- /t/115_simplified_mapping.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/mapping'); 5 | 6 | $d->load_file("test1.proto"); 7 | $d->load_file("test2.proto"); 8 | $d->load_file("test3.proto"); 9 | $d->map( 10 | { enum => 'test1.Enum', to => 'Test1::Enumeration' }, 11 | { message => 'test2.Message3', to => 'Test2::Composite' }, 12 | { service => 'test3.Service1', to => 'Test3::AService', options => { client_services => 'noop' } }, 13 | { package => 'test1', prefix => 'Test1' }, 14 | { package => 'test2', prefix => 'Test2' }, 15 | { package => 'test3', prefix => 'Test3', options => { client_services => 'noop' } }, 16 | ); 17 | 18 | eq_or_diff(Test1::Message3->decode("\x0a\x02\x08\x01\x12\x02\x08\x01"), Test1::Message3->new({ 19 | test1_message3_message1 => Test1::Message1->new({ 20 | test1_message1 => 1, 21 | }), 22 | test1_message3_message2 => Test1::Message2->new({ 23 | test1_message2 => 1, 24 | }), 25 | }), "composite message - multiple packages"); 26 | 27 | eq_or_diff(Test2::Composite->decode("\x0a\x02\x08\x01\x12\x02\x08\x01"), Test2::Composite->new({ 28 | test1_message3_message1 => Test2::Message1->new({ 29 | test2_message1 => 1, 30 | }), 31 | test1_message3_message2 => Test1::Message2->new({ 32 | test1_message2 => 1, 33 | }), 34 | }), "composite message - multiple packages"); 35 | 36 | is(Test1::Enumeration::VALUE1(), 2, 'enum value'); 37 | is(Test1::Enumeration::VALUE2(), 7, 'enum value'); 38 | is(Test1::Enumeration::VALUE3(), 12, 'enum value'); 39 | 40 | is(Test3::AService->service_descriptor->full_name, 'test3.Service1', 'explicit service mapping'); 41 | is(Test3::Service2->service_descriptor->full_name, 'test3.Service2', 'package service mapping'); 42 | } 43 | 44 | done_testing(); 45 | -------------------------------------------------------------------------------- /t/140_parse_string.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new; 4 | $d->load_string("person.proto", <<'EOT'); 5 | syntax = "proto2"; 6 | 7 | package test; 8 | 9 | message Person { 10 | required string name = 1; 11 | required int32 id = 2; 12 | optional string email = 3; 13 | } 14 | EOT 15 | 16 | $d->map({ package => 'test', prefix => 'Test' }); 17 | 18 | decode_eq_or_diff('Test::Person', "\x0a\x03foo\x10\x1f", 19 | Test::Person->new({ id => 31, name => 'foo' })); 20 | 21 | done_testing(); 22 | -------------------------------------------------------------------------------- /t/145_parse_binary.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new; 5 | # protoc -o t/proto/person.pb t/proto/person.proto 6 | $d->load_serialized_string(_slurp('t/proto/person.pb')); 7 | 8 | $d->map({ package => 'test', prefix => 'Test1' }); 9 | 10 | decode_eq_or_diff('Test1::Person', "\x0a\x03foo\x10\x1f", 11 | Test1::Person->new({ id => 31, name => 'foo' })); 12 | } 13 | 14 | # check no WKTs are mapped since none is used 15 | ok(!Google::ProtocolBuffers::Dynamic::WKT::Timestamp->can('new')); 16 | ok(!Google::ProtocolBuffers::Dynamic::WKT::Duration->can('new')); 17 | 18 | # uses multiple Google::ProtocolBuffers::Dynamic instances on purpose 19 | { 20 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 21 | # protoc --include_imports -o t/proto/wkt/timestamp.pb t/proto/wkt/timestamp.proto 22 | $d->load_serialized_string(_slurp('t/proto/wkt/timestamp.pb')); 23 | 24 | $d->map({ package => 'test', prefix => 'Test2' }); 25 | 26 | decode_eq_or_diff('Test2::Basic', "\x60\x03\x0a\x04\x08\x0f\x10\x11", 27 | Test2::Basic->new({ 28 | timestamp_f => Google::ProtocolBuffers::Dynamic::WKT::Timestamp->new({ seconds => 15, nanos => 17 }) 29 | })); 30 | } 31 | 32 | # check only Timestamp is mapped 33 | ok(Google::ProtocolBuffers::Dynamic::WKT::Timestamp->can('new')); 34 | ok(!Google::ProtocolBuffers::Dynamic::WKT::Duration->can('new')); 35 | 36 | # uses multiple Google::ProtocolBuffers::Dynamic instances on purpose 37 | { 38 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 39 | # protoc --include_imports -o t/proto/wkt/scalar.pb t/proto/wkt/scalar.proto 40 | $d->load_serialized_string(_slurp('t/proto/wkt/scalar.pb')); 41 | 42 | $d->map({ package => 'test', prefix => 'Test3' }); 43 | 44 | decode_eq_or_diff('Test3::Basic', "\x60\x03\x0a\x04\x08\x0f\x10\x11", 45 | Test3::Basic->new({ 46 | timestamp_f => Google::ProtocolBuffers::Dynamic::WKT::Timestamp->new({ seconds => 15, nanos => 17 }) 47 | })); 48 | } 49 | 50 | # check all WKTs are mapped now 51 | ok(Google::ProtocolBuffers::Dynamic::WKT::Timestamp->can('new')); 52 | ok(Google::ProtocolBuffers::Dynamic::WKT::Duration->can('new')); 53 | 54 | # uses multiple Google::ProtocolBuffers::Dynamic instances on purpose 55 | { 56 | my $d = Google::ProtocolBuffers::Dynamic->new; 57 | # protoc --include_imports -I t/proto -o t/proto/wkt/scalar_copies.pb t/proto/wkt/scalar_copies.proto 58 | $d->load_serialized_string(_slurp('t/proto/wkt/scalar_copies.pb')); 59 | 60 | $d->map({ package => 'test', prefix => 'Test4' }); 61 | } 62 | 63 | done_testing(); 64 | 65 | sub _slurp { 66 | open my $fh, '<', $_[0]; 67 | binmode $fh; 68 | local $/; 69 | readline $fh; 70 | } 71 | -------------------------------------------------------------------------------- /t/200_check_required.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 5 | $d->load_file("options.proto"); 6 | $d->map({ package => 'test', prefix => 'Test1', options => { check_required_fields => 1 } }); 7 | 8 | decode_eq_or_diff( 9 | 'Test1::Required', "\x08\x02", 10 | Test1::Required->new({ value => 2 }), 11 | ); 12 | 13 | decode_throws_ok( 14 | 'Test1::Required', "", 15 | qr/Deserialization failed: Missing required field test.Required.value/, 16 | qr/Deserialization failed: Missing required field test.Required.value/, 17 | "Missing required field", 18 | ); 19 | } 20 | 21 | { 22 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 23 | $d->load_file("options.proto"); 24 | $d->map({ package => 'test', prefix => 'Test2', options => { check_required_fields => 0 } }); 25 | 26 | decode_eq_or_diff( 27 | 'Test2::Required', "\x08\x02", 28 | Test2::Required->new({ value => 2 }), 29 | ); 30 | 31 | decode_eq_or_diff( 32 | 'Test2::Required', "", 33 | Test2::Required->new({}), 34 | ); 35 | } 36 | 37 | done_testing(); 38 | -------------------------------------------------------------------------------- /t/205_explicit_defaults.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 5 | $d->load_file("options.proto"); 6 | $d->map({ package => 'test', prefix => 'Test1', options => { explicit_defaults => 1 } }); 7 | 8 | decode_eq_or_diff( 9 | 'Test1::Defaults', "\x08\x02", 10 | Test1::Defaults->new({ 11 | int32_f => 2, 12 | uint32_f => 8, 13 | int64_f => 9, 14 | uint64_f => 10, 15 | float_f => 1.25, 16 | double_f => 2.125, 17 | string_f => "abcde", 18 | bytes_f => "def", 19 | enum_f => 2, 20 | }), 21 | ); 22 | 23 | decode_eq_or_diff( 24 | 'Test1::Defaults', "", 25 | Test1::Defaults->new({ 26 | int32_f => 7, 27 | uint32_f => 8, 28 | int64_f => 9, 29 | uint64_f => 10, 30 | float_f => 1.25, 31 | double_f => 2.125, 32 | string_f => "abcde", 33 | bytes_f => "def", 34 | enum_f => 2, 35 | }), 36 | ); 37 | } 38 | 39 | { 40 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 41 | $d->load_file("options.proto"); 42 | $d->map({ package => 'test', prefix => 'Test2', options => { explicit_default => 0 } }); 43 | 44 | decode_eq_or_diff( 45 | 'Test2::Defaults', "\x08\x02", 46 | Test2::Defaults->new({ int32_f => 2 }), 47 | ); 48 | 49 | decode_eq_or_diff( 50 | 'Test2::Defaults', "", 51 | Test2::Defaults->new({}), 52 | ); 53 | } 54 | 55 | done_testing(); 56 | -------------------------------------------------------------------------------- /t/212_ignore_undef_values.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 5 | $d->load_file("map.proto"); 6 | $d->map({ package => 'test', prefix => 'Test1', options => { encode_defaults_proto3 => 1 } }); 7 | 8 | my $encoded; 9 | 10 | warning_like(sub { 11 | $encoded = Test1::Item->encode({ one_value => undef }); 12 | }, qr/While encoding field 'one_value': Use of uninitialized value in subroutine entry/); 13 | eq_or_diff($encoded, "\x08\x00"); 14 | 15 | warning_like(sub { 16 | $encoded = Test1::StringMap->encode({ string_int32_map => { a => undef } }); 17 | }, qr/While encoding field 'string_int32_map.\{a\}': Use of uninitialized value in subroutine entry/); 18 | eq_or_diff($encoded, "\x0a\x05\x0a\x01a\x10\x00"); 19 | } 20 | 21 | { 22 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 23 | $d->load_file("map.proto"); 24 | $d->map({ package => 'test', prefix => 'Test2', options => { ignore_undef_fields => 1, encode_defaults_proto3 => 1 } }); 25 | 26 | my $encoded; 27 | warning_like(sub { 28 | $encoded = Test2::Item->encode({ one_value => undef }); 29 | }, undef); 30 | eq_or_diff($encoded, ''); 31 | 32 | warning_like(sub { 33 | $encoded = Test2::StringMap->encode({ string_int32_map => { a => undef } }); 34 | }, undef); 35 | eq_or_diff($encoded, ''); 36 | } 37 | 38 | done_testing(); 39 | -------------------------------------------------------------------------------- /t/215_check_enum_values.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 5 | $d->load_file("enum.proto"); 6 | $d->map({ package => 'test', prefix => 'Test1', options => { check_enum_values => 1 } }); 7 | 8 | decode_eq_or_diff( 9 | 'Test1::MessageBefore', Test1::MessageAfter->encode({ value => 3, array => [3, 2, 1] }), 10 | Test1::MessageBefore->new({ array => [1, 2, 1] }), 11 | "unknown enum value uses default in deserialization", 12 | ); 13 | 14 | throws_ok( 15 | sub { Test1::MessageBefore->encode({ value => 3 }) }, 16 | qr/Invalid enumeration value 3 for field 'test.MessageBefore.value'/, 17 | "unknown enum value croaks in serialization" 18 | ); 19 | 20 | throws_ok( 21 | sub { Test1::MessageBefore->encode({ array => [3, 2] }) }, 22 | qr/Invalid enumeration value 3 for field 'test.MessageBefore.array'/, 23 | "unknown enum value croaks in serialization" 24 | ); 25 | 26 | throws_ok( 27 | sub { Test1::MessageBefore->new->set_value(3) }, 28 | qr/Invalid value 3 for enumeration field 'test.MessageBefore.value'/, 29 | "unknown enum value croaks on set" 30 | ); 31 | } 32 | 33 | { 34 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 35 | $d->load_file("enum.proto"); 36 | $d->map({ package => 'test', prefix => 'Test2', options => { check_enum_values => 0 } }); 37 | 38 | decode_eq_or_diff( 39 | 'Test2::MessageBefore', Test2::MessageAfter->encode({ value => 3, array => [3, 2, 1] }), 40 | Test2::MessageBefore->new({ value => 3, array => [3, 2, 1] }), 41 | ); 42 | 43 | eq_or_diff( 44 | Test2::MessageBefore->encode({ value => 3 }), 45 | "\x08\x03", 46 | ); 47 | 48 | eq_or_diff( 49 | Test2::MessageBefore->encode({ array => [3, 2] }), 50 | "\x10\x03\x10\x02", 51 | ); 52 | 53 | lives_ok( 54 | sub { Test2::MessageBefore->new->set_value(3) }, 55 | "unknown enum value croaks in serialization" 56 | ); 57 | } 58 | 59 | done_testing(); 60 | -------------------------------------------------------------------------------- /t/225_decode_unblessed.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("person.proto"); 5 | $d->map({ package => 'test', prefix => 'Test', options => { decode_blessed => 0 } }); 6 | 7 | my $p_bytes = "\x0a\x03foo\x10\x1f"; 8 | my $p = Test::Person->decode($p_bytes); 9 | my $pj = Test::Person->decode_json('{"id":31,"name":"foo"}'); 10 | 11 | decode_eq_or_diff('Test::Person', $p_bytes, { id => 31, name => 'foo' }); 12 | eq_or_diff($pj, { id => 31, name => 'foo' }); 13 | 14 | my $pa_bytes = "\x0a\x07\x0a\x03foo\x10\x1f" . 15 | "\x0a\x06\x0a\x02ba\x10\x20"; 16 | my $pa = Test::PersonArray->decode($pa_bytes); 17 | my $paj = Test::PersonArray->decode_json('{"persons":[{"name":"foo","id":31},{"name":"ba","id":32}]}'); 18 | 19 | decode_eq_or_diff('Test::PersonArray', $pa_bytes, { 20 | persons => [ 21 | { id => 31, name => 'foo' }, 22 | { id => 32, name => 'ba' }, 23 | ], 24 | }); 25 | eq_or_diff($paj,{ 26 | persons => [ 27 | { id => 31, name => 'foo' }, 28 | { id => 32, name => 'ba' }, 29 | ], 30 | }); 31 | 32 | eq_or_diff(Test::Person->encode($p), "\x0a\x03foo\x10\x1f"); 33 | eq_or_diff(Test::PersonArray->encode($pa), "\x0a\x07\x0a\x03foo\x10\x1f" . 34 | "\x0a\x06\x0a\x02ba\x10\x20"); 35 | 36 | eq_or_diff(Test::Person->encode_json($p), '{"name":"foo","id":31}'); 37 | eq_or_diff(Test::PersonArray->encode_json($pa), '{"persons":[{"name":"foo","id":31},{"name":"ba","id":32}]}'); 38 | 39 | done_testing(); 40 | -------------------------------------------------------------------------------- /t/235_fail_ref_coercion.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | use if $] < 5.014, 'Test::More' => 'skip_all' => 'Not available on Perl < 5.14'; 3 | 4 | { 5 | package TestObj; 6 | 7 | sub new { bless {}, __PACKAGE__ } 8 | 9 | package TestOvl; 10 | 11 | use overload ( 12 | '0+' => sub { 123 }, 13 | ); 14 | 15 | sub new { bless {}, __PACKAGE__ } 16 | } 17 | 18 | { 19 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 20 | $d->load_file("scalar.proto"); 21 | $d->load_file("repeated.proto"); 22 | $d->map({ package => 'test', prefix => 'Test1', options => { fail_ref_coercion => 1 } }); 23 | 24 | my $ref = \1; 25 | my $obj = TestObj->new; 26 | my $ovl = TestOvl->new; 27 | 28 | throws_ok( 29 | sub { Test1::Basic->encode({ string_f => $ref }) }, 30 | qr/Reference used when a scalar value is expected for field 'test.Basic.string_f'/, 31 | "ref for scalar croaks in serialization" 32 | ); 33 | 34 | throws_ok( 35 | sub { Test1::Basic->encode({ string_f => $obj }) }, 36 | qr/Reference used when a scalar value is expected for field 'test.Basic.string_f'/, 37 | "obj for scalar croaks in serialization" 38 | ); 39 | 40 | throws_ok( 41 | sub { Test1::Repeated->encode({ string_f => ['a', $ref] }) }, 42 | qr/Reference used when a scalar value is expected for field 'test.Repeated.string_f'/, 43 | "ref for repeated scalar croaks in serialization" 44 | ); 45 | 46 | decode_eq_or_diff( 47 | 'Test1::Repeated', Test1::Repeated->encode({ string_f => ['a', $ovl] }), 48 | Test1::Repeated->new({ 49 | string_f => ['a', "123"], 50 | }), 51 | ); 52 | } 53 | 54 | { 55 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 56 | $d->load_file("scalar.proto"); 57 | $d->load_file("repeated.proto"); 58 | $d->map({ package => 'test', prefix => 'Test2', options => { fail_ref_coercion => 0 } }); 59 | 60 | my $ref = \1; 61 | my $obj = TestObj->new; 62 | my $ovl = TestOvl->new; 63 | 64 | decode_eq_or_diff( 65 | 'Test2::Basic', Test2::Basic->encode({ string_f => $ref }), 66 | Test2::Basic->new({ 67 | string_f => "$ref", 68 | }), 69 | ); 70 | 71 | decode_eq_or_diff( 72 | 'Test2::Basic', Test2::Basic->encode({ string_f => $obj }), 73 | Test2::Basic->new({ 74 | string_f => "$obj", 75 | }), 76 | ); 77 | 78 | decode_eq_or_diff( 79 | 'Test2::Repeated', Test2::Repeated->encode({ string_f => ['a', $ref] }), 80 | Test2::Repeated->new({ 81 | string_f => ['a', "$ref"], 82 | }), 83 | ); 84 | 85 | decode_eq_or_diff( 86 | 'Test2::Repeated', Test2::Repeated->encode({ string_f => ['a', $ovl] }), 87 | Test2::Repeated->new({ 88 | string_f => ['a', "123"], 89 | }), 90 | ); 91 | } 92 | 93 | done_testing(); 94 | -------------------------------------------------------------------------------- /t/241_decode_to_fieldtable.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $mtt = $Google::ProtocolBuffers::Dynamic::Fieldtable::debug_decoder_transform; 5 | 6 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 7 | $d->load_file("person3.proto"); 8 | $d->load_file("map.proto"); 9 | $d->map({ package => 'test', 'prefix' => 'Test1', options => { decode_blessed => 0 } }); 10 | Test1::Person3->set_decoder_options({ fieldtable => 1, transform => $mtt }); 11 | Test1::Person3Array->set_decoder_options({ fieldtable => 1, transform => $mtt }); 12 | Test1::StringMap->set_decoder_options({ fieldtable => 1, transform => $mtt }); 13 | 14 | my $persons_bytes = Test1::Person3Array->encode({ 15 | persons => [ 16 | { name => 'test1' }, 17 | { id => 2, 'email' => 'abc' }, 18 | ], 19 | }); 20 | 21 | decode_eq_or_diff('Test1::Person3Array', $persons_bytes, [ 22 | [1, [ 23 | [ [1, 'test1'] ], 24 | [ [2, 2], [3, 'abc'] ] 25 | ] ], 26 | ]); 27 | 28 | my $map_bytes = Test1::StringMap->encode({ 29 | string_int32_map => { a => 1, b => 2 }, 30 | }); 31 | 32 | decode_eq_or_diff('Test1::StringMap', $map_bytes, [ [1, { a => 1, b => 2 }] ]); 33 | } 34 | 35 | done_testing(); 36 | -------------------------------------------------------------------------------- /t/241_encode_to_fieldtable.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | # transformation set on the message 4 | { 5 | my $mtt = $Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_transform; 6 | 7 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 8 | $d->load_file("person3.proto"); 9 | $d->map({ package => 'test', 'prefix' => 'Test1', options => { decode_blessed => 0 } }); 10 | Test1::Person3->set_encoder_options({ fieldtable => 1, transform => $mtt }); 11 | Test1::Person3Array->set_encoder_options({ fieldtable => 1, transform => $mtt }); 12 | 13 | my $person_bytes = Test1::Person3->encode([ 14 | [ 2 => id => 2 ], [ 3 => email => 'abc' ], 15 | ]); 16 | 17 | decode_eq_or_diff('Test1::Person3', $person_bytes, { 18 | id => 2, email => 'abc', 19 | }); 20 | 21 | my $persons_bytes = Test1::Person3Array->encode([ 22 | [ 1 => persons => [ 23 | [ [ 1 => name => 'test1' ] ], 24 | [ [ 2 => id => 2 ], [ 3 => 'email' => 'abc' ] ], 25 | ] ], 26 | ]); 27 | 28 | decode_eq_or_diff('Test1::Person3Array', $persons_bytes, { 29 | persons => [ 30 | { name => 'test1' }, 31 | { id => 2, email => 'abc' }, 32 | ], 33 | }); 34 | } 35 | 36 | # transformation set on a single field 37 | { 38 | my $mtt = $Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_transform; 39 | 40 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 41 | $d->load_file("transform/encoder.proto"); 42 | $d->map({ package => 'test', 'prefix' => 'Test2', options => { decode_blessed => 0 } }); 43 | Test2::ContainerMessage->set_encoder_options({ fieldtable => 1, transform_fields => { scalar => $mtt } }); 44 | 45 | my $container_bytes = Test2::ContainerMessage->encode({ 46 | scalar => [ [ 1 => id => 2 ], [ 2 => name => 'abc' ] ], 47 | }); 48 | 49 | decode_eq_or_diff('Test2::ContainerMessage', $container_bytes, { 50 | scalar => { id => 2, name => 'abc' }, 51 | }); 52 | } 53 | 54 | # check required fields 55 | { 56 | my $mtt = $Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_transform; 57 | 58 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 59 | $d->load_file("person.proto"); 60 | $d->map({ package => 'test', 'prefix' => 'Test3', options => { decode_blessed => 0 } }); 61 | Test3::Person->set_encoder_options({ fieldtable => 1, transform => $mtt }); 62 | 63 | my $person_bytes = Test3::Person->encode([ 64 | [ 2 => id => 2 ], [ 1 => name => 'abc' ], 65 | ]); 66 | 67 | decode_eq_or_diff('Test1::Person3', $person_bytes, { 68 | id => 2, name => 'abc', 69 | }); 70 | 71 | throws_ok( 72 | sub { 73 | Test3::Person->encode([ 74 | [ 2 => id => 2 ], [ 3 => email => 'abc' ], 75 | ]) 76 | }, 77 | qr/Serialization failed: Missing required field 'test.Person.name'/, 78 | ); 79 | } 80 | 81 | # trahsformation fails 82 | { 83 | my $mtt = $Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_transform; 84 | 85 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 86 | $d->load_file("person3.proto"); 87 | $d->map({ package => 'test', 'prefix' => 'Test4', options => { decode_blessed => 0 } }); 88 | Test4::Person3->set_encoder_options({ fieldtable => 1, transform => $mtt }); 89 | 90 | my $person_bytes = Test4::Person3->encode([ 91 | ]); 92 | 93 | eq_or_diff($person_bytes, ''); 94 | } 95 | 96 | done_testing(); 97 | -------------------------------------------------------------------------------- /t/241_encode_unknown_fields.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | { 4 | my $mtt = $Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_unknown_fields; 5 | 6 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 7 | $d->load_file("person3.proto"); 8 | $d->map({ package => 'test', 'prefix' => 'Test1', options => { decode_blessed => 0 } }); 9 | Test1::Person3->set_encoder_options({ unknown_field => $mtt }); 10 | Test1::Person3Array->set_encoder_options({ unknown_field => $mtt }); 11 | 12 | my $person_bytes = Test1::Person3->encode({ 13 | id => 2, foo => { x => 1 } 14 | }); 15 | 16 | eq_or_diff($person_bytes, "\x10\x02"); 17 | eq_or_diff(Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_unknown_fields_get(), [ 18 | ['foo', { x => 1 }], 19 | ]); 20 | 21 | my $person_array_bytes = Test1::Person3Array->encode({ 22 | persons => [ 23 | { 24 | id => 2, foo => 'w' 25 | }, 26 | { 27 | id => 3 28 | }, 29 | { 30 | id => 4, bar => 'xx' 31 | }, 32 | ], 33 | }); 34 | 35 | eq_or_diff($person_array_bytes, "\x0a\x02\x10\x02\x0a\x02\x10\x03\x0a\x02\x10\x04"); 36 | eq_or_diff(Google::ProtocolBuffers::Dynamic::Fieldtable::debug_encoder_unknown_fields_get(), [ 37 | ['persons', 0, 'foo', 'w'], 38 | ['persons', 2, 'bar', 'xx'], 39 | ]); 40 | } 41 | 42 | done_testing(); 43 | -------------------------------------------------------------------------------- /t/250_decode_callbacks_sanity.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my @no_blessed = (options => { decode_blessed => 0 }); 4 | 5 | # transformation 6 | { 7 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 8 | $d->load_file("transform/decoder.proto"); 9 | $d->map({ package => 'test', prefix => 'Test1', @no_blessed }); 10 | 11 | { 12 | # using this variable is just to force the callback to be a closure 13 | # as opposed to just an anonymous function, this, together with 14 | # the extra scope is to test proper reference counting of callbacks 15 | my $key = 'values'; 16 | my $strip_repeated_wrapper = { transform => sub { $_[0] = $_[0]->{$key} } }; 17 | Test1::Int32Array->set_decoder_options($strip_repeated_wrapper); 18 | Test1::Int32ArrayArray->set_decoder_options($strip_repeated_wrapper); 19 | } 20 | 21 | # nested messages 22 | decode_eq_or_diff('Test1::Message', Test1::Message->encode({ 23 | nested_array => [{ 24 | values => [{ 25 | values => [1, 2, 3], 26 | }], 27 | }, { 28 | values => [{ 29 | values => [4, 5, 6], 30 | }, { 31 | values => [7, 8, 9], 32 | }], 33 | }], 34 | }), { 35 | nested_array => [ [ [1, 2, 3] ], [ [4, 5, 6], [7, 8, 9] ] ], 36 | }); 37 | 38 | # top-level message 39 | decode_eq_or_diff('Test1::Int32Array', Test1::Int32Array->encode({ 40 | values => [1, 2, 3], 41 | }), 42 | [1, 2, 3] 43 | ); 44 | } 45 | 46 | # no-op cases 47 | my $noop_index = 0; 48 | for my $noop (sub { $_[0] = 42 }, sub { }, sub { return }, sub { $_[0] }) { 49 | my $prefix = 'TestNoop' . ++$noop_index; 50 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 51 | $d->load_file("transform/decoder.proto"); 52 | $d->map({ package => 'test', prefix => $prefix, @no_blessed }); 53 | 54 | my $package = "${prefix}::Int32Array"; 55 | $package->set_decoder_options({ transform => $noop }); 56 | 57 | if ($noop_index == 1) { 58 | # check the test is sane 59 | decode_eq_or_diff($package, $package->encode({ 60 | values => [1, 2, 3], 61 | }), 62 | 42, 63 | ); 64 | } else { 65 | # actually check what we need to check 66 | decode_eq_or_diff($package, $package->encode({ 67 | values => [1, 2, 3], 68 | }), { 69 | values => [1, 2, 3], 70 | }); 71 | } 72 | } 73 | 74 | # concatenated messages (callbacks are only triggered once) 75 | { 76 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 77 | $d->load_file("transform/decoder.proto"); 78 | $d->map({ package => 'test', prefix => 'Test3', @no_blessed }); 79 | 80 | my $strip_repeated_wrapper = { transform => sub { $_[0] = $_[0]->{values} } }; 81 | Test3::Int32Array->set_decoder_options($strip_repeated_wrapper); 82 | Test3::Int32ArrayArray->set_decoder_options($strip_repeated_wrapper); 83 | 84 | # nested messages 85 | decode_eq_or_diff('Test3::Message', Test3::Message->encode({ 86 | concatenated_array => { 87 | values => [{ 88 | values => [1, 2, 3], 89 | }, { 90 | values => [4, 5, 6], 91 | }], 92 | }, 93 | }) . Test3::Message->encode({ 94 | concatenated_array => { 95 | values => [{ 96 | values => [7, 8, 9], 97 | }], 98 | }, 99 | }), { 100 | concatenated_array => [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ], 101 | }); 102 | } 103 | 104 | done_testing(); 105 | -------------------------------------------------------------------------------- /t/250_encode_callback_sanity.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my @no_blessed = (options => { decode_blessed => 0 }); 4 | 5 | # transformation 6 | { 7 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 8 | $d->load_file("transform/encoder.proto"); 9 | $d->map({ package => 'test', prefix => 'Test1', @no_blessed }); 10 | 11 | { 12 | # using this variable is just to force the callback to be a closure 13 | # as opposed to just an anonymous function, this, together with 14 | # the extra scope is to test proper reference counting of callbacks 15 | my $key = 'values'; 16 | my $add_repeated_wrapper = { transform => sub { $_[0] = { $key => $_[1] } } }; 17 | my $add_tied_map_wrapper = { transform => sub { $_[0] = tied_hash($key => $_[1]) } }; 18 | 19 | Test1::Int32Array->set_encoder_options($add_repeated_wrapper); 20 | Test1::Int32ArrayArray->set_encoder_options($add_repeated_wrapper); 21 | Test1::StringInt32MapTied->set_encoder_options($add_tied_map_wrapper); 22 | } 23 | 24 | # invalid value for wrapper 25 | throws_ok( 26 | sub { 27 | Test1::ContainerMessage->encode({ 28 | array_wrapper => { a => 2, b => 3 }, 29 | }); 30 | }, 31 | qr/Not an array reference when encoding field 'test.Int32ArrayArray.values'/ 32 | ); 33 | 34 | # nested messages 35 | eq_or_diff(Test1::ContainerMessage->decode(Test1::ContainerMessage->encode({ 36 | array_wrapper => [[1, 2], [3, 4]], 37 | })), { 38 | array_wrapper => { 39 | values => [ 40 | { values => [1, 2] }, 41 | { values => [3, 4] }, 42 | ], 43 | }, 44 | }); 45 | 46 | # top-level message 47 | eq_or_diff(Test1::Int32Array->decode(Test1::Int32Array->encode( 48 | [1, 2, 3] 49 | )), { 50 | values => [1, 2, 3], 51 | }); 52 | 53 | # tied map value 54 | eq_or_diff(Test1::ContainerMessage->decode(Test1::ContainerMessage->encode({ 55 | tied_map_wrapper => { a => 1 }, 56 | })), { 57 | tied_map_wrapper => { 58 | values => { 59 | a => 1, 60 | }, 61 | }, 62 | }); 63 | } 64 | 65 | done_testing(); 66 | -------------------------------------------------------------------------------- /t/251_decode_callbacks_invalid_fields.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my @no_blessed = (options => { decode_blessed => 0 }); 4 | 5 | # invalid fields 6 | { 7 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 8 | $d->load_file("transform/decoder.proto"); 9 | $d->map({ package => 'test', prefix => 'Test1', @no_blessed }); 10 | 11 | for my $field_name (qw( 12 | not_message 13 | repeated_not_message 14 | repeated_message 15 | map_field 16 | )) { 17 | throws_ok( 18 | sub { 19 | Test1::InvalidFields->set_decoder_options({ 20 | transform_fields => { 21 | $field_name => sub { $_[0] }, 22 | }, 23 | }); 24 | }, 25 | qr/^Can't apply transformation to field $field_name /, 26 | ); 27 | } 28 | } 29 | 30 | done_testing(); 31 | -------------------------------------------------------------------------------- /t/255_decode_callbacks_field.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my @no_blessed = (options => { decode_blessed => 0 }); 4 | 5 | # transformed 6 | { 7 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 8 | $d->load_file("transform/decoder.proto"); 9 | $d->map({ package => 'test', prefix => 'Test1', @no_blessed }); 10 | 11 | my $strip_repeated_wrapper = { 12 | transform_fields => { 13 | transformed => sub { $_[0] = $_[0]->{values} }, 14 | } 15 | }; 16 | Test1::FieldMessage->set_decoder_options($strip_repeated_wrapper); 17 | 18 | decode_eq_or_diff('Test1::FieldMessage', Test1::FieldMessage->encode({ 19 | transformed => { 20 | values => [1, 2, 3], 21 | }, 22 | original => { 23 | values => [4, 5, 6], 24 | }, 25 | }),{ 26 | transformed => [1, 2, 3], 27 | original => { 28 | values => [4, 5, 6], 29 | }, 30 | }); 31 | } 32 | 33 | # field transform overrides message 34 | { 35 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 36 | $d->load_file("transform/decoder.proto"); 37 | $d->map({ package => 'test', prefix => 'Test2', @no_blessed }); 38 | 39 | my $strip_repeated_wrapper = { 40 | transform_fields => { 41 | transformed => sub { $_[0] = $_[0]->{values} }, 42 | } 43 | }; 44 | my $rename_repeated_wrapper = { 45 | transform => sub { $_[0] = { pretty_values => $_[0]->{values} } }, 46 | }; 47 | Test2::Int32Array->set_decoder_options($rename_repeated_wrapper); 48 | Test2::FieldMessage->set_decoder_options($strip_repeated_wrapper); 49 | 50 | decode_eq_or_diff('Test2::FieldMessage', Test2::FieldMessage->encode({ 51 | transformed => { 52 | values => [1, 2, 3], 53 | }, 54 | original => { 55 | values => [4, 5, 6], 56 | }, 57 | }),{ 58 | transformed => [1, 2, 3], 59 | original => { 60 | pretty_values => [4, 5, 6], 61 | }, 62 | }); 63 | } 64 | 65 | done_testing(); 66 | -------------------------------------------------------------------------------- /t/260_decode_callback_map.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my @no_blessed = (options => { decode_blessed => 0 }); 4 | 5 | # map values 6 | { 7 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 8 | $d->load_file("transform/decoder.proto"); 9 | $d->map({ package => 'test', prefix => 'Test1', @no_blessed }); 10 | 11 | my $strip_repeated_wrapper = { transform => sub { $_[0] = $_[0]->{values} } }; 12 | Test1::Int32Array->set_decoder_options($strip_repeated_wrapper); 13 | 14 | decode_eq_or_diff('Test1::MapMessage', Test1::MapMessage->encode({ 15 | map_values => { 16 | key1 => { 17 | values => [1, 2, 3], 18 | }, 19 | }, 20 | }), { 21 | map_values => { 22 | key1 => [1, 2, 3], 23 | }, 24 | }); 25 | } 26 | 27 | done_testing(); 28 | -------------------------------------------------------------------------------- /t/300_scalar_accessors.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("scalar.proto"); 5 | $d->map({ package => 'test', prefix => 'Test' }); 6 | 7 | my %default_defaults = ( 8 | double_f => '0', 9 | float_f => '0', 10 | int32_f => 0, 11 | int64_f => 0, 12 | uint32_f => 0, 13 | uint64_f => 0, 14 | bool_f => '', 15 | string_f => '', 16 | bytes_f => '', 17 | enum_f => 1, 18 | ); 19 | 20 | my %test_defaults = ( 21 | double_f => 1.0, 22 | float_f => 2.0, 23 | int32_f => 3, 24 | int64_f => 4, 25 | uint32_f => 5, 26 | uint64_f => 6, 27 | bool_f => 1, 28 | string_f => "a string", 29 | bytes_f => "some bytes", 30 | enum_f => 3, 31 | ); 32 | 33 | my %get_values = ( 34 | double_f => 11.0, 35 | float_f => 12.0, 36 | int32_f => 23, 37 | int64_f => 34, 38 | uint32_f => 45, 39 | uint64_f => 56, 40 | bool_f => 1, 41 | string_f => "a get string", 42 | bytes_f => "some get bytes", 43 | enum_f => 2, 44 | ); 45 | 46 | my %set_values = ( 47 | double_f => 11.0, 48 | float_f => 12.0, 49 | int32_f => 13, 50 | int64_f => 14, 51 | uint32_f => 15, 52 | uint64_f => 16, 53 | bool_f => 1, 54 | string_f => "a set string", 55 | bytes_f => "some set bytes", 56 | enum_f => 2, 57 | ); 58 | 59 | my $default_defaults = Test::Default->decode(''); 60 | my $basic_defaults = Test::Basic->decode(''); 61 | 62 | for my $field (sort keys %default_defaults) { 63 | my $with_field = bless { $field => $get_values{$field} }, 'Test::Basic'; 64 | my $cleared = bless { $field => $get_values{$field} }, 'Test::Basic'; 65 | my $empty = bless {}, 'Test::Basic'; 66 | my $has = "has_$field"; 67 | my $get = "get_$field"; 68 | my $set = "set_$field"; 69 | my $clear = "clear_$field"; 70 | 71 | ok(!$empty->$has, "empty '$field'"); 72 | is($empty->$get, $default_defaults{$field}, "empty '$field' is default"); 73 | 74 | $empty->$set($set_values{$field}); 75 | $cleared->$clear; 76 | 77 | ok(!$default_defaults->$has, "field '$field' is not present"); 78 | ok($with_field->$has, "field '$field' is present"); 79 | is($with_field->$get, $get_values{$field}, "getter for '$field' works"); 80 | is($empty->$get, $set_values{$field}, "setter for '$field' works"); 81 | is($default_defaults->$get, $test_defaults{$field}, "default default for '$field'"); 82 | is($basic_defaults->$get, $default_defaults{$field}, "custom default for '$field'"); 83 | ok(!$cleared->$has, "cleared '$field'"); 84 | is($cleared->$get, $default_defaults{$field}, "cleared '$field' is default"); 85 | } 86 | 87 | throws_ok( 88 | sub { Test::Basic->new->set_enum_f(77) }, 89 | qr/Invalid value 77 for enumeration field 'test.Basic.enum_f'/, 90 | 'invalid enum value' 91 | ); 92 | 93 | done_testing(); 94 | -------------------------------------------------------------------------------- /t/301_boolean_accessors.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $encoded_true = "\x38\x01"; 4 | my $encoded_false = "\x38\x00"; 5 | my $encoded_default = ""; 6 | 7 | { 8 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 9 | $d->load_file("scalar.proto"); 10 | $d->map_message("test.Basic", "PerlBasic", { explicit_defaults => 1, boolean_values => 'perl' }); 11 | $d->resolve_references(); 12 | 13 | my $obj = PerlBasic->new; 14 | 15 | is($obj->get_bool_f, ''); 16 | 17 | $obj->set_bool_f("a"); 18 | is($obj->get_bool_f, 1); 19 | 20 | $obj->set_bool_f(0); 21 | is($obj->get_bool_f, ''); 22 | } 23 | 24 | { 25 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 26 | $d->load_file("scalar.proto"); 27 | $d->map_message("test.Basic", "NumericBasic", { explicit_defaults => 1, boolean_values => 'numeric' }); 28 | $d->resolve_references(); 29 | 30 | my $obj = NumericBasic->new; 31 | 32 | is($obj->get_bool_f, 0); 33 | 34 | $obj->set_bool_f("a"); 35 | is($obj->get_bool_f, 1); 36 | 37 | $obj->set_bool_f(''); 38 | is($obj->get_bool_f, 0); 39 | } 40 | 41 | done_testing(); 42 | 43 | -------------------------------------------------------------------------------- /t/305_oneof_accessors.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("oneof.proto"); 5 | $d->map({ package => 'test', prefix => 'Test' }); 6 | 7 | my $obj = Test::OneOf1->new; 8 | 9 | $obj->set_value1(7); 10 | 11 | is($obj->get_value2, ''); 12 | is($obj->get_value3, 0); 13 | is($obj->get_value4, 0); 14 | 15 | $obj->set_value2('abc'); 16 | 17 | is($obj->get_value2, 'abc'); 18 | is($obj->get_value3, 0); 19 | is($obj->get_value4, 0); 20 | 21 | $obj->set_value3(7); 22 | 23 | is($obj->get_value2, ''); 24 | is($obj->get_value3, 7); 25 | is($obj->get_value4, 0); 26 | 27 | $obj->set_value4(9); 28 | 29 | is($obj->get_value2, ''); 30 | is($obj->get_value3, 0); 31 | is($obj->get_value4, 9); 32 | 33 | $obj->set_value2('abc'); 34 | 35 | is($obj->get_value2, 'abc'); 36 | is($obj->get_value3, 0); 37 | is($obj->get_value4, 0); 38 | 39 | is($obj->get_value1, 7); 40 | 41 | done_testing(); 42 | -------------------------------------------------------------------------------- /t/315_message_accessors.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("message.proto"); 5 | $d->map({ package => 'test', prefix => 'Test' }); 6 | 7 | my $obj = Test::OuterWithMessage->new; 8 | my $empty = {}; 9 | my $inner = Test::Inner->new; 10 | 11 | eq_or_diff($obj->get_optional_inner, undef); 12 | 13 | $obj->set_optional_inner($empty); 14 | eq_or_diff($obj->get_optional_inner, $empty); 15 | 16 | $obj->set_optional_inner($inner); 17 | eq_or_diff($obj->get_optional_inner, $inner); 18 | 19 | $obj->set_optional_inner(undef); 20 | eq_or_diff($obj->get_optional_inner, undef); 21 | 22 | throws_ok( 23 | sub { $obj->set_optional_inner(123) }, 24 | qr/Value for message field 'test.OuterWithMessage.optional_inner' is not a hash reference/, 25 | 'not a hash reference', 26 | ); 27 | 28 | done_testing(); 29 | -------------------------------------------------------------------------------- /t/320_map_accessors.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("map_proto2.proto"); 5 | $d->map({ package => 'test', prefix => 'Test', options => { implicit_maps => 1 } }); 6 | 7 | my %initial_values = ( 8 | string_int32_map => { a => 7 }, 9 | bool_int32_map => { "" => 8 }, 10 | int32_message_map => { 7 => { one_value => 7 } }, 11 | int64_int32_map => { 9 => 17 }, 12 | uint32_enum_map => { 44 => 2 }, 13 | uint64_int32_map => { 12 => 8 }, 14 | ); 15 | 16 | my %added_values = ( 17 | string_int32_map => { b => 8 }, 18 | bool_int32_map => { 1 => 9 }, 19 | int32_message_map => { 8 => { one_value => 8 } }, 20 | int64_int32_map => { 10 => 18 }, 21 | uint32_enum_map => { 45 => 3 }, 22 | uint64_int32_map => { 13 => 8 }, 23 | ); 24 | 25 | for my $field (keys %initial_values) { 26 | my $with_field = bless { $field => { %{$initial_values{$field}} } }, 'Test::Maps'; 27 | my $cleared = bless { $field => { %{$initial_values{$field}} } }, 'Test::Maps'; 28 | my $empty = bless {}, 'Test::Maps'; 29 | my $get = "get_$field"; 30 | my $set = "set_$field"; 31 | my $clear = "clear_$field"; 32 | my $get_map = "get_$field\_map"; 33 | my $set_map = "set_$field\_map"; 34 | my ($initial_k, $initial_v) = %{$initial_values{$field}}; 35 | my ($add_k, $add_v) = %{$added_values{$field}}; 36 | 37 | is($empty->$get_map, undef, 'empty list is empty'); 38 | 39 | $with_field->$set(%{$added_values{$field}}); 40 | $empty->$set(%{$added_values{$field}}); 41 | 42 | eq_or_diff($with_field->$get_map, { %{$initial_values{$field}}, %{$added_values{$field}} }, 'value added to non-empty map'); 43 | eq_or_diff($empty->$get_map, $added_values{$field}, 'value added to empty map'); 44 | is($with_field->$get($add_k), $add_v, 'get item'); 45 | $with_field->$set($add_k, $initial_v); 46 | is($with_field->$get($add_k), $initial_v, 'set item'); 47 | 48 | $empty->$set_map({%{$initial_values{$field}}, %{$added_values{$field}}}); 49 | eq_or_diff($empty->$get_map, {%{$initial_values{$field}}, %{$added_values{$field}} }, 'value set to non-empty list'); 50 | 51 | $cleared->$clear; 52 | is($cleared->$get_map, undef, 'cleared list is empty'); 53 | } 54 | 55 | throws_ok( 56 | sub { Test::Maps->new->set_uint32_enum_map(4, 77) }, 57 | qr/Invalid value 77 for enumeration field 'test.Maps.uint32_enum_map'/, 58 | 'invalid enum value' 59 | ); 60 | 61 | throws_ok( 62 | sub { Test::Maps->new->set_string_int32_map_map(12) }, 63 | qr/Value for field 'test.Maps.string_int32_map' is not a hash reference/, 64 | 'not a hash', 65 | ); 66 | 67 | throws_ok( 68 | sub { Test::Maps->new({ string_int32_map => 1 })->set_string_int32_map("c", 7) }, 69 | qr/Value of field 'test.Maps.string_int32_map' is not a hash reference/, 70 | 'not a hash', 71 | ); 72 | 73 | throws_ok( 74 | sub { Test::Maps->new->get_string_int32_map("0") }, 75 | qr/Accessing unset map field 'test.Maps.string_int32_map'/, 76 | 'access item of empty hash', 77 | ); 78 | 79 | throws_ok( 80 | sub { Test::Maps->new({ string_int32_map => {} })->get_string_int32_map("a") }, 81 | qr/Accessing empty map field 'test.Maps.string_int32_map'/, 82 | 'access item of empty hash', 83 | ); 84 | 85 | throws_ok( 86 | sub { Test::Maps->new({ string_int32_map => { b => 14 } })->get_string_int32_map("a") }, 87 | qr/Accessing non-existing key 'a' for field 'test.Maps.string_int32_map'/, 88 | 'access non-existing hash item', 89 | ); 90 | 91 | done_testing(); 92 | -------------------------------------------------------------------------------- /t/330_field_numbers.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("scalar.proto"); 5 | $d->map({ package => 'test', prefix => 'Test' }); 6 | 7 | is(Test::Basic::DOUBLE_F_FIELD_NUMBER(), 1); 8 | is(Test::Basic::ENUM_F_FIELD_NUMBER(), 10); 9 | 10 | done_testing(); 11 | -------------------------------------------------------------------------------- /t/335_extension_api.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_file("extensions.proto"); 5 | $d->map({ package => 'test', prefix => 'Test' }); 6 | 7 | my $message = Test::Message1->new({ 8 | value => 2, 9 | '[test.value]' => 4, 10 | '[test.extension1]' => 3, 11 | '[test.Message2.extension2]' => Test::Message2->new({ 12 | value => 5, 13 | }), 14 | }); 15 | 16 | is($message->get_value, 2); 17 | is($message->get_test_value, 4); 18 | is($message->get_test_extension1, 3); 19 | is($message->get_test_message2_extension2->get_value, 5); 20 | 21 | throws_ok( 22 | sub { $message->get_extension('value') }, 23 | qr/Unknown extension field 'value' for message 'test.Message1'/, 24 | "not an extension", 25 | ); 26 | 27 | is($message->get_extension('test.value'), 4); 28 | is($message->get_extension('test.Message2.extension2')->get_value, 5); 29 | is($message->get_extension(Test::Message1::TEST_VALUE_KEY()), 4); 30 | is($message->get_extension(Test::Message1::TEST_MESSAGE2_EXTENSION2_KEY())->get_value, 5); 31 | 32 | throws_ok( 33 | sub { $message->get_extension_item('test.value', 1) }, 34 | qr/Extension field 'test.value' is a non-repeated field/, 35 | "scalar/repeated field mismatch", 36 | ); 37 | 38 | done_testing(); 39 | -------------------------------------------------------------------------------- /t/350_introspection.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use Google::ProtocolBuffers::Dynamic qw(:descriptor :values); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("scalar.proto"); 7 | $d->load_file("repeated.proto"); 8 | $d->load_file("message.proto"); 9 | $d->map({ package => 'test', prefix => 'Test' }); 10 | 11 | my $basic = Test::Basic->message_descriptor(); 12 | my $fields = $basic->fields; 13 | 14 | is($basic->full_name, 'test.Basic'); 15 | 16 | is(scalar @$fields, 16); 17 | eq_or_diff([sort map $_->name, @$fields], [qw( 18 | bool_f 19 | bytes_f 20 | double_f 21 | enum_f 22 | fixed32_f 23 | fixed64_f 24 | float_f 25 | int32_f 26 | int64_f 27 | sfixed32_f 28 | sfixed64_f 29 | sint32_f 30 | sint64_f 31 | string_f 32 | uint32_f 33 | uint64_f 34 | )]); 35 | 36 | my $bool_f = $basic->find_field_by_name('bool_f'); 37 | is($bool_f->full_name, 'test.Basic.bool_f'); 38 | is($bool_f->containing_type->full_name, 'test.Basic'); 39 | is($bool_f->descriptor_type, DESCRIPTOR_BOOL); 40 | is($bool_f->value_type, VALUE_BOOL); 41 | isnt(VALUE_BOOL, DESCRIPTOR_BOOL); 42 | 43 | check_props($basic, { 44 | bool_f => { is_primitive => 1 }, 45 | bytes_f => { is_string => 1 }, 46 | string_f => { is_string => 1 }, 47 | }); 48 | 49 | check_props(Test::Repeated->message_descriptor(), { 50 | bool_f => { is_primitive => 1, is_repeated => 1 }, 51 | }); 52 | 53 | check_props(Test::Packed->message_descriptor(), { 54 | bool_f => { is_primitive => 1, is_repeated => 1, is_packed => 1 }, 55 | }); 56 | 57 | check_props(Test::OuterWithMessage->message_descriptor(), { 58 | optional_inner => { is_message => 1 }, 59 | repeated_inner => { is_message => 1, is_repeated => 1 }, 60 | }); 61 | 62 | check_default_value(Test::Basic->message_descriptor(), { 63 | bool_f => '', 64 | bytes_f => '', 65 | double_f => 0.0, 66 | enum_f => 1, 67 | fixed32_f => 0, 68 | fixed64_f => 0, 69 | float_f => 0.0, 70 | int32_f => 0, 71 | int64_f => 0, 72 | sfixed32_f => 0, 73 | sfixed64_f => 0, 74 | sint32_f => 0, 75 | sint64_f => 0, 76 | string_f => '', 77 | uint32_f => 0, 78 | uint64_f => 0, 79 | }); 80 | 81 | check_default_value(Test::Default->message_descriptor(), { 82 | bool_f => 1, 83 | bytes_f => 'some bytes', 84 | double_f => 1.0, 85 | enum_f => 3, 86 | fixed32_f => 9, 87 | fixed64_f => 11, 88 | float_f => 2.0, 89 | int32_f => 3, 90 | int64_f => 4, 91 | sfixed32_f => -10, 92 | sfixed64_f => -12, 93 | sint32_f => -7, 94 | sint64_f => -8, 95 | string_f => 'a string', 96 | uint32_f => 5, 97 | uint64_f => 6, 98 | }); 99 | 100 | done_testing(); 101 | 102 | sub check_props { 103 | my($message_def, $properties) = @_; 104 | 105 | for my $field_name (sort keys %$properties) { 106 | my $field = $message_def->find_field_by_name($field_name); 107 | my $props = $properties->{$field_name}; 108 | 109 | for my $prop (qw(is_primitive is_string is_message is_repeated is_packed)) { 110 | is($field->$prop, $props->{$prop} // '', "$field_name - $prop"); 111 | } 112 | } 113 | } 114 | 115 | sub check_default_value { 116 | my($message_def, $default_values) = @_; 117 | 118 | for my $field_name (sort keys %$default_values) { 119 | my $field = $message_def->find_field_by_name($field_name); 120 | my $default_value = $default_values->{$field_name}; 121 | 122 | is($field->default_value, $default_value, "$field_name - default_value"); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /t/351_enum_introspection.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use Google::ProtocolBuffers::Dynamic qw(:descriptor :values); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("scalar.proto"); 7 | $d->map({ package => 'test', prefix => 'Test' }); 8 | 9 | my $enum_f = Test::Basic->message_descriptor->find_field_by_name('enum_f'); 10 | 11 | my $enum_t = $enum_f->enum_type; 12 | is($enum_t->full_name, 'test.ScalarEnum'); 13 | eq_or_diff($enum_t->values, { 14 | S_FIRST => 1, 15 | S_SECOND => 2, 16 | S_THIRD => 3, 17 | }); 18 | is($enum_t->default_value, 1); 19 | is($enum_t->value_count, 3); 20 | is($enum_t->find_name_by_number(2), 'S_SECOND'); 21 | is($enum_t->find_name_by_number(7), undef); 22 | is($enum_t->find_number_by_name('S_SECOND'), 2); 23 | is($enum_t->find_number_by_name('SEVENTH'), undef); 24 | 25 | my $enum_t_again = Test::ScalarEnum->enum_descriptor; 26 | is($enum_t_again->full_name, 'test.ScalarEnum'); 27 | eq_or_diff($enum_t_again->values, { 28 | S_FIRST => 1, 29 | S_SECOND => 2, 30 | S_THIRD => 3, 31 | }); 32 | 33 | done_testing(); 34 | -------------------------------------------------------------------------------- /t/352_proto3_optional_introspection.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test 'proto3_optional'; 2 | 3 | use Google::ProtocolBuffers::Dynamic qw(:descriptor :values); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("optional_proto3.proto"); 7 | $d->map({ package => 'test', prefix => 'Test' }); 8 | 9 | my $basic = Test::Optional1->message_descriptor(); 10 | 11 | is($basic->field_count, 22); 12 | is($basic->oneof_count, 11); 13 | 14 | my $fields = [@{$basic->fields}[2, 12, 20]]; 15 | my $oneofs = [@{$basic->oneofs}[3, 0]]; 16 | 17 | is($fields->[0]->name, 'int32_f'); 18 | is($fields->[0]->has_presence, ''); 19 | is($fields->[1]->name, 'int32_pf'); 20 | is($fields->[1]->has_presence, 1); 21 | is($fields->[2]->name, 'int32_of'); 22 | is($fields->[2]->has_presence, 1); 23 | 24 | { 25 | my $oneof = $oneofs->[0]; 26 | 27 | is($oneof->name, '_int32_pf'); 28 | is($oneof->is_synthetic, 1); 29 | is($oneof->field_count, 1); 30 | eq_or_diff([qw(int32_pf)], [map $_->name, @{$oneof->fields}]); 31 | } 32 | 33 | { 34 | my $oneof = $oneofs->[1]; 35 | 36 | is($oneof->name, 'test'); 37 | is($oneof->is_synthetic, ''); 38 | is($oneof->field_count, 2); 39 | eq_or_diff([qw(int32_of string_of)], [map $_->name, @{$oneof->fields}]); 40 | 41 | is($oneof->find_field_by_number(68)->name, 'string_of'); 42 | is($oneof->find_field_by_number(42), undef); 43 | 44 | is($oneof->find_field_by_name('int32_of')->number, 67); 45 | is($oneof->find_field_by_name('int64_pf'), undef); 46 | } 47 | 48 | is($fields->[0]->containing_oneof, undef); 49 | is($fields->[1]->containing_oneof->name, '_int32_pf'); 50 | is($fields->[1]->real_containing_oneof, undef); 51 | is($fields->[2]->containing_oneof->name, 'test'); 52 | is($fields->[2]->real_containing_oneof->name, 'test'); 53 | 54 | done_testing(); 55 | -------------------------------------------------------------------------------- /t/353_file_introspection.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use Google::ProtocolBuffers::Dynamic qw(:descriptor :values); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("scalar.proto"); 7 | $d->load_file('extensions.proto'); 8 | $d->load_file('grpc/greeter.proto'); 9 | $d->map( 10 | { package => 'test', prefix => 'Test' }, 11 | { package => 'helloworld', prefix => 'Test', options => { 12 | client_services => 'noop', 13 | }}, 14 | ); 15 | 16 | my $scalar_file = Test::Basic->message_descriptor->file; 17 | is($scalar_file->package, 'test'); 18 | is($scalar_file->name, 'scalar.proto'); 19 | 20 | { 21 | my $messages = $scalar_file->messages; 22 | 23 | isa_ok($_, 'Google::ProtocolBuffers::Dynamic::MessageDef') for @$messages; 24 | eq_or_diff([map $_->name, @$messages], [qw(Basic Default)]); 25 | } 26 | 27 | { 28 | my $enums = $scalar_file->enums; 29 | 30 | isa_ok($_, 'Google::ProtocolBuffers::Dynamic::EnumDef') for @$enums; 31 | eq_or_diff([map $_->name, @$enums], [qw(ScalarEnum)]); 32 | } 33 | 34 | my $service_file = Test::Greeter->service_descriptor->file; 35 | is($service_file->package, 'helloworld'); 36 | is($service_file->name, 'grpc/greeter.proto'); 37 | 38 | { 39 | my $services = $service_file->services; 40 | 41 | isa_ok($_, 'Google::ProtocolBuffers::Dynamic::ServiceDef') for @$services; 42 | eq_or_diff([map $_->name, @$services], [qw(Greeter)]); 43 | } 44 | 45 | my $extension_file = Test::Message1->message_descriptor->file; 46 | is($extension_file->package, 'test'); 47 | is($extension_file->name, 'extensions.proto'); 48 | 49 | { 50 | my $extensions = $extension_file->extensions; 51 | 52 | isa_ok($_, 'Google::ProtocolBuffers::Dynamic::FieldDef') for @$extensions; 53 | eq_or_diff([map $_->name, @$extensions], [qw(extension1 value)]); 54 | } 55 | 56 | done_testing(); 57 | -------------------------------------------------------------------------------- /t/355_map_introspection.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use Google::ProtocolBuffers::Dynamic qw(:descriptor :values); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("map.proto"); 7 | $d->map({ package => 'test', prefix => 'Test' }); 8 | 9 | my $maps = Test::Maps->message_descriptor; 10 | my $item = Test::Item->message_descriptor; 11 | my $string_int32_map = $maps->find_field_by_name('string_int32_map'); 12 | my $one_value = $item->find_field_by_name('one_value'); 13 | 14 | ok($string_int32_map->is_map, 'string_int32_map field marked as map'); 15 | ok($string_int32_map->message_type->is_map_entry, 16 | 'key/value pair is marked as a map entry'); 17 | ok(!$one_value->is_map, 'one_value field not marked as map'); 18 | 19 | done_testing(); 20 | -------------------------------------------------------------------------------- /t/357_service_introspection.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use constant { 4 | true => !!1, 5 | false => !!0, 6 | }; 7 | 8 | use Google::ProtocolBuffers::Dynamic qw(:descriptor :values); 9 | 10 | { 11 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 12 | $d->load_file("grpc/greeter.proto"); 13 | $d->map({ package => 'helloworld', prefix => 'TestNoop', options => { 14 | client_services => 'noop' 15 | } }); 16 | 17 | check_mapping('TestNoop::Greeter', 'noop'); 18 | } 19 | 20 | if (eval { require Grpc::XS }) { 21 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 22 | $d->load_file("grpc/greeter.proto"); 23 | $d->map({ package => 'helloworld', prefix => 'TestGrpcXS', options => { 24 | client_services => 'noop' 25 | } }); 26 | 27 | check_mapping('TestGrpcXS::Greeter', 'grpc_xs'); 28 | } 29 | 30 | done_testing(); 31 | 32 | sub check_mapping { 33 | my ($package, $type) = @_; 34 | 35 | my $service = $package->service_descriptor; 36 | my $methods = $service->methods; 37 | 38 | note("Testing mapping for '$type'"); 39 | is($service->full_name, 'helloworld.Greeter'); 40 | is(scalar @$methods, 4); 41 | 42 | my @methods = sort { $a->name cmp $b->name } @$methods; 43 | 44 | eq_or_diff(method_attrs($methods[0]), { 45 | client_streaming => true, 46 | server_streaming => false, 47 | name => 'JoinedHello', 48 | service => 'helloworld.Greeter', 49 | full_name => 'helloworld.Greeter.JoinedHello', 50 | input => 'helloworld.HelloRequest', 51 | output => 'helloworld.HelloReply', 52 | }); 53 | 54 | eq_or_diff(method_attrs($methods[1]), { 55 | client_streaming => false, 56 | server_streaming => false, 57 | name => 'SayHello', 58 | service => 'helloworld.Greeter', 59 | full_name => 'helloworld.Greeter.SayHello', 60 | input => 'helloworld.HelloRequest', 61 | output => 'helloworld.HelloReply', 62 | }); 63 | 64 | eq_or_diff(method_attrs($methods[2]), { 65 | client_streaming => false, 66 | server_streaming => true, 67 | name => 'SplitHello', 68 | service => 'helloworld.Greeter', 69 | full_name => 'helloworld.Greeter.SplitHello', 70 | input => 'helloworld.HelloRequest', 71 | output => 'helloworld.HelloReply', 72 | }); 73 | 74 | eq_or_diff(method_attrs($methods[3]), { 75 | client_streaming => true, 76 | server_streaming => true, 77 | name => 'WavingHello', 78 | service => 'helloworld.Greeter', 79 | full_name => 'helloworld.Greeter.WavingHello', 80 | input => 'helloworld.HelloRequest', 81 | output => 'helloworld.HelloReply', 82 | }); 83 | } 84 | 85 | sub method_attrs { 86 | return { 87 | name => $_[0]->name, 88 | full_name => $_[0]->full_name, 89 | service => $_[0]->containing_service->full_name, 90 | input => $_[0]->input_type->full_name, 91 | output => $_[0]->output_type->full_name, 92 | client_streaming => $_[0]->client_streaming, 93 | server_streaming => $_[0]->server_streaming, 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /t/358_custom_options.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use Google::ProtocolBuffers::Dynamic; 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/options'); 6 | $d->load_file("custom_options_use.proto"); 7 | $d->map({ package => 'test', prefix => 'Test' }); 8 | 9 | { 10 | my $message = Test::MessageWithOptions->message_descriptor(); 11 | my $options = $message->options; 12 | 13 | is($options->custom_option_by_name('options.msg_string'), 'Custom message option'); 14 | is($options->custom_option_by_name('options.msg_integer'), -2); 15 | is($options->custom_option_by_name('options.msg_nope'), undef); 16 | 17 | is($options->custom_option_by_number(51234), 'Custom message option'); 18 | is($options->custom_option_by_number(52234), undef); 19 | } 20 | 21 | { 22 | my $message = Test::MessageWithOptions->message_descriptor(); 23 | my $int32_f = $message->find_field_by_name('int32_f'); 24 | my $options = $int32_f->options; 25 | 26 | is($options->custom_option_by_name('options.fld_string'), 'Custom field option'); 27 | is($options->custom_option_by_name('options.fld_integer'), 0); 28 | } 29 | 30 | { 31 | my $message = Test::MessageWithOptions->message_descriptor(); 32 | my $file = $message->file; 33 | my $options = $file->options; 34 | 35 | is($options->custom_option_by_name('options.file_int32'), 7); 36 | } 37 | 38 | done_testing(); 39 | -------------------------------------------------------------------------------- /t/359_options.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto/options'); 4 | $d->load_file("builtin_options.proto"); 5 | $d->map({ package => 'test', prefix => 'Test' }); 6 | 7 | { 8 | my $message = Test::MessageWithOptions->message_descriptor; 9 | my $options = $message->options; 10 | 11 | is($options->deprecated, 1); 12 | is($options->custom_option_by_name('not.there'), undef); 13 | } 14 | 15 | { 16 | my $message = Test::MessageWithoutOptions->message_descriptor; 17 | my $options = $message->options; 18 | 19 | is($options->deprecated, ''); 20 | is($options->custom_option_by_name('not.there'), undef); 21 | } 22 | 23 | { 24 | my $file = Test::MessageWithOptions->message_descriptor->file; 25 | my $options = $file->options; 26 | 27 | is($options->deprecated, 1); 28 | is($options->java_package, 'org.test'); 29 | is($options->custom_option_by_name('not.there'), undef); 30 | 31 | throws_ok(sub { $options->nope }, 32 | qr/Unknown option 'nope'/); 33 | } 34 | 35 | done_testing(); 36 | -------------------------------------------------------------------------------- /t/400_issue_12.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_string("person.proto", <<'EOT'); 5 | syntax = "proto2"; 6 | 7 | package humans; 8 | 9 | message Person { 10 | repeated SomeSubMessage foo = 6; 11 | message SomeSubMessage { 12 | repeated int32 arrayOfInts = 2; 13 | } 14 | } 15 | EOT 16 | 17 | $d->map_package_prefix('humans', 'Humans'); 18 | $d->resolve_references(); 19 | 20 | eq_or_diff(Humans::Person->encode({foo => [{}]}), "\x32\x00", 'sanity check'); 21 | throws_ok( 22 | sub { Humans::Person->encode({foo => [{arrayOfInts => undef}]}) }, 23 | qr/Not an array reference when encoding field/, 24 | 'encoding failure in inner message', 25 | ); 26 | eq_or_diff(Humans::Person->encode({foo => [{}]}), "\x32\x00", "works after error"); 27 | 28 | done_testing(); 29 | -------------------------------------------------------------------------------- /t/401_descriptor_error.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use MIME::Base64; 4 | 5 | # obtained from the test case in 63e04f32bf2cae65697222a0773597b5ba9a3740, before the fix 6 | my $broken_descriptor = <<'EOD'; 7 | CooCCglmb28ucHJvdG8SA2ZvbyIvCgNNb28SDgoDbW9vGAEoCVIDbW9vEg4KA2JvbxgCKAlSA2Jv 8 | b0IICgZvbmVfb2ZKvgEKBhIEAAAJAQoICgEMEgMAABIKCAoBAhIDAggLCgoKAgQAEgQEAAkBCgoK 9 | AwQAARIDBAgLCgwKBAQACAASBAUICAkKDAoFBAAIAAESAwUOFAoLCgQEAAIAEgMGDh0KDAoFBAAC 10 | AAUSAwYOFAoMCgUEAAIAARIDBhUYCgwKBQQAAgADEgMGGxwKCwoEBAACARIDBw4dCgwKBQQAAgEF 11 | EgMHDhQKDAoFBAACAQESAwcVGAoMCgUEAAIBAxIDBxscYgZwcm90bzM= 12 | EOD 13 | my $d = Google::ProtocolBuffers::Dynamic->new; 14 | 15 | throws_ok( 16 | sub { $d->load_serialized_string(MIME::Base64::decode_base64($broken_descriptor)) }, 17 | qr/Oneof must have at least one field./ 18 | ); 19 | 20 | lives_ok( 21 | sub { undef $d }, 22 | 'Leaves the descriptor pool in a consistent state', 23 | ); 24 | 25 | done_testing(); 26 | -------------------------------------------------------------------------------- /t/402_parsing_error.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $error_proto = <<'EOT'; 4 | syntax = "proto3"; 5 | 6 | message Wrong { 7 | int32 int32_f; 8 | } 9 | EOT 10 | 11 | my $warning_proto = <<'EOT'; 12 | message Wrong { 13 | optional int32 int32_f = 1; 14 | } 15 | EOT 16 | 17 | my $d = Google::ProtocolBuffers::Dynamic->new; 18 | 19 | throws_ok(sub { $d->load_string('error.proto', $error_proto) }, 20 | qr/Error during protobuf parsing: error.proto:3:17: Missing field number/); 21 | 22 | warning_like(sub { $d->load_string('warning.proto', $warning_proto) }, 23 | qr/No syntax specified for the proto file: warning.proto./); 24 | 25 | done_testing(); 26 | -------------------------------------------------------------------------------- /t/403_issue_46.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 4 | $d->load_string("odd_enums.proto", <<'EOT'); 5 | syntax = "proto3"; 6 | 7 | package enums; 8 | 9 | enum Keywords { 10 | DEFAULT = 0; 11 | STDIN = 1; 12 | STDOUT = 2; 13 | STDERR = 3; 14 | ARGV = 4; 15 | ARGVOUT = 5; 16 | ENV = 6; 17 | INC = 7; 18 | SIG = 8; 19 | _ = 9; 20 | BEGIN = 10; 21 | CHECK = 11; 22 | END = 12; 23 | INIT = 13; 24 | UNITCHECK=14; 25 | } 26 | EOT 27 | 28 | $d->map({ package => 'enums', prefix => 'Enums'}); 29 | 30 | my $values = Enums::Keywords->enum_descriptor->values; 31 | 32 | for my $name (sort keys %$values) { 33 | # could be done without eval "", but this is simpler... 34 | my $code = sprintf <<'EOT', $name, $name, $name, $values->{$name}, $name; 35 | is(*%s{CODE}, undef, '%s - did not define a sub in main'); 36 | is(Enums::Keywords::%s(), %d, '%s - did define the constant'); 37 | 38 | 1; 39 | EOT 40 | 41 | eval $code or do { 42 | my $err = $@ // "Zombie error"; 43 | fail 'eval failed'; 44 | diag $err; 45 | }; 46 | } 47 | 48 | done_testing(); 49 | -------------------------------------------------------------------------------- /t/404_issue_47.t: -------------------------------------------------------------------------------- 1 | use Config; 2 | use if !$Config{usethreads}, 'Test::More', skip_all => 'Threads not available'; 3 | 4 | use t::lib::Test; 5 | use threads; 6 | 7 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 8 | $d->load_file("person.proto"); 9 | $d->map({ package => 'test', prefix => 'Test' }); 10 | 11 | for my $i (1 .. 1) { 12 | threads->create(sub {}); 13 | } 14 | 15 | $_->join for threads->list; 16 | 17 | ok(1, 'got here'); 18 | 19 | # encoding/decoding still works 20 | 21 | my $p_bytes = "\x0a\x03foo\x10\x1f"; 22 | my $p = Test::Person->decode($p_bytes); 23 | 24 | eq_or_diff($p, Test::Person->new({ id => 31, name => 'foo' })); 25 | eq_or_diff(Test::Person->encode($p), $p_bytes); 26 | done_testing(); 27 | -------------------------------------------------------------------------------- /t/452_protoc_generated_wkt.t: -------------------------------------------------------------------------------- 1 | use t::lib::Test; 2 | 3 | use lib 't/lib/generated'; 4 | 5 | # protoc -include_imports includes all dependencies in the serialized 6 | # output, while the set of descriptors passed to plugins does not contain 7 | # WKTs. Hence the need for a separate test. 8 | 9 | # protoc --perl-gpd_out=package=Protoc.WKT.Scalar:t/lib/generated/ t/proto/wkt/scalar.proto 10 | use_ok('Protoc::WKT::Scalar'); 11 | 12 | done_testing(); 13 | -------------------------------------------------------------------------------- /t/605_basic_grpc.t: -------------------------------------------------------------------------------- 1 | use t::lib::GrpcClient; 2 | 3 | spawn_server('t/grpc/sayhello.pl'); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("grpc/greeter.proto"); 7 | $d->map( 8 | { package => 'helloworld', prefix => 'Helloworld', options => { client_services => 'grpc_xs' } }, 9 | ); 10 | 11 | my $credentials = Grpc::XS::ChannelCredentials::createInsecure; 12 | my $greeter = Helloworld::Greeter->new( 13 | server_address, 14 | credentials => $credentials, 15 | ); 16 | my $request = Helloworld::HelloRequest->new({ 17 | name => 'grpc-perl', 18 | }); 19 | my $call = $greeter->SayHello( argument => $request ); 20 | my $response = $call->wait; 21 | 22 | ok($response, 'got a response'); 23 | is($response && $response->get_message, 'Hello, grpc-perl'); 24 | 25 | done_testing(); 26 | -------------------------------------------------------------------------------- /t/610_stream_client_grpc.t: -------------------------------------------------------------------------------- 1 | use t::lib::GrpcClient; 2 | 3 | spawn_server('t/grpc/sayhello_stream_client.pl'); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("grpc/greeter.proto"); 7 | $d->map( 8 | { package => 'helloworld', prefix => 'Helloworld', options => { client_services => 'grpc_xs' } }, 9 | ); 10 | 11 | my $credentials = Grpc::XS::ChannelCredentials::createInsecure; 12 | my $greeter = Helloworld::Greeter->new( 13 | server_address, 14 | credentials => $credentials, 15 | ); 16 | my $call = $greeter->JoinedHello(); 17 | for my $char (split //, 'grpc-perl') { 18 | $call->write(Helloworld::HelloRequest->new({ 19 | name => $char, 20 | })); 21 | } 22 | my $response = $call->wait; 23 | 24 | ok($response, 'got a response'); 25 | is($response && $response->get_message, 'Hello, grpc-perl'); 26 | 27 | done_testing(); 28 | -------------------------------------------------------------------------------- /t/615_stream_server_grpc.t: -------------------------------------------------------------------------------- 1 | use t::lib::GrpcClient; 2 | 3 | spawn_server('t/grpc/sayhello_stream_server.pl'); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("grpc/greeter.proto"); 7 | $d->map( 8 | { package => 'helloworld', prefix => 'Helloworld', options => { client_services => 'grpc_xs' } }, 9 | ); 10 | 11 | my $credentials = Grpc::XS::ChannelCredentials::createInsecure; 12 | my $greeter = Helloworld::Greeter->new( 13 | server_address, 14 | credentials => $credentials, 15 | ); 16 | my $request = Helloworld::HelloRequest->new({ 17 | name => 'grpc-perl', 18 | }); 19 | my $call = $greeter->SplitHello( argument => $request ); 20 | my @responses = $call->responses; 21 | 22 | is(scalar @responses, 10); 23 | is(join('', map $_->get_message, @responses), 'Hello, grpc-perl'); 24 | 25 | done_testing(); 26 | -------------------------------------------------------------------------------- /t/620_stream_bidi_grpc.t: -------------------------------------------------------------------------------- 1 | use t::lib::GrpcClient; 2 | 3 | spawn_server('t/grpc/sayhello_stream_bidi.pl'); 4 | 5 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 6 | $d->load_file("grpc/greeter.proto"); 7 | $d->map( 8 | { package => 'helloworld', prefix => 'Helloworld', options => { client_services => 'grpc_xs' } }, 9 | ); 10 | 11 | my $credentials = Grpc::XS::ChannelCredentials::createInsecure; 12 | my $greeter = Helloworld::Greeter->new( 13 | server_address, 14 | credentials => $credentials, 15 | ); 16 | my $call = $greeter->WavingHello(); 17 | my @chars = split //, 'grpc-perl'; 18 | my @responses; 19 | 20 | for (;;) { 21 | my $response = $call->read; 22 | push @responses, $response if $response; 23 | last if !$response && !@chars; 24 | if (@chars) { 25 | $call->write(Helloworld::HelloRequest->new({ 26 | name => shift @chars, 27 | })); 28 | $call->writesDone if !@chars; 29 | } 30 | } 31 | 32 | is(scalar @responses, 10); 33 | is(join('', map $_->get_message, @responses), 'Hello, grpc-perl'); 34 | 35 | done_testing(); 36 | -------------------------------------------------------------------------------- /t/grpc/sayhello.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Google::ProtocolBuffers::Dynamic; 7 | use Grpc::XS; 8 | use Grpc::Constants; 9 | 10 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 11 | $d->load_file("grpc/greeter.proto"); 12 | $d->map( 13 | { package => 'helloworld', prefix => 'Helloworld' }, 14 | ); 15 | 16 | my $server = new Grpc::XS::Server(); 17 | my $port = $server->addHttp2Port('127.0.0.1:0'); 18 | my $channel = new Grpc::XS::Channel('localhost:'.$port); 19 | 20 | $server->start(); 21 | 22 | print "port: $port\n"; 23 | flush STDOUT; 24 | 25 | my $client_event = $server->requestCall(); 26 | my $server_call = $client_event->{call}; 27 | 28 | my $request_event = $server_call->startBatch( 29 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 30 | ); 31 | my $request = Helloworld::HelloRequest->decode($request_event->{message}); 32 | my $response = Helloworld::HelloReply->encode({ 33 | message => "Hello, " . $request->get_name, 34 | }); 35 | 36 | $server_call->startBatch( 37 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 38 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 39 | 'metadata' => {}, 40 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 41 | 'details' => 'Everything good', 42 | }, 43 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 44 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { message => $response }, 45 | ); 46 | 47 | exit 0; 48 | -------------------------------------------------------------------------------- /t/grpc/sayhello_stream_bidi.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Google::ProtocolBuffers::Dynamic; 7 | use Grpc::XS; 8 | use Grpc::Constants; 9 | 10 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 11 | $d->load_file("grpc/greeter.proto"); 12 | $d->map( 13 | { package => 'helloworld', prefix => 'Helloworld' }, 14 | ); 15 | 16 | my $server = new Grpc::XS::Server(); 17 | my $port = $server->addHttp2Port('127.0.0.1:0'); 18 | my $channel = new Grpc::XS::Channel('localhost:'.$port); 19 | 20 | $server->start(); 21 | 22 | print "port: $port\n"; 23 | flush STDOUT; 24 | 25 | my $client_event = $server->requestCall(); 26 | my $server_call = $client_event->{call}; 27 | 28 | $server_call->startBatch( 29 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 30 | ); 31 | my @parts = ('Hello, '); 32 | for (;;) { 33 | last if !@parts; 34 | $server_call->startBatch( 35 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 36 | message => Helloworld::HelloReply->encode({ message => shift @parts }), 37 | }, 38 | ); 39 | if (my $request_event = $server_call->startBatch( 40 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 41 | )) { 42 | if (my $message = $request_event->{message}) { 43 | my $decoded = Helloworld::HelloRequest->decode($request_event->{message}); 44 | push @parts, $decoded->get_name; 45 | } 46 | } 47 | } 48 | 49 | $server_call->startBatch( 50 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 51 | 'metadata' => {}, 52 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 53 | 'details' => 'Everything good', 54 | }, 55 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 56 | ); 57 | 58 | exit 0; 59 | -------------------------------------------------------------------------------- /t/grpc/sayhello_stream_client.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Google::ProtocolBuffers::Dynamic; 7 | use Grpc::XS; 8 | use Grpc::Constants; 9 | 10 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 11 | $d->load_file("grpc/greeter.proto"); 12 | $d->map( 13 | { package => 'helloworld', prefix => 'Helloworld' }, 14 | ); 15 | 16 | my $server = new Grpc::XS::Server(); 17 | my $port = $server->addHttp2Port('127.0.0.1:0'); 18 | my $channel = new Grpc::XS::Channel('localhost:'.$port); 19 | 20 | $server->start(); 21 | 22 | print "port: $port\n"; 23 | flush STDOUT; 24 | 25 | my $client_event = $server->requestCall(); 26 | my $server_call = $client_event->{call}; 27 | 28 | my @requests; 29 | while (my $request_event = $server_call->startBatch( 30 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 31 | )) { 32 | last unless defined $request_event->{message}; 33 | push @requests, Helloworld::HelloRequest->decode($request_event->{message}); 34 | } 35 | my $response = Helloworld::HelloReply->encode({ 36 | message => "Hello, " . join('', map $_->get_name, @requests), 37 | }); 38 | 39 | $server_call->startBatch( 40 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 41 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 42 | 'metadata' => {}, 43 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 44 | 'details' => 'Everything good', 45 | }, 46 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 47 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { message => $response }, 48 | ); 49 | 50 | exit 0; 51 | -------------------------------------------------------------------------------- /t/grpc/sayhello_stream_server.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Google::ProtocolBuffers::Dynamic; 7 | use Grpc::XS; 8 | use Grpc::Constants; 9 | 10 | my $d = Google::ProtocolBuffers::Dynamic->new('t/proto'); 11 | $d->load_file("grpc/greeter.proto"); 12 | $d->map( 13 | { package => 'helloworld', prefix => 'Helloworld' }, 14 | ); 15 | 16 | my $server = new Grpc::XS::Server(); 17 | my $port = $server->addHttp2Port('127.0.0.1:0'); 18 | my $channel = new Grpc::XS::Channel('localhost:'.$port); 19 | 20 | $server->start(); 21 | 22 | print "port: $port\n"; 23 | flush STDOUT; 24 | 25 | my $client_event = $server->requestCall(); 26 | my $server_call = $client_event->{call}; 27 | 28 | my $request_event = $server_call->startBatch( 29 | Grpc::Constants::GRPC_OP_RECV_MESSAGE() => 1, 30 | ); 31 | my $request = Helloworld::HelloRequest->decode($request_event->{message}); 32 | 33 | $server_call->startBatch( 34 | Grpc::Constants::GRPC_OP_SEND_INITIAL_METADATA() => {}, 35 | ); 36 | for my $part ("Hello, ", split //, $request->get_name) { 37 | $server_call->startBatch( 38 | Grpc::Constants::GRPC_OP_SEND_MESSAGE() => { 39 | message => Helloworld::HelloReply->encode({ message => $part }), 40 | }, 41 | ); 42 | } 43 | $server_call->startBatch( 44 | Grpc::Constants::GRPC_OP_SEND_STATUS_FROM_SERVER() => { 45 | 'metadata' => {}, 46 | 'code' => Grpc::Constants::GRPC_STATUS_OK(), 47 | 'details' => 'Everything good', 48 | }, 49 | Grpc::Constants::GRPC_OP_RECV_CLOSE_ON_SERVER() => 1, 50 | ); 51 | 52 | exit 0; 53 | -------------------------------------------------------------------------------- /t/lib/DummyTiedArray.pm: -------------------------------------------------------------------------------- 1 | package t::lib::DummyTiedArray; 2 | 3 | use strict; 4 | use warnings; 5 | use Tie::Array; 6 | 7 | our @ISA = qw(Tie::Array); 8 | 9 | sub TIEARRAY { 10 | my ($class, $init) = @_; 11 | 12 | return bless { value => ($init || []), count => 0 }, $class; 13 | } 14 | 15 | sub FETCH { 16 | $_[0]->{count}++; 17 | 18 | return $_[0]->{value}[$_[1]]; 19 | } 20 | 21 | sub FETCHSIZE { 22 | return scalar @{$_[0]->{value}}; 23 | } 24 | 25 | sub fetch_count { 26 | return { 27 | count => $_[0]->{count}, 28 | inner => t::lib::DummyTiedScalar::inner_fetch_count($_[0]->{value}), 29 | }; 30 | } 31 | 32 | 1; 33 | -------------------------------------------------------------------------------- /t/lib/DummyTiedHash.pm: -------------------------------------------------------------------------------- 1 | package t::lib::DummyTiedHash; 2 | 3 | use strict; 4 | use warnings; 5 | use Tie::Hash; 6 | 7 | our @ISA = qw(Tie::StdHash); 8 | 9 | sub TIEHASH { 10 | my ($class, $init) = @_; 11 | 12 | return bless { value => ($init || {}), count => 0, keys => [] }, $class; 13 | } 14 | 15 | sub FETCH { 16 | $_[0]->{count}++; 17 | 18 | return $_[0]->{value}{$_[1]}; 19 | } 20 | 21 | sub EXISTS { 22 | return exists $_[0]->{value}{$_[1]}; 23 | } 24 | 25 | sub FIRSTKEY { 26 | $_[0]->{count}++; 27 | $_[0]->{keys} = [keys %{$_[0]->{value}}]; 28 | 29 | return shift @{$_[0]->{keys}}; 30 | } 31 | 32 | sub NEXTKEY { 33 | $_[0]->{count}++; 34 | 35 | return shift @{$_[0]->{keys}}; 36 | } 37 | 38 | sub fetch_count { 39 | return { 40 | count => $_[0]->{count}, 41 | inner => t::lib::DummyTiedScalar::inner_fetch_count($_[0]->{value}), 42 | }; 43 | } 44 | 45 | 1; 46 | -------------------------------------------------------------------------------- /t/lib/DummyTiedScalar.pm: -------------------------------------------------------------------------------- 1 | package t::lib::DummyTiedScalar; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | sub TIESCALAR { 7 | my ($class, $init) = @_; 8 | 9 | return bless { value => $init, count => 0 }, $class; 10 | } 11 | 12 | sub FETCH { 13 | ++$_[0]->{count}; 14 | 15 | return ${$_[0]->{value}}; 16 | } 17 | 18 | sub fetch_count { 19 | return reduce_fetch({ 20 | count => $_[0]->{count}, 21 | inner => t::lib::DummyTiedScalar::inner_fetch_count(${$_[0]->{value}}), 22 | }); 23 | } 24 | 25 | sub reduce_fetch { 26 | return $_[0]->{inner} == -1 ? $_[0]->{count} : $_[0]; 27 | } 28 | 29 | sub inner_fetch_count { 30 | return tied($_[0])->fetch_count if tied($_[0]); 31 | if (ref $_[0] eq 'HASH') { 32 | return tied(%{$_[0]})->fetch_count if tied(%{$_[0]}); 33 | return { 34 | map +($_ => inner_fetch_count($_[0]->{$_})), keys %{$_[0]} 35 | }; 36 | } elsif (ref $_[0] eq 'ARRAY') { 37 | return tied(@{$_[0]})->fetch_count if tied(@{$_[0]}); 38 | return [map inner_fetch_count($_), @{$_[0]}] 39 | } else { 40 | return -1; 41 | } 42 | } 43 | 44 | 1; 45 | -------------------------------------------------------------------------------- /t/lib/GrpcClient.pm: -------------------------------------------------------------------------------- 1 | package t::lib::GrpcClient; 2 | 3 | use strict; 4 | use warnings; 5 | use parent 'Test::Builder::Module'; 6 | 7 | use t::lib::Test; 8 | 9 | use IPC::Open2; 10 | 11 | our @EXPORT = ( 12 | @t::lib::Test::EXPORT, 13 | qw( 14 | spawn_server 15 | server_address 16 | ), 17 | ); 18 | 19 | sub import { 20 | unshift @INC, 't/lib'; 21 | 22 | eval { 23 | require Grpc::XS; 24 | 25 | 1; 26 | } or do { 27 | Test::More->import(skip_all => 'Grpc::XS not available'); 28 | }; 29 | 30 | strict->import; 31 | warnings->import; 32 | 33 | # for libgrpc 34 | delete @ENV{grep /https?_proxy/i, keys %ENV}; 35 | 36 | goto &t::lib::Test::import; 37 | } 38 | 39 | my ($server_pid, $server_port); 40 | 41 | sub spawn_server { 42 | my ($server_script) = @_; 43 | $server_pid = open2(my $child_in, my $child_out, $^X, '-Mblib', $server_script); 44 | close $child_out; 45 | my $port_line = readline($child_in); 46 | die "Invalid format '$port_line'" unless $port_line =~ /port: (\d+)$/; 47 | my $port = $1; 48 | 49 | $server_port = $port; 50 | } 51 | 52 | sub server_address { 53 | return "127.0.0.1:$server_port"; 54 | } 55 | 56 | END { 57 | kill 3, $server_pid if $server_pid; 58 | } 59 | 60 | 1; 61 | -------------------------------------------------------------------------------- /t/lib/Test.pm: -------------------------------------------------------------------------------- 1 | package t::lib::Test; 2 | 3 | use strict; 4 | use warnings; 5 | use parent 'Test::Builder::Module'; 6 | 7 | use Test::More; 8 | use Test::Differences; 9 | use Test::Exception; 10 | use Test::Warn; 11 | 12 | use t::lib::DummyTiedScalar; 13 | use t::lib::DummyTiedArray; 14 | use t::lib::DummyTiedHash; 15 | 16 | use Google::ProtocolBuffers::Dynamic; 17 | use Config; 18 | 19 | our @EXPORT = ( 20 | @Test::More::EXPORT, 21 | @Test::Differences::EXPORT, 22 | @Test::Exception::EXPORT, 23 | @Test::Warn::EXPORT, 24 | qw( 25 | maybe_bigint 26 | tie_scalar 27 | tie_array 28 | tied_array 29 | tied_hash 30 | 31 | tied_fetch_count 32 | 33 | decode_eq_or_diff 34 | decode_throws_ok 35 | decoder_functions 36 | ) 37 | ); 38 | 39 | sub import { 40 | unshift @INC, 't/lib'; 41 | 42 | strict->import; 43 | warnings->import; 44 | 45 | if (@_ > 1 && $_[1] eq 'proto3_optional') { 46 | splice @_, 1, 1; 47 | 48 | @_ = ($_[0], "skip_all", "Protocol Buffers v3.12 required") 49 | unless Google::ProtocolBuffers::Dynamic::has_proto3_optional(); 50 | } 51 | 52 | goto &Test::Builder::Module::import; 53 | } 54 | 55 | sub maybe_bigint { 56 | return $_[0] + 0 if $Config{ivsize} >= 8; 57 | 58 | require Math::BigInt; 59 | my $bi = Math::BigInt->new($_[0]); 60 | 61 | return $bi > -2147483648 && $bi < 2147483647 ? 0 + $_[0] : $bi; 62 | } 63 | 64 | sub tie_scalar { 65 | tie $_[0], 't::lib::DummyTiedScalar', \$_[1]; 66 | } 67 | 68 | sub tie_array { 69 | tie @{$_[0]}, 't::lib::DummyTiedArray', $_[1]; 70 | } 71 | 72 | sub tied_array { 73 | my @result; 74 | 75 | tie @result, 't::lib::DummyTiedArray', [@_]; 76 | 77 | return \@result; 78 | } 79 | 80 | sub tied_hash { 81 | my %result; 82 | 83 | tie %result, 't::lib::DummyTiedHash', {@_}; 84 | 85 | return \%result; 86 | } 87 | 88 | sub tied_fetch_count { 89 | if (tied $_[0]) { 90 | return tied($_[0])->fetch_count; 91 | } else { 92 | return t::lib::DummyTiedScalar::inner_fetch_count($_[0]); 93 | } 94 | } 95 | 96 | sub decoder_functions { 97 | return qw(decode_upb decode_bbpb);; 98 | } 99 | 100 | sub decode_eq_or_diff { 101 | my ($package, $bytes, $expected, $description) = @_; 102 | 103 | local $Test::Builder::Level = $Test::Builder::Level + 1; 104 | 105 | eq_or_diff($package->decode_upb($bytes), $expected, $description ? "$description (decode_upb)" : '(decode_upb)'); 106 | eq_or_diff($package->decode_bbpb($bytes), $expected, $description ? "$description (decode_bbpb)" : '(decode_bbpb)'); 107 | } 108 | 109 | sub decode_throws_ok { 110 | my ($package, $bytes, $expected_upb, $expected_bbpb, $description) = @_; 111 | 112 | local $Test::Builder::Level = $Test::Builder::Level + 1; 113 | 114 | throws_ok(sub { $package->decode_upb($bytes) }, $expected_upb, $description ? "$description (decode_upb)" : '(decode_upb)'); 115 | throws_ok(sub { $package->decode_bbpb($bytes) }, $expected_bbpb, $description ? "$description (decode_bbpb)" : '(decode_bbpb)'); 116 | } 117 | 118 | 1; 119 | -------------------------------------------------------------------------------- /t/lib/generated/Protoc/WKT/Scalar.pm: -------------------------------------------------------------------------------- 1 | package Protoc::WKT::Scalar; 2 | 3 | use strict; 4 | use warnings; 5 | # @@protoc_insertion_point(after_pragmas) 6 | use MIME::Base64 qw(); 7 | use Google::ProtocolBuffers::Dynamic; 8 | 9 | my $gpd = Google::ProtocolBuffers::Dynamic->new; 10 | 11 | $gpd->load_serialized_string(MIME::Base64::decode_base64(<<'EOD')); 12 | CvkFChh0L3Byb3RvL3drdC9zY2FsYXIucHJvdG8SBHRlc3QaHmdvb2dsZS9wcm90b2J1Zi9kdXJh 13 | dGlvbi5wcm90bxofZ29vZ2xlL3Byb3RvYnVmL3RpbWVzdGFtcC5wcm90bxoeZ29vZ2xlL3Byb3Rv 14 | YnVmL3dyYXBwZXJzLnByb3RvIu0ECgVCYXNpYxI7Cgt0aW1lc3RhbXBfZhgBIAEoCzIaLmdvb2ds 15 | ZS5wcm90b2J1Zi5UaW1lc3RhbXBSCnRpbWVzdGFtcEYSOAoKZHVyYXRpb25fZhgCIAEoCzIZLmdv 16 | b2dsZS5wcm90b2J1Zi5EdXJhdGlvblIJZHVyYXRpb25GEjcKCGRvdWJsZV9mGAMgASgLMhwuZ29v 17 | Z2xlLnByb3RvYnVmLkRvdWJsZVZhbHVlUgdkb3VibGVGEjQKB2Zsb2F0X2YYBCABKAsyGy5nb29n 18 | bGUucHJvdG9idWYuRmxvYXRWYWx1ZVIGZmxvYXRGEjQKB2ludDY0X2YYBSABKAsyGy5nb29nbGUu 19 | cHJvdG9idWYuSW50NjRWYWx1ZVIGaW50NjRGEjcKCHVpbnQ2NF9mGAYgASgLMhwuZ29vZ2xlLnBy 20 | b3RvYnVmLlVJbnQ2NFZhbHVlUgd1aW50NjRGEjQKB2ludDMyX2YYByABKAsyGy5nb29nbGUucHJv 21 | dG9idWYuSW50MzJWYWx1ZVIGaW50MzJGEjcKCHVpbnQzMl9mGAggASgLMhwuZ29vZ2xlLnByb3Rv 22 | YnVmLlVJbnQzMlZhbHVlUgd1aW50MzJGEjEKBmJvb2xfZhgJIAEoCzIaLmdvb2dsZS5wcm90b2J1 23 | Zi5Cb29sVmFsdWVSBWJvb2xGEjcKCHN0cmluZ19mGAogASgLMhwuZ29vZ2xlLnByb3RvYnVmLlN0 24 | cmluZ1ZhbHVlUgdzdHJpbmdGEjQKB2J5dGVzX2YYCyABKAsyGy5nb29nbGUucHJvdG9idWYuQnl0 25 | ZXNWYWx1ZVIGYnl0ZXNGYgZwcm90bzM= 26 | 27 | EOD 28 | 29 | 30 | # @@protoc_insertion_point(after_loading) 31 | 32 | $gpd->map( 33 | +{ 34 | 'package' => 'test', 35 | 'prefix' => 'Protoc::WKT::Scalar::Test' 36 | }, 37 | ); 38 | 39 | # @@protoc_insertion_point(after_mapping) 40 | 41 | undef $gpd; 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /t/proto/bigint.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message BigInts { 6 | optional uint64 uint64_f = 1; 7 | optional int64 int64_f = 2; 8 | } 9 | -------------------------------------------------------------------------------- /t/proto/bool.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message Bool { 6 | optional bool bool_f = 7; 7 | } 8 | 9 | message OuterBool { 10 | optional Bool bool = 8; 11 | } -------------------------------------------------------------------------------- /t/proto/defaults_proto3.proto: -------------------------------------------------------------------------------- 1 | syntax = 'proto3'; 2 | 3 | package test; 4 | 5 | message Defaults { 6 | int32 int32_f = 1; 7 | } 8 | -------------------------------------------------------------------------------- /t/proto/edge.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | message EmptyFields { 6 | EmptyMessage empty_message = 1; 7 | SomeMessage some_message = 2; 8 | repeated int32 packed_int32 = 3; 9 | repeated fixed32 packed_fixed32 = 4; 10 | repeated fixed64 packed_fixed64 = 5; 11 | string some_string = 6; 12 | int32 some_varint = 7; 13 | double some_fixed64 = 8; 14 | float some_fixed32 = 9; 15 | repeated string repeated_string = 10; 16 | } 17 | 18 | message EmptyMessage { 19 | } 20 | 21 | message SomeMessage { 22 | int32 int32_value = 1; 23 | } 24 | 25 | message OuterMessage { 26 | EmptyFields empty_fields = 1; 27 | } 28 | 29 | message UnknownFields { 30 | int32 field2 = 2; 31 | int32 field1000 = 1000; 32 | } 33 | -------------------------------------------------------------------------------- /t/proto/enum.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message MessageBefore { 6 | enum EnumBefore { 7 | FIRST = 1; 8 | SECOND = 2; 9 | } 10 | optional EnumBefore value = 1; 11 | repeated EnumBefore array = 2; 12 | } 13 | 14 | message MessageAfter { 15 | enum EnumAfter { 16 | FIRST = 1; 17 | SECOND = 2; 18 | THIRD = 3; 19 | } 20 | optional EnumAfter value = 1; 21 | repeated EnumAfter array = 2; 22 | } 23 | -------------------------------------------------------------------------------- /t/proto/extensions.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message Message1 { 6 | optional int32 value = 1; 7 | extensions 100 to 150; 8 | } 9 | 10 | extend Message1 { 11 | optional int32 extension1 = 100; 12 | } 13 | 14 | extend Message1 { 15 | optional int32 value = 102; 16 | } 17 | 18 | message Message2 { 19 | extend Message1 { 20 | optional Message2 extension2 = 101; 21 | } 22 | optional int32 value = 1; 23 | } 24 | -------------------------------------------------------------------------------- /t/proto/grpc/greeter.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package helloworld; 4 | 5 | service Greeter { 6 | rpc SayHello (HelloRequest) returns (HelloReply); 7 | 8 | rpc JoinedHello (stream HelloRequest) returns (HelloReply); 9 | 10 | rpc SplitHello (HelloRequest) returns (stream HelloReply); 11 | 12 | rpc WavingHello (stream HelloRequest) returns (stream HelloReply); 13 | } 14 | 15 | message HelloRequest { 16 | string name = 1; 17 | } 18 | 19 | message HelloReply { 20 | string message = 1; 21 | } 22 | -------------------------------------------------------------------------------- /t/proto/hierarchical/test1.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test.sub1; 4 | 5 | message Foo { 6 | optional int32 foo = 1; 7 | } 8 | -------------------------------------------------------------------------------- /t/proto/hierarchical/test2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test.sub2; 4 | 5 | message Foo { 6 | optional int32 foo = 1; 7 | } 8 | -------------------------------------------------------------------------------- /t/proto/hierarchical/test3.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message Foo { 6 | optional int32 foo = 1; 7 | } 8 | -------------------------------------------------------------------------------- /t/proto/hierarchical/test4.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test.sub1.sub2; 4 | 5 | message Foo { 6 | optional int32 foo = 1; 7 | } 8 | -------------------------------------------------------------------------------- /t/proto/map.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | enum MapEnum { 6 | M_UNKNOWN = 0; 7 | M_FIRST = 1; 8 | M_SECOND = 2; 9 | } 10 | 11 | message Maps { 12 | map string_int32_map = 1; 13 | map int32_string_map = 2; 14 | map int32_bool_map = 3; 15 | map bool_int32_map = 4; 16 | map int32_enum_map = 5; 17 | map int32_message_map = 6; 18 | map string_string_map_map = 7; 19 | map uint32_bool_map = 8; 20 | map uint64_bool_map = 9; 21 | } 22 | 23 | message Item { 24 | int32 one_value = 1; 25 | string another_value = 2; 26 | } 27 | 28 | message StringMap { 29 | map string_int32_map = 1; 30 | } 31 | -------------------------------------------------------------------------------- /t/proto/map_proto2.proto: -------------------------------------------------------------------------------- 1 | syntax = 'proto2'; 2 | 3 | package test; 4 | 5 | enum MapEnum { 6 | MAP_FIRST = 1; 7 | MAP_SECOND = 2; 8 | MAP_THIRD = 3; 9 | } 10 | 11 | message Maps { 12 | message StringInt32MapEntry { 13 | optional string key = 1; 14 | optional int32 value = 2; 15 | } 16 | 17 | message BoolInt32MapEntry { 18 | optional bool key = 1; 19 | optional int32 value = 2; 20 | } 21 | 22 | message Int32MessageMapEntry { 23 | optional int32 key = 1; 24 | optional Item value = 2; 25 | } 26 | 27 | message Int64Int32MapEntry { 28 | optional int64 key = 1; 29 | optional int32 value = 2; 30 | } 31 | 32 | message Uint32EnumMapEntry { 33 | optional uint32 key = 1; 34 | optional MapEnum value = 2; 35 | } 36 | 37 | message Uint64Int32MapEntry { 38 | optional uint64 key = 1; 39 | optional int32 value = 2; 40 | } 41 | 42 | repeated StringInt32MapEntry string_int32_map = 1; 43 | repeated BoolInt32MapEntry bool_int32_map = 2; 44 | repeated Int32MessageMapEntry int32_message_map = 3; 45 | repeated Int64Int32MapEntry int64_int32_map = 4; 46 | repeated Uint32EnumMapEntry uint32_enum_map = 5; 47 | repeated Uint64Int32MapEntry uint64_int32_map = 6; 48 | } 49 | 50 | message Item { 51 | optional int32 one_value = 1; 52 | optional string another_value = 2; 53 | } 54 | -------------------------------------------------------------------------------- /t/proto/mapping/test1.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test1; 4 | 5 | enum Enum { 6 | VALUE1 = 2; 7 | VALUE2 = 7; 8 | VALUE3 = 12; 9 | } 10 | 11 | message Message1 { 12 | optional int32 test1_message1 = 1; 13 | } 14 | 15 | message Message2 { 16 | optional int32 test1_message2 = 1; 17 | } 18 | 19 | message Message3 { 20 | optional Message1 test1_message3_message1 = 1; 21 | optional Message2 test1_message3_message2 = 2; 22 | } 23 | 24 | message Message4 { 25 | message Message5 { 26 | optional int32 value = 1; 27 | } 28 | 29 | enum Enum { 30 | VALUE1 = 3; 31 | VALUE2 = 2; 32 | VALUE3 = 1; 33 | } 34 | 35 | optional Message5 inner = 2; 36 | } 37 | -------------------------------------------------------------------------------- /t/proto/mapping/test2.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test2; 4 | 5 | import "test1.proto"; 6 | 7 | message Message1 { 8 | optional int32 test2_message1 = 1; 9 | } 10 | 11 | message Message3 { 12 | optional Message1 test1_message3_message1 = 1; 13 | optional test1.Message2 test1_message3_message2 = 2; 14 | } 15 | -------------------------------------------------------------------------------- /t/proto/mapping/test3.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test3; 4 | 5 | import "test1.proto"; 6 | import "test2.proto"; 7 | 8 | service Service1 { 9 | rpc Method (test1.Message1) returns (test2.Message1); 10 | } 11 | 12 | service Service2 { 13 | rpc Method (test1.Message2) returns (test2.Message3); 14 | } 15 | -------------------------------------------------------------------------------- /t/proto/mapping/wkts.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/duration.proto"; 4 | import "google/protobuf/timestamp.proto"; 5 | import "google/protobuf/wrappers.proto"; 6 | import "google/protobuf/descriptor.proto"; 7 | 8 | package wkts; 9 | 10 | message TestWKTs { 11 | google.protobuf.DescriptorProto descriptor_f = 1; 12 | google.protobuf.Timestamp timestamp_f = 2; 13 | google.protobuf.Duration duration_f = 3; 14 | google.protobuf.DoubleValue double_f = 4; 15 | } 16 | -------------------------------------------------------------------------------- /t/proto/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message Inner { 6 | optional int32 value = 1; 7 | optional int32 other = 2; 8 | } 9 | 10 | message OuterWithMessage { 11 | optional Inner optional_inner = 1; 12 | repeated Inner repeated_inner = 2; 13 | } 14 | 15 | message OuterWithGroup { 16 | repeated group Inner = 1 { 17 | optional int32 value = 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /t/proto/message_prefix/a.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package a; 3 | 4 | import "b.proto"; 5 | 6 | message A { 7 | b.B foo = 1; 8 | } 9 | -------------------------------------------------------------------------------- /t/proto/message_prefix/b.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package b; 3 | 4 | message B { 5 | int32 bar = 1; 6 | } 7 | -------------------------------------------------------------------------------- /t/proto/message_prefix/r.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package r; 3 | 4 | message Foo { 5 | oneof optional_bar { 6 | r.Bar bar = 1; 7 | } 8 | int32 x = 2; 9 | } 10 | 11 | message Bar { 12 | oneof optional_foo { 13 | r.Foo foo = 1; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /t/proto/oneof.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message OneOf1 { 6 | optional int32 value1 = 2; 7 | oneof oneof1 { 8 | string value2 = 3; 9 | int32 value3 = 1; 10 | int32 value4 = 4; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /t/proto/optional_proto3.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | enum Enum { 6 | FIRST = 0; 7 | SECOND = 1; 8 | THIRD = 2; 9 | } 10 | 11 | message Optional1 { 12 | double double_f = 1; 13 | float float_f = 2; 14 | int32 int32_f = 3; 15 | int64 int64_f = 4; 16 | uint32 uint32_f = 5; 17 | uint64 uint64_f = 6; 18 | bool bool_f = 7; 19 | string string_f = 8; 20 | bytes bytes_f = 9; 21 | Enum enum_f = 10; 22 | 23 | optional double double_pf = 33; 24 | optional float float_pf = 34; 25 | optional int32 int32_pf = 35; 26 | optional int32 int64_pf = 36; 27 | optional uint32 uint32_pf = 37; 28 | optional uint32 uint64_pf = 38; 29 | optional bool bool_pf = 39; 30 | optional string string_pf = 40; 31 | optional bytes bytes_pf = 41; 32 | optional Enum enum_pf = 42; 33 | 34 | oneof test { 35 | int32 int32_of = 67; 36 | string string_of = 68; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /t/proto/options.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | enum OptionsEnum { 6 | OPTION_FIRST = 1; 7 | OPTION_SECOND = 2; 8 | OPTION_THIRD = 3; 9 | } 10 | 11 | message Defaults { 12 | optional int32 int32_f = 1 [default = 7]; 13 | optional uint32 uint32_f = 2 [default = 8]; 14 | optional int64 int64_f = 3 [default = 9]; 15 | optional uint64 uint64_f = 4 [default = 10]; 16 | optional float float_f = 5 [default = 1.25]; 17 | optional double double_f = 6 [default = 2.125]; 18 | optional string string_f = 7 [default = "abcde"]; 19 | optional bytes bytes_f = 8 [default = "def"]; 20 | optional OptionsEnum enum_f = 9 [default = OPTION_SECOND]; 21 | } 22 | 23 | message Required { 24 | required int32 value = 1; 25 | } 26 | -------------------------------------------------------------------------------- /t/proto/options/builtin_options.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | option deprecated = true; 6 | option java_package = "org.test"; 7 | 8 | message MessageWithOptions { 9 | option deprecated = true; 10 | } 11 | 12 | message MessageWithoutOptions { 13 | } 14 | -------------------------------------------------------------------------------- /t/proto/options/custom_options.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/descriptor.proto"; 4 | 5 | package options; 6 | 7 | extend google.protobuf.FileOptions { 8 | int32 file_int32 = 51234; 9 | } 10 | 11 | extend google.protobuf.MessageOptions { 12 | string msg_string = 51234; 13 | sint32 msg_integer = 51235; 14 | } 15 | 16 | extend google.protobuf.FieldOptions { 17 | string fld_string = 52234; 18 | sint32 fld_integer = 52235; 19 | } 20 | -------------------------------------------------------------------------------- /t/proto/options/custom_options_use.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "custom_options.proto"; 4 | 5 | package test; 6 | 7 | option (options.file_int32) = 7; 8 | 9 | message MessageWithOptions { 10 | option (options.msg_string) = "Custom message option"; 11 | option (options.msg_integer) = -2; 12 | 13 | int32 int32_f = 1 [(options.fld_string) = "Custom field option"]; 14 | string string_f = 2; 15 | } 16 | -------------------------------------------------------------------------------- /t/proto/order.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message OrderedFields { 6 | optional Inner field_1 = 1; 7 | optional int32 field_2 = 2; 8 | optional int32 field_3 = 3; 9 | optional Inner field_4 = 4; 10 | 11 | message Inner { 12 | optional int32 field = 1; 13 | } 14 | } 15 | 16 | message DisorderedFields { 17 | optional Inner field_4 = 4; 18 | optional int32 field_3 = 3; 19 | optional int32 field_2 = 2; 20 | optional Inner field_1 = 1; 21 | 22 | message Inner { 23 | optional int32 field = 1; 24 | } 25 | } 26 | 27 | message MixedOneof { 28 | optional int32 field_2 = 2; 29 | oneof oneof_1 { 30 | int32 field_1 = 1; 31 | int32 field_3 = 3; 32 | } 33 | optional int32 field_4 = 4; 34 | } 35 | 36 | message InterleavedOneof { 37 | optional int32 field_2 = 2; 38 | oneof oneof_1 { 39 | int32 field_1 = 1; 40 | int32 field_4 = 4; 41 | } 42 | oneof oneof_2 { 43 | int32 field_3 = 3; 44 | int32 field_5 = 5; 45 | } 46 | optional int32 field_6 = 6; 47 | } 48 | -------------------------------------------------------------------------------- /t/proto/packed_proto3.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | message Packed { 6 | repeated int32 int32_f = 3; 7 | } 8 | 9 | message Unpacked { 10 | repeated int32 int32_f = 3 [packed=false]; 11 | } 12 | -------------------------------------------------------------------------------- /t/proto/person.pb: -------------------------------------------------------------------------------- 1 | 2 | } 3 | t/proto/person.prototest"1 4 | Person 5 | name (  6 | 7 | id ( 8 | email ( ", 9 | PersonArray 10 | persons ( 2 .test.Person -------------------------------------------------------------------------------- /t/proto/person.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message Person { 6 | required string name = 1; 7 | required int32 id = 2; 8 | optional string email = 3; 9 | } 10 | 11 | message PersonArray { 12 | repeated Person persons = 1; 13 | } 14 | -------------------------------------------------------------------------------- /t/proto/person3.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | message Person3 { 6 | string name = 1; 7 | int32 id = 2; 8 | string email = 3; 9 | } 10 | 11 | message Person3Array { 12 | repeated Person3 persons = 1; 13 | } 14 | -------------------------------------------------------------------------------- /t/proto/recurse.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | message List { 6 | optional int32 value = 1; 7 | optional List next = 2; 8 | } 9 | -------------------------------------------------------------------------------- /t/proto/repeated.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | enum RepeatedEnum { 6 | REPEATED_FIRST = 1; 7 | REPEATED_SECOND = 2; 8 | REPEATED_THIRD = 3; 9 | } 10 | 11 | message Repeated { 12 | repeated double double_f = 1; 13 | repeated float float_f = 2; 14 | repeated int32 int32_f = 3; 15 | repeated int64 int64_f = 4; 16 | repeated uint32 uint32_f = 5; 17 | repeated uint64 uint64_f = 6; 18 | repeated bool bool_f = 7; 19 | repeated string string_f = 8; 20 | repeated bytes bytes_f = 9; 21 | repeated RepeatedEnum enum_f = 10; 22 | } 23 | 24 | message Packed { 25 | repeated double double_f = 1 [packed=true]; 26 | repeated float float_f = 2 [packed=true]; 27 | repeated int32 int32_f = 3 [packed=true]; 28 | repeated int64 int64_f = 4 [packed=true]; 29 | repeated uint32 uint32_f = 5 [packed=true]; 30 | repeated uint64 uint64_f = 6 [packed=true]; 31 | repeated bool bool_f = 7 [packed=true]; 32 | repeated RepeatedEnum enum_f = 10 [packed=true]; 33 | } 34 | -------------------------------------------------------------------------------- /t/proto/scalar.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package test; 4 | 5 | enum ScalarEnum { 6 | S_FIRST = 1; 7 | S_SECOND = 2; 8 | S_THIRD = 3; 9 | } 10 | 11 | message Basic { 12 | optional double double_f = 1; 13 | optional float float_f = 2; 14 | optional int32 int32_f = 3; 15 | optional int64 int64_f = 4; 16 | optional uint32 uint32_f = 5; 17 | optional uint64 uint64_f = 6; 18 | optional bool bool_f = 7; 19 | optional string string_f = 8; 20 | optional bytes bytes_f = 9; 21 | optional ScalarEnum enum_f = 10; 22 | optional sint32 sint32_f = 11; 23 | optional sint64 sint64_f = 12; 24 | optional fixed32 fixed32_f = 13; 25 | optional sfixed32 sfixed32_f = 14; 26 | optional fixed64 fixed64_f = 15; 27 | optional sfixed64 sfixed64_f = 16; 28 | } 29 | 30 | message Default { 31 | optional double double_f = 1 [default = 1.0]; 32 | optional float float_f = 2 [default = 2.0]; 33 | optional int32 int32_f = 3 [default = 3]; 34 | optional int64 int64_f = 4 [default = 4]; 35 | optional uint32 uint32_f = 5 [default = 5]; 36 | optional uint64 uint64_f = 6 [default = 6]; 37 | optional bool bool_f = 7 [default = true]; 38 | optional string string_f = 8 [default = "a string"]; 39 | optional bytes bytes_f = 9 [default = "some bytes"]; 40 | optional ScalarEnum enum_f = 10 [default = S_THIRD]; 41 | optional sint32 sint32_f = 11 [default = -7]; 42 | optional sint64 sint64_f = 12 [default = -8]; 43 | optional fixed32 fixed32_f = 13 [default = 9]; 44 | optional sfixed32 sfixed32_f = 14 [default = -10]; 45 | optional fixed64 fixed64_f = 15 [default = 11]; 46 | optional sfixed64 sfixed64_f = 16 [default = -12]; 47 | } 48 | -------------------------------------------------------------------------------- /t/proto/transform/decoder.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | message Message { 6 | repeated Int32ArrayArray nested_array = 1; 7 | Int32ArrayArray concatenated_array = 2; 8 | } 9 | 10 | message FieldMessage { 11 | Int32Array transformed = 1; 12 | Int32Array original = 2; 13 | } 14 | 15 | message MapMessage { 16 | map map_values = 1; 17 | } 18 | 19 | message Int32Array { 20 | repeated int32 values = 1; 21 | } 22 | 23 | message Int32ArrayArray { 24 | repeated Int32Array values = 1; 25 | } 26 | 27 | message InvalidFields { 28 | int32 not_message = 1; 29 | repeated int32 repeated_not_message = 2; 30 | repeated Int32Array repeated_message = 3; 31 | map map_field = 4; 32 | } 33 | -------------------------------------------------------------------------------- /t/proto/transform/encoder.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | 5 | message Int32Array { 6 | repeated int32 values = 1; 7 | } 8 | 9 | message Int32ArrayArray { 10 | repeated Int32Array values = 1; 11 | } 12 | 13 | message StringInt32MapTied { 14 | map values = 1; 15 | } 16 | 17 | message ContainerMessage { 18 | ItemMessage scalar = 1; 19 | Int32ArrayArray array_wrapper = 2; 20 | StringInt32MapTied tied_map_wrapper = 3; 21 | } 22 | 23 | message ItemMessage { 24 | int32 id = 1; 25 | string name = 2; 26 | } -------------------------------------------------------------------------------- /t/proto/wkt/copies/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2008 Google Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | Code generated by the Protocol Buffer compiler is owned by the owner 30 | of the input file used when generating it. This code is not 31 | standalone and requires a support library to be linked with it. This 32 | support library is itself covered by the above license. 33 | -------------------------------------------------------------------------------- /t/proto/wkt/scalar.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbarbon/google-protobuf-dynamic/7ced3f1340d35c20c2ea29baef8ff19357ae7834/t/proto/wkt/scalar.pb -------------------------------------------------------------------------------- /t/proto/wkt/scalar.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/duration.proto"; 4 | import "google/protobuf/timestamp.proto"; 5 | import "google/protobuf/wrappers.proto"; 6 | 7 | package test; 8 | 9 | message Basic { 10 | google.protobuf.Timestamp timestamp_f = 1; 11 | google.protobuf.Duration duration_f = 2; 12 | google.protobuf.DoubleValue double_f = 3; 13 | google.protobuf.FloatValue float_f = 4; 14 | google.protobuf.Int64Value int64_f = 5; 15 | google.protobuf.UInt64Value uint64_f = 6; 16 | google.protobuf.Int32Value int32_f = 7; 17 | google.protobuf.UInt32Value uint32_f = 8; 18 | google.protobuf.BoolValue bool_f = 9; 19 | google.protobuf.StringValue string_f = 10; 20 | google.protobuf.BytesValue bytes_f = 11; 21 | } 22 | -------------------------------------------------------------------------------- /t/proto/wkt/scalar_copies.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbarbon/google-protobuf-dynamic/7ced3f1340d35c20c2ea29baef8ff19357ae7834/t/proto/wkt/scalar_copies.pb -------------------------------------------------------------------------------- /t/proto/wkt/scalar_copies.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "wkt/copies/duration.proto"; 4 | import "wkt/copies/timestamp.proto"; 5 | import "wkt/copies/wrappers.proto"; 6 | 7 | package test; 8 | 9 | message BasicCopy { 10 | google.protobuf.Timestamp timestamp_f = 1; 11 | google.protobuf.Duration duration_f = 2; 12 | google.protobuf.DoubleValue double_f = 3; 13 | google.protobuf.FloatValue float_f = 4; 14 | google.protobuf.Int64Value int64_f = 5; 15 | google.protobuf.UInt64Value uint64_f = 6; 16 | google.protobuf.Int32Value int32_f = 7; 17 | google.protobuf.UInt32Value uint32_f = 8; 18 | google.protobuf.BoolValue bool_f = 9; 19 | google.protobuf.StringValue string_f = 10; 20 | google.protobuf.BytesValue bytes_f = 11; 21 | } 22 | -------------------------------------------------------------------------------- /t/proto/wkt/timestamp.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbarbon/google-protobuf-dynamic/7ced3f1340d35c20c2ea29baef8ff19357ae7834/t/proto/wkt/timestamp.pb -------------------------------------------------------------------------------- /t/proto/wkt/timestamp.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/timestamp.proto"; 4 | 5 | package test; 6 | 7 | message Basic { 8 | google.protobuf.Timestamp timestamp_f = 1; 9 | } 10 | -------------------------------------------------------------------------------- /xsp/dynamic.xsp: -------------------------------------------------------------------------------- 1 | %module{Google::ProtocolBuffers::Dynamic}; 2 | 3 | #include "perl_unpollute.h" 4 | #include "dynamic.h" 5 | 6 | %name{Google::ProtocolBuffers::Dynamic} class gpd::Dynamic { 7 | Dynamic(std::string root_directory = std::string()); 8 | ~Dynamic() %code{% THIS->unref(); %}; 9 | 10 | void load_file(const std::string &file) 11 | %code{% THIS->load_file(aTHX_ *file); %}; 12 | 13 | void load_string(const std::string &file, SV *sv) 14 | %code{% THIS->load_string(aTHX_ *file, sv); %}; 15 | 16 | void load_serialized_string(SV *sv) 17 | %code{% THIS->load_serialized_string(aTHX_ sv); %}; 18 | 19 | // options is only useful when testing 20 | void map_wkts(SV *options = NULL) 21 | %code{% THIS->map_wkts(aTHX_ gpd::MappingOptions(aTHX_ options)); %}; 22 | 23 | void map_message(const std::string &message, const std::string &perl_package, SV *options = NULL) 24 | %code{% THIS->map_message(aTHX_ *message, *perl_package, gpd::MappingOptions(aTHX_ options)); %}; 25 | 26 | void map_enum(const std::string &enum_name, const std::string &perl_package, SV *options = NULL) 27 | %code{% THIS->map_enum(aTHX_ *enum_name, *perl_package, gpd::MappingOptions(aTHX_ options)); %}; 28 | 29 | void map_service(const std::string &service_name, const std::string &perl_package, SV *options = NULL) 30 | %code{% THIS->map_service(aTHX_ *service_name, *perl_package, gpd::MappingOptions(aTHX_ options)); %}; 31 | 32 | void map_package(const std::string &pb_package, const std::string &perl_package, SV *options = NULL) 33 | %code{% THIS->map_package(aTHX_ *pb_package, *perl_package, gpd::MappingOptions(aTHX_ options)); %}; 34 | 35 | void map_package_prefix(const std::string &pb_package, const std::string &perl_package, SV *options = NULL) 36 | %code{% THIS->map_package_prefix(aTHX_ *pb_package, *perl_package, gpd::MappingOptions(aTHX_ options)); %}; 37 | 38 | void map_message_prefix(const std::string &pb_message, const std::string &perl_package_prefix, SV *options = NULL) 39 | %code{% THIS->map_message_prefix(aTHX_ *pb_message, *perl_package_prefix, gpd::MappingOptions(aTHX_ options)); %}; 40 | 41 | void resolve_references(); 42 | 43 | static bool is_proto3(); 44 | static bool has_proto3_optional(); 45 | }; 46 | -------------------------------------------------------------------------------- /xsp/main.xspt: -------------------------------------------------------------------------------- 1 | %loadplugin{feature::default_xs_typemap}; 2 | 3 | %typemap{SV *}; 4 | %typemap{std::string}; 5 | %typemap{const std::string}; 6 | 7 | %typemap{uint32_t}{simple}{ 8 | %xs_type{T_UV}; 9 | }; 10 | %typemap{int32_t}{simple}{ 11 | %xs_type{T_IV}; 12 | }; 13 | --------------------------------------------------------------------------------