1616
1717module Gapic
1818 ##
19- # TODO: Describe Protobuf
19+ # A set of internal utilities for coercing data to protobuf messages.
20+ #
2021 module Protobuf
2122 ##
2223 # Creates an instance of a protobuf message from a hash that may include nested hashes. `google/protobuf` allows
@@ -31,10 +32,13 @@ module Protobuf
3132 def self . coerce hash , to :
3233 return hash if hash . is_a? to
3334
35+ # Special case handling of certain types
36+ return time_to_timestamp hash if to == Google ::Protobuf ::Timestamp && hash . is_a? ( Time )
37+
3438 # Sanity check: input must be a Hash
3539 raise ArgumentError , "Value #{ hash } must be a Hash or a #{ to . name } " unless hash . is_a? Hash
3640
37- hash = coerce_submessages hash , to
41+ hash = coerce_submessages hash , to . descriptor
3842 to . new hash
3943 end
4044
@@ -44,89 +48,58 @@ def self.coerce hash, to:
4448 # @private
4549 #
4650 # @param hash [Hash] The hash whose nested hashes will be coerced.
47- # @param message_class [Class ] The corresponding protobuf message class of the given hash .
51+ # @param message_descriptor [Google::Protobuf::Descriptor ] The protobuf descriptor for the message .
4852 #
4953 # @return [Hash] A hash whose nested hashes have been coerced.
50- def self . coerce_submessages hash , message_class
54+ def self . coerce_submessages hash , message_descriptor
5155 return nil if hash . nil?
5256 coerced = { }
53- message_descriptor = message_class . descriptor
5457 hash . each do |key , val |
5558 field_descriptor = message_descriptor . lookup key . to_s
56- coerced [ key ] = if field_descriptor && field_descriptor . type == :message
57- coerce_submessage val , field_descriptor
58- elsif field_descriptor && field_descriptor . type == :bytes &&
59- ( val . is_a? ( IO ) || val . is_a? ( StringIO ) )
60- val . binmode . read
61- else
62- # `google/protobuf` should throw an error if no field descriptor is
63- # found. Simply pass through.
64- val
65- end
59+ coerced [ key ] =
60+ if field_descriptor &.type == :message
61+ coerce_submessage val , field_descriptor
62+ elsif field_descriptor &.type == :bytes && ( val . is_a? ( IO ) || val . is_a? ( StringIO ) )
63+ val . binmode . read
64+ else
65+ # For non-message fields, just pass the scalar value through.
66+ # Note: if field_descriptor is not found, we just pass the value
67+ # through and let protobuf raise an error.
68+ val
69+ end
6670 end
6771 coerced
6872 end
6973
7074 ##
71- # Coerces the value of a field to be acceptable by the instantiation method of the wrapping message.
75+ # Coerces a message-typed field.
76+ # The field can be a normal single message, a repeated message, or a map.
7277 #
7378 # @private
7479 #
75- # @param val [Object] The value to be coerced.
76- # @param field_descriptor [Google::Protobuf::FieldDescriptor] The field descriptor of the value .
80+ # @param val [Object] The value to coerce
81+ # @param field_descriptor [Google::Protobuf::FieldDescriptor] The field descriptor.
7782 #
78- # @return [Object] The coerced version of the given value.
7983 def self . coerce_submessage val , field_descriptor
80- if ( field_descriptor . label == :repeated ) && !( map_field? field_descriptor )
81- coerce_array val , field_descriptor
82- elsif field_descriptor . subtype . msgclass == Google ::Protobuf ::Timestamp && val . is_a? ( Time )
83- time_to_timestamp val
84+ if val . is_a? Array
85+ # Assume this is a repeated message field, iterate over it and coerce
86+ # each to the message class.
87+ # Protobuf will raise an error if this assumption is incorrect.
88+ val . map do |elem |
89+ coerce elem , to : field_descriptor . subtype . msgclass
90+ end
91+ elsif field_descriptor . label == :repeated
92+ # Non-array passed to a repeated field: assume this is a map, and that
93+ # a hash is being passed, and let protobuf handle the conversion.
94+ # Protobuf will raise an error if this assumption is incorrect.
95+ val
8496 else
85- coerce_value val , field_descriptor
86- end
87- end
88-
89- ##
90- # Coerces the values of an array to be acceptable by the instantiation method the wrapping message.
91- #
92- # @private
93- #
94- # @param array [Array<Object>] The values to be coerced.
95- # @param field_descriptor [Google::Protobuf::FieldDescriptor] The field descriptor of the values.
96- #
97- # @return [Array<Object>] The coerced version of the given values.
98- def self . coerce_array array , field_descriptor
99- raise ArgumentError , "Value #{ array } must be an array" unless array . is_a? Array
100- array . map do |val |
101- coerce_value val , field_descriptor
97+ # Assume this is a normal single message, and coerce to the message
98+ # class.
99+ coerce val , to : field_descriptor . subtype . msgclass
102100 end
103101 end
104102
105- ##
106- # Hack to determine if field_descriptor is for a map.
107- #
108- # TODO(geigerj): Remove this once protobuf Ruby supports an official way
109- # to determine if a FieldDescriptor represents a map.
110- # See: https://github.com/google/protobuf/issues/3425
111- def self . map_field? field_descriptor
112- ( field_descriptor . label == :repeated ) &&
113- ( field_descriptor . subtype . name . include? "_MapEntry_" )
114- end
115-
116- ##
117- # Coerces the value of a field to be acceptable by the instantiation method of the wrapping message.
118- #
119- # @private
120- #
121- # @param val [Object] The value to be coerced.
122- # @param field_descriptor [Google::Protobuf::FieldDescriptor] The field descriptor of the value.
123- #
124- # @return [Object] The coerced version of the given value.
125- def self . coerce_value val , field_descriptor
126- return val unless ( val . is_a? Hash ) && !( map_field? field_descriptor )
127- coerce val , to : field_descriptor . subtype . msgclass
128- end
129-
130103 ##
131104 # Utility for converting a Google::Protobuf::Timestamp instance to a Ruby time.
132105 #
@@ -147,6 +120,6 @@ def self.time_to_timestamp time
147120 Google ::Protobuf ::Timestamp . new seconds : time . to_i , nanos : time . nsec
148121 end
149122
150- private_class_method :coerce_submessages , :coerce_submessage , :coerce_array , :coerce_value , :map_field?
123+ private_class_method :coerce_submessages , :coerce_submessage
151124 end
152125end
0 commit comments