protobuf 3.6.12 → 3.7.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +4 -4
  3. data/CHANGES.md +14 -3
  4. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +39 -2
  5. data/lib/protobuf/field.rb +2 -2
  6. data/lib/protobuf/field/base_field.rb +27 -84
  7. data/lib/protobuf/field/bool_field.rb +3 -13
  8. data/lib/protobuf/field/bytes_field.rb +10 -26
  9. data/lib/protobuf/field/enum_field.rb +10 -20
  10. data/lib/protobuf/field/message_field.rb +13 -23
  11. data/lib/protobuf/generators/enum_generator.rb +1 -0
  12. data/lib/protobuf/generators/field_generator.rb +8 -2
  13. data/lib/protobuf/generators/file_generator.rb +8 -0
  14. data/lib/protobuf/generators/service_generator.rb +2 -2
  15. data/lib/protobuf/message.rb +47 -12
  16. data/lib/protobuf/message/fields.rb +80 -8
  17. data/lib/protobuf/rpc/connectors/common.rb +1 -1
  18. data/lib/protobuf/rpc/connectors/ping.rb +2 -2
  19. data/lib/protobuf/rpc/connectors/zmq.rb +1 -1
  20. data/lib/protobuf/rpc/middleware/exception_handler.rb +0 -4
  21. data/lib/protobuf/rpc/middleware/logger.rb +0 -4
  22. data/lib/protobuf/rpc/middleware/request_decoder.rb +16 -11
  23. data/lib/protobuf/rpc/middleware/response_encoder.rb +20 -15
  24. data/lib/protobuf/rpc/service.rb +10 -2
  25. data/lib/protobuf/rpc/service_directory.rb +0 -8
  26. data/lib/protobuf/rpc/service_dispatcher.rb +6 -5
  27. data/lib/protobuf/rpc/service_filters.rb +30 -8
  28. data/lib/protobuf/version.rb +1 -1
  29. data/proto/google/protobuf/descriptor.proto +190 -31
  30. data/spec/lib/protobuf/field/bool_field_spec.rb +33 -7
  31. data/spec/lib/protobuf/field/enum_field_spec.rb +26 -0
  32. data/spec/lib/protobuf/field/float_field_spec.rb +32 -1
  33. data/spec/lib/protobuf/field/int32_field_spec.rb +32 -1
  34. data/spec/lib/protobuf/field/message_field_spec.rb +79 -0
  35. data/spec/lib/protobuf/field/string_field_spec.rb +34 -0
  36. data/spec/lib/protobuf/field_spec.rb +1 -0
  37. data/spec/lib/protobuf/generators/enum_generator_spec.rb +9 -0
  38. data/spec/lib/protobuf/generators/service_generator_spec.rb +9 -0
  39. data/spec/lib/protobuf/message_spec.rb +328 -63
  40. data/spec/lib/protobuf/rpc/connectors/ping_spec.rb +3 -3
  41. data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +18 -1
  42. data/spec/support/protos/enum.pb.rb +1 -1
  43. data/spec/support/protos/google_unittest.pb.rb +113 -111
  44. data/spec/support/protos/google_unittest.proto +7 -0
  45. data/spec/support/protos/multi_field_extensions.pb.rb +1 -1
  46. data/spec/support/protos/resource.pb.rb +9 -9
  47. metadata +79 -5
@@ -1,8 +1,8 @@
1
- require 'protobuf/field/varint_field'
1
+ require 'protobuf/field/integer_field'
2
2
 
3
3
  module Protobuf
4
4
  module Field
5
- class EnumField < VarintField
5
+ class EnumField < IntegerField
6
6
 
7
7
  ##
8
8
  # Class Methods
@@ -25,36 +25,26 @@ module Protobuf
25
25
  end
26
26
 
27
27
  def decode(value)
28
- value if acceptable?(value)
28
+ decoded = super(value)
29
+ decoded if acceptable?(decoded)
29
30
  end
30
31
 
31
32
  def enum?
32
33
  true
33
34
  end
34
35
 
36
+ def coerce!(value)
37
+ enum_value = type_class.fetch(value)
38
+ fail TypeError, "Invalid Enum value: #{value.inspect} for #{name}" unless enum_value
39
+ enum_value
40
+ end
41
+
35
42
  private
36
43
 
37
44
  ##
38
45
  # Private Instance Methods
39
46
  #
40
47
 
