diff --git a/lib/pb/serializable.rb b/lib/pb/serializable.rb index fdb0b22..327b4e7 100644 --- a/lib/pb/serializable.rb +++ b/lib/pb/serializable.rb @@ -17,7 +17,7 @@ def self.included(base) # @param with [ # Google::Protobuf::FieldMask, # Array<(Symbol, Hash)>, - # Hash{Symbol=>(Array,Symbol,Hash)}, + # Hash{Symbol=>(Array,Symbol,Hash,Proc)}, # ] # Specifies the list of fields to be serialized in the Proto message object. # `nil` means that all fields defined in .proto will be serialized. diff --git a/lib/pb/serializer.rb b/lib/pb/serializer.rb index 9c8aadd..f8c97e7 100644 --- a/lib/pb/serializer.rb +++ b/lib/pb/serializer.rb @@ -81,7 +81,7 @@ def build_default_mask(descriptor) "google.protobuf.BoolValue" , "google.protobuf.BytesValue" then m << fd.name.to_sym else - m << { fd.name.to_sym => build_default_mask(fd.subtype) } + m << { fd.name.to_sym => -> { build_default_mask(fd.subtype) } } end else m << fd.name.to_sym @@ -102,22 +102,24 @@ def parse_field_mask(field_mask) end end - # @param input [Google::Protobuf::FieldMask, Symbol, Array<(Symbol,Hash)>, Hash{Symbol=>(Array,Symbol,Hash)}] - # @return [Hash{Symbol=>(Array,Hash)}] + # @param input [Google::Protobuf::FieldMask, Symbol, Array<(Symbol,Hash)>, Hash{Symbol=>(Array,Symbol,Hash,Proc)}, Proc] + # @return [Hash{Symbol=>(Array,Hash,Proc)}] def normalize_mask(input) if input.kind_of?(Google::Protobuf::FieldMask) input = parse_field_mask(input) end - normalized = {} - + input = input.call if input.kind_of?(Proc) input = [input] if input.kind_of?(Hash) + + normalized = {} Array(input).each do |el| case el when Symbol normalized[el] ||= [] when Hash el.each do |k, v| + v = v.call if v.kind_of?(Proc) v = [v] if v.kind_of?(Hash) normalized[k] ||= [] normalized[k].push(*Array(v)) diff --git a/spec/examples/recursive_spec.rb b/spec/examples/recursive_spec.rb new file mode 100644 index 0000000..04c5ce1 --- /dev/null +++ b/spec/examples/recursive_spec.rb @@ -0,0 +1,33 @@ +RSpec.describe 'recursive models' do + module self::Sandbox + class StringListSerializer < Pb::Serializer::Base + message TestFixture::Recursive::StringList + + attribute :car + attribute :cdr, allow_nil: true, serializer: StringListSerializer + end + + StringList = Struct.new(:car, :cdr) + end + + let(:sandbox) { self.class::Sandbox } + + it "serializes recursive ruby object into protobuf type" do + l = sandbox::StringList.new( + "Alpha", + sandbox::StringList.new( + "Bravo", + sandbox::StringList.new( + "Charlie", + nil, + ), + ), + ) + pb = sandbox::StringListSerializer.new(l).to_pb + + expect(pb).to be_a(TestFixture::Recursive::StringList) + expect(pb.car).to eq "Alpha" + expect(pb.cdr.car).to eq "Bravo" + expect(pb.cdr.cdr.car).to eq "Charlie" + end +end diff --git a/spec/fixtures/recursive_model.proto b/spec/fixtures/recursive_model.proto new file mode 100644 index 0000000..4e877c3 --- /dev/null +++ b/spec/fixtures/recursive_model.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package example.recursive; + +option ruby_package = "TestFixture::Recursive"; + +message StringList { + string car = 1; + StringList cdr = 2; +} diff --git a/spec/fixtures/recursive_model_pb.rb b/spec/fixtures/recursive_model_pb.rb new file mode 100644 index 0000000..68cb3fd --- /dev/null +++ b/spec/fixtures/recursive_model_pb.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: recursive_model.proto + +require 'google/protobuf' + + +descriptor_data = "\n\x15recursive_model.proto\x12\x11\x65xample.recursive\"E\n\nStringList\x12\x0b\n\x03\x63\x61r\x18\x01 \x01(\t\x12*\n\x03\x63\x64r\x18\x02 \x01(\x0b\x32\x1d.example.recursive.StringListB\x19\xea\x02\x16TestFixture::Recursiveb\x06proto3" + +pool = Google::Protobuf::DescriptorPool.generated_pool + +begin + pool.add_serialized_file(descriptor_data) +rescue TypeError => e + # Compatibility code: will be removed in the next major version. + require 'google/protobuf/descriptor_pb' + parsed = Google::Protobuf::FileDescriptorProto.decode(descriptor_data) + parsed.clear_dependency + serialized = parsed.class.encode(parsed) + file = pool.add_serialized_file(serialized) + warn "Warning: Protobuf detected an import path issue while loading generated file #{__FILE__}" + imports = [ + ] + imports.each do |type_name, expected_filename| + import_file = pool.lookup(type_name).file_descriptor + if import_file.name != expected_filename + warn "- #{file.name} imports #{expected_filename}, but that import was loaded as #{import_file.name}" + end + end + warn "Each proto file must use a consistent fully-qualified name." + warn "This will become an error in the next major version." +end + +module TestFixture + module Recursive + StringList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("example.recursive.StringList").msgclass + end +end