41
- def define_setter
42
- field = self
43
- message_class.class_eval do
44
- define_method("#{field.name}=") do |value|
45
- orig_value = value
46
- if value.nil?
47
- @values.delete(field.name)
48
- else
49
- value = field.type_class.fetch(value)
50
- fail TypeError, "Invalid Enum value: #{orig_value.inspect} for #{field.name}" unless value
51
-
52
- @values[field.name] = value
53
- end
54
- end
55
- end
56
- end
57
-
58
48
  def typed_default_value
59
49
  if default.is_a?(Symbol)
60
50
  type_class.const_get(default)
@@ -9,7 +9,7 @@ module Protobuf
9
9
  #
10
10
 
11
11
  def acceptable?(val)
12
- val.is_a?(type_class) || val.respond_to?(:to_hash)
12
+ val.is_a?(type_class) || val.respond_to?(:to_hash) || val.respond_to?(:to_proto)
13
13
  end
14
14
 
15
15
  def decode(bytes)
@@ -30,30 +30,20 @@ module Protobuf
30
30
  ::Protobuf::WireType::LENGTH_DELIMITED
31
31
  end
32
32
 
33
- private
33
+ def coerce!(value)
34
+ return nil if value.nil?
34
35
 
35
- ##
36
- # Private Instance Methods
37
- #
36
+ coerced_value = if value.respond_to?(:to_proto)
37
+ value.to_proto
38
+ elsif value.respond_to?(:to_hash)
39
+ type_class.new(value.to_hash)
40
+ else
41
+ value
42
+ end
43
+
44
+ return coerced_value if coerced_value.is_a?(type_class)
38
45
 
39
- def define_setter
40
- field = self
41
- message_class.class_eval do
42
- define_method("#{field.name}=") do |val|
43
- case
44
- when val.nil?
45
- @values.delete(field.name)
46
- when val.is_a?(field.type_class)
47
- @values[field.name] = val
48
- when val.respond_to?(:to_proto)
49
- @values[field.name] = val.to_proto
50
- when val.respond_to?(:to_hash)
51
- @values[field.name] = field.type_class.new(val.to_hash)
52
- else
53
- fail TypeError, "Expected value of type '#{field.type_class}' for field #{field.name}, but got '#{val.class}'"
54
- end
55
- end
56
- end
46
+ fail TypeError, "Expected value of type '#{type_class}' for field #{name}, but got '#{value.class}'"
57
47
  end
58
48
 
59
49
  end
@@ -32,6 +32,7 @@ module Protobuf
32
32
 
33
33
  def build_value(enum_value_descriptor)
34
34
  name = enum_value_descriptor.name
35
+ name.upcase! if ENV.key?('PB_UPCASE_ENUMS')
35
36
  number = enum_value_descriptor.number
36
37
  "define :#{name}, #{number}"
37
38
  end
@@ -20,7 +20,13 @@ module Protobuf
20
20
  attr_reader :field_options
21
21
 
22
22
  def applicable_options
23
- @applicable_options ||= field_options.map { |k, v| ":#{k} => #{v}" }
23
+ # Note on the strange use of `#inspect`:
24
+ # :boom.inspect #=> ":boom"
25
+ # :".boom.foo".inspect #=> ":\".boom.foo\""
26
+ # An alternative to `#inspect` would be always adding double quotes,
27
+ # but the generatated code looks un-idiomatic:
28
+ # ":\"#{:boom}\"" #=> ":\"boom\"" <-- Note the unnecessary double quotes
29
+ @applicable_options ||= field_options.map { |k, v| "#{k.inspect} => #{v}" }
24
30
  end
25
31
 
26
32
  def default_value
@@ -64,7 +70,7 @@ module Protobuf
64
70
  end
65
71
 
66
72
  def name
67
- @name ||= ":#{descriptor.name}"
73
+ @name ||= descriptor.name.to_sym.inspect
68
74
  end
69
75
 
70
76
  def number
@@ -64,6 +64,11 @@ module Protobuf
64
64
  # the value is an array of field descriptors.
65
65
  #
66
66
  def map_extensions(descriptor, namespaces)
67
+ if fully_qualified_token?(descriptor.name)
68
+ fully_qualified_namespace = descriptor.name
69
+ elsif !(namespace = namespaces.reject(&:empty?).join(".")).empty?
70
+ fully_qualified_namespace = ".#{namespace}"
71
+ end
67
72
  # Record all the message descriptor name's we encounter (should be the whole tree).
68
73
  if descriptor.is_a?(::Google::Protobuf::DescriptorProto)
69
74
  if fully_qualified_token?(descriptor.name)
@@ -75,6 +80,9 @@ module Protobuf
75
80
  end
76
81
 
77
82
  descriptor.extension.each do |field_descriptor|
83
+ unless fully_qualified_token?(field_descriptor.name) && fully_qualified_namespace
84
+ field_descriptor.name = "#{fully_qualified_namespace}.#{field_descriptor.name}"
85
+ end
78
86
  @extension_fields[field_descriptor.extendee] << field_descriptor
79
87
  end
80
88
 
@@ -15,10 +15,10 @@ module Protobuf
15
15
  end
16
16
 
17
17
  def build_method(method_descriptor)
18
- name = method_descriptor.name
19
18
  request_klass = modulize(method_descriptor.input_type)
20
19
  response_klass = modulize(method_descriptor.output_type)
21
- "rpc :#{name.underscore}, #{request_klass}, #{response_klass}"
20
+ name = ENV.key?('PB_USE_RAW_RPC_NAMES') ? method_descriptor.name : method_descriptor.name.underscore
21
+ "rpc :#{name}, #{request_klass}, #{response_klass}"
22
22
  end
23
23
 
24
24
  end
@@ -77,14 +77,14 @@ module Protobuf
77
77
  return to_enum(:each_field) unless block_given?
78
78
 
79
79
  self.class.all_fields.each do |field|
80
- value = __send__(field.getter)
80
+ value = self[field.name]
81
81
  yield(field, value)
82
82
  end
83
83
  end
84
84
 
85
85
  def each_field_for_serialization
86
86
  self.class.all_fields.each do |field|
87
- value = @values[field.getter]
87
+ value = @values[field.fully_qualified_name]
88
88
  if value.nil?
89
89
  fail ::Protobuf::SerializationError, "Required field #{self.class.name}##{field.name} does not have a value." if field.required?
90
90
  next
@@ -95,13 +95,15 @@ module Protobuf
95
95
  end
96
96
 
97
97
  def field?(name)
98
- @values.key?(name)
98
+ field = self.class.get_field(name, true)
99
+ return false if field.nil?
100
+ @values.key?(field.fully_qualified_name)
99
101
  end
100
102
  ::Protobuf.deprecator.define_deprecated_methods(self, :has_field? => :field?)
101
103
 
102
104
  def inspect
103
105
  attrs = self.class.fields.map do |field|
104
- [field.name, send(field.name).inspect].join('=')
106
+ [field.name, self[field.name].inspect].join('=')
105
107
  end.join(' ')
106
108
 
107
109
  "#<#{self.class} #{attrs}>"
@@ -113,7 +115,7 @@ module Protobuf
113
115
 
114
116
  def respond_to_has_and_present?(key)
115
117
  respond_to_has?(key) &&
116
- (__send__(key).present? || [true, false].include?(__send__(key)))
118
+ (self[key].present? || [true, false].include?(self[key]))
117
119
  end
118
120
 
119
121
  # Return a hash-representation of the given fields for this message type.
@@ -121,9 +123,10 @@ module Protobuf
121
123
  result = {}
122
124
 
123
125
  @values.each_key do |field_name|
124
- value = __send__(field_name)
126
+ value = self[field_name]
127
+ field = self.class.get_field(field_name, true)
125
128
  hashed_value = value.respond_to?(:to_hash_value) ? value.to_hash_value : value
126
- result[field_name] = hashed_value
129
+ result[field.name] = hashed_value
127
130
  end
128
131
 
129
132
  result
@@ -140,20 +143,52 @@ module Protobuf
140
143
  def ==(other)
141
144
  return false unless other.is_a?(self.class)
142
145
  each_field do |field, value|
143
- return false unless value == other.__send__(field.name)
146
+ return false unless value == other[field.name]
144
147
  end
145
148
  true
146
149
  end
147
150
 
148
151
  def [](name)
149
152
  if (field = self.class.get_field(name, true))
150
- __send__(field.getter)
153
+ if field.repeated?
154
+ @values[field.fully_qualified_name] ||= ::Protobuf::Field::FieldArray.new(field)
155
+ elsif @values.key?(field.fully_qualified_name)
156
+ @values[field.fully_qualified_name]
157
+ else
158
+ field.default_value
159
+ end
160
+ else
161
+ fail ArgumentError, "invalid field name=#{name.inspect}"
151
162
  end
152
163
  end
153
164
 
154
165
  def []=(name, value)
155
166
  if (field = self.class.get_field(name, true))
156
- __send__(field.setter, value) unless value.nil?
167
+ if field.repeated?
168
+ if value.is_a?(Array)
169
+ value = value.compact
170
+ else
171
+ fail TypeError, <<-TYPE_ERROR
172
+ Expected repeated value of type '#{field.type_class}'
173
+ Got '#{value.class}' for repeated protobuf field #{field.name}
174
+ TYPE_ERROR
175
+ end
176
+
177
+ if value.empty?
178
+ @values.delete(field.fully_qualified_name)
179
+ else
180
+ @values[field.fully_qualified_name] ||= ::Protobuf::Field::FieldArray.new(field)
181
+ @values[field.fully_qualified_name].replace(value)
182
+ end
183
+ else
184
+ if value.nil?
185
+ @values.delete(field.fully_qualified_name)
186
+ elsif field.acceptable?(value)
187
+ @values[field.fully_qualified_name] = field.coerce!(value)
188
+ else
189
+ fail TypeError, "Unacceptable value #{value} for field #{field.name} of type #{field.type_class}"
190
+ end
191
+ end
157
192
  else
158
193
  unless ::Protobuf.ignore_unknown_fields?
159
194
  fail ::Protobuf::FieldNotDefinedError, name
@@ -193,9 +228,9 @@ module Protobuf
193
228
  object.__send__(:initialize)
194
229
  @values.each do |name, value|
195
230
  if value.is_a?(::Protobuf::Field::FieldArray)
196
- object.__send__(name).replace(value.map { |v| duplicate.call(v) })
231
+ object[name].replace(value.map { |v| duplicate.call(v) })
197
232
  else
198
- object.__send__("#{name}=", duplicate.call(value))
233
+ object[name] = duplicate.call(value)
199
234
  end
200
235
  end
201
236
  object
@@ -1,7 +1,11 @@
1
+ require "set"
2
+
1
3
  module Protobuf
2
4
  class Message
3
5
  module Fields
4
6
 
7
+ ACCESSOR_SUFFIXES = ["", "=", "!", "?"].freeze
8
+
5
9
  def self.extended(other)
6
10
  other.extend(ClassMethods)
7
11
  ::Protobuf.deprecator.define_deprecated_methods(
@@ -92,20 +96,88 @@ module Protobuf
92
96
  end
93
97
  end
94
98
 
95
- def define_field(rule, type_class, field_name, tag, options)
96
- raise_if_tag_collision(tag, field_name)
97
- raise_if_name_collision(field_name)
99
+ def define_field(rule, type_class, fully_qualified_field_name, tag, options)
100
+ raise_if_tag_collision(tag, fully_qualified_field_name)
101
+ raise_if_name_collision(fully_qualified_field_name)
102
+
103
+ # Determine appropirate accessor for fields depending on name collisions via extensions:
104
+
105
+ # Case 1: Base field = "string_field" and no extensions of the same name
106
+ # Result:
107
+ # message.string_field #=> @values["string_field"]
108
+ # message[:string_field] #=> @values["string_field"]
109
+ # message['string_field'] #=> @values["string_field"]
110
+
111
+ # Case 2: Base field = "string_field" and extension 1 = ".my_package.string_field", extension N = ".package_N.string_field"...
112
+ # Result:
113
+ # message.string_field #=> @values["string_field"]
114
+ # message[:string_field] #=> @values["string_field"]
115
+ # message['string_field'] #=> @values["string_field"]
116
+ # message[:'.my_package.string_field'] #=> @values[".my_package.string_field"]
117
+ # message['.my_package.string_field'] #=> @values[".my_package.string_field"]
118
+
119
+ # Case 3: No base field, extension 1 = ".my_package.string_field", extension 2 = ".other_package.string_field", extension N...
120
+ # Result:
121
+ # message.string_field #=> raise NoMethodError (no simple accessor allowed)
122
+ # message[:string_field] #=> raise NoMethodError (no simple accessor allowed)
123
+ # message['string_field'] #=> raise NoMethodError (no simple accessor allowed)
124
+ # message[:'.my_package.string_field'] #=> @values[".my_package.string_field"]
125
+ # message['.my_package.string_field'] #=> @values[".my_package.string_field"]
126
+ # message[:'.other_package.string_field'] #=> @values[".other_package.string_field"]
127
+ # message['.other_package.string_field'] #=> @values[".other_package.string_field"]
128
+
129
+ # Case 4: No base field, extension = ".my_package.string_field", no other extensions
130
+ # Result:
131
+ # message.string_field #=> @values[".my_package.string_field"]
132
+ # message[:string_field] #=> @values[".my_package.string_field"]
133
+ # message['string_field'] #=> @values[".my_package.string_field"]
134
+ # message[:'.my_package.string_field'] #=> @values[".my_package.string_field"]
135
+ # message[:'.my_package.string_field'] #=> @values[".my_package.string_field"]
136
+
137
+ if options[:extension]
138
+ base_name = fully_qualified_field_name.to_s.split('.').last.to_sym
139
+ if field_store[base_name]
140
+ # Case 3
141
+ if field_store[base_name].extension?
142
+ remove_existing_accessors(base_name)
143
+ simple_name = nil
144
+ # Case 2
145
+ else
146
+ simple_name = nil
147
+ end
148
+ # Case 4
149
+ else
150
+ simple_name = base_name
151
+ end
152
+ else
153
+ # Case 1
154
+ simple_name = fully_qualified_field_name
155
+ end
98
156
 
99
- field = ::Protobuf::Field.build(self, rule, type_class, field_name, tag, options)
157
+ field = ::Protobuf::Field.build(self, rule, type_class, fully_qualified_field_name,
158
+ tag, simple_name, options)
100
159
  field_store[tag] = field
101
- field_store[field_name] = field
102
- field_store[field_name.to_s] = field
160
+ field_store[fully_qualified_field_name.to_sym] = field
161
+ field_store[fully_qualified_field_name.to_s] = field
162
+ if simple_name && simple_name != fully_qualified_field_name
163
+ field_store[simple_name.to_sym] = field
164
+ field_store[simple_name.to_s] = field
165
+ end
103
166
  # defining a new field for the message will cause cached @all_fields, @extension_fields,
104
167
  # and @fields to be incorrect; reset them
105
168
  @all_fields = @extension_fields = @fields = nil
169
+ end
106
170
 
107
- define_method("#{field_name}!") do
108
- @values[field_name]
171
+ def remove_existing_accessors(accessor)
172
+ field_store.delete(accessor.to_sym).try(:fully_qualified_name_only!)
173
+ field_store.delete(accessor.to_s)
174
+ ACCESSOR_SUFFIXES.each do |modifier|
175
+ begin
176
+ remove_method("#{accessor}#{modifier}")
177
+ # rubocop: disable Lint/HandleExceptions
178
+ rescue NameError
179
+ # Do not remove the method
180
+ end
109
181
  end
110
182
  end
111
183
 
@@ -119,7 +119,7 @@ module Protobuf
119
119
  def setup_connection
120
120
  initialize_stats
121
121
  @request_data = request_bytes
122
- @stats.request_size = @request_data.size
122
+ @stats.request_size = request_bytes.size
123
123
  end
124
124
 
125
125
  def succeed(response)
@@ -29,9 +29,9 @@ module Protobuf
29
29
  def timeout
30
30
  @timeout ||= begin
31
31
  if ::ENV.key?("PB_RPC_PING_PORT_TIMEOUT")
32
- ::ENV["PB_RPC_PING_PORT_TIMEOUT"].to_f / 1000
32
+ ::ENV["PB_RPC_PING_PORT_TIMEOUT"].to_i
33
33
  else
34
- 0.2 # 200 ms
34
+ 5 # 5 seconds
35
35
  end
36
36
  end
37
37
  end
@@ -226,7 +226,7 @@ module Protobuf
226
226
 
227
227
  # Alias for ::Protobuf::Rpc::ServiceDirectory.instance
228
228
  def service_directory
229
- ::Protobuf::Rpc.service_directory
229
+ ::Protobuf::Rpc::ServiceDirectory.instance
230
230
  end
231
231
 
232
232
  def snd_timeout
@@ -11,10 +11,6 @@ module Protobuf
11
11
  end
12
12
 
13
13
  def call(env)
14
- dup._call(env)
15
- end
16
-
17
- def _call(env)
18
14
  app.call(env)
19
15
  rescue => exception
20
16
  log_exception(exception)