protobuf 3.10.9 → 3.10.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ed2f7beb2186899fa855dcdc852e2ab8d8433f0365d6bfd01bd5ad5d010b189
4
- data.tar.gz: fe237afa9ce1f42475919b85246f97890343a652b752f82b57863309b314a759
3
+ metadata.gz: 8096ad0d169fe7be73b2cce91ed6b45fc29ce8377f6599468ea4b165f3cba5fc
4
+ data.tar.gz: f562a6ffed606efb06c22f429859d3c6f34a95024e12775b5fe500242f4af760
5
5
  SHA512:
6
- metadata.gz: 223ba3d3993ca39c973228c6572d0b15ff21c86967114749fad157d19386ae7e285a6a40ab2a05187108f63f28f1d1242ed6861ac2f4dc456d8e1327cc46974e
7
- data.tar.gz: 374f7d7484f34098d8d07ede5b08be53842550ec0cc7291410b1d25b85289ffe921d0aa6d4dd55a39cf12005b2374ab4a9dd546f061e3f6e95b55e489f1b7845
6
+ metadata.gz: 759ac6f9f38a1eb1a462cbaa8b7f8c172563fe85963fa51e8557d37464def8dd486b66a4e272593b6a9cb10f6094df3848678b139dd8fe15fd057f3a1d50d512
7
+ data.tar.gz: eacef1704e921e60280621d4cea12396c43a20e597e0485dfb402c18cf2d918d110f33cfb88fec57d729eb1bdf797c15abef77216604c99b3dca845d55ac1e21
data/.circleci/config.yml CHANGED
@@ -1,46 +1,130 @@
1
1
  version: 2.1
2
2
 
3
3
  jobs:
4
- # skipping build step because Gemfile.lock is not included in the source
5
- # this makes the bundler caching step a noop
4
+ rubocop:
5
+ docker:
6
+ # RuboCop 0.81 does not load on Ruby 4.0 (it needs the benchmark gem).
7
+ - image: cimg/ruby:3.4
8
+ steps:
9
+ - checkout
10
+ - run:
11
+ name: Install gems
12
+ command: bundle install --jobs 4 --retry 3
13
+ - run:
14
+ name: RuboCop
15
+ command: bundle exec rubocop
16
+
6
17
  test:
7
18
  parameters:
8
- ruby-image:
19
+ ruby:
20
+ type: string
21
+ rails:
9
22
  type: string
10
23
  docker:
11
- - image: << parameters.ruby-image >>
24
+ - image: cimg/ruby:<< parameters.ruby >>
25
+ environment:
26
+ # Newer protoc versions reject the explicit `map_entry` option in the spec protos.
27
+ PROTOC_VERSION: "3.16.0"
28
+ RAILS_VERSION: "~> << parameters.rails >>.0"
12
29
  steps:
30
+ - checkout
31
+ - run:
32
+ name: Install ZeroMQ
33
+ command: sudo apt-get update && sudo apt-get install -y libzmq3-dev
13
34
  - run:
14
- name: Install protobuf compiler
35
+ name: Install protoc
15
36
  command: |
16
- archive=protoc-3.16.0-linux-x86_64
17
- curl -O -L https://github.com/protocolbuffers/protobuf/releases/download/v3.16.0/$archive.zip
18
- sudo unzip -d '/usr/local' $archive.zip 'bin/*' 'include/*'
19
- sudo chown -R $(whoami) /usr/local/bin/protoc /usr/local/include/google
20
- rm -rf $archive.zip
37
+ archive="protoc-${PROTOC_VERSION}-linux-x86_64"
38
+ curl -sSL -o "/tmp/${archive}.zip" \
39
+ "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${archive}.zip"
40
+ sudo unzip -q -o -d /usr/local "/tmp/${archive}.zip" 'bin/*' 'include/*'
41
+ sudo chmod -R a+rX /usr/local/bin/protoc /usr/local/include/google
42
+ protoc --version
43
+ # Gemfile.lock is not committed, so each job resolves gems for its own
44
+ # Ruby and RAILS_VERSION.
45
+ - run:
46
+ name: Install gems
47
+ command: bundle install --jobs 4 --retry 3
48
+ - run:
49
+ name: Compile protos
50
+ command: bundle exec rake compile:spec compile:rpc
21
51
  - run:
22
- command: protoc --version
52
+ name: Check compiled protos are committed
53
+ command: git diff --exit-code
23
54
  - run:
24
- command: sudo apt-get update
55
+ name: Run specs
56
+ command: bundle exec rspec
57
+
58
+ test-jruby:
59
+ parameters:
60
+ jruby:
61
+ type: string
62
+ rails:
63
+ type: string
64
+ docker:
65
+ - image: jruby:<< parameters.jruby >>
66
+ environment:
67
+ PROTOC_VERSION: "3.16.0"
68
+ RAILS_VERSION: "~> << parameters.rails >>.0"
69
+ JRUBY_OPTS: "--dev"
70
+ steps:
71
+ # The jruby image has no git, so install it before checkout.
25
72
  - run:
26
- message: Install ZeroMQ
27
- command: sudo apt-get -y install libzmq3-dev
73
+ name: Install system packages
74
+ command: apt-get update && apt-get install -y git unzip build-essential libzmq3-dev
28
75
  - checkout
29
76
  - run:
30
- command: gem install bundler
77
+ name: Install protoc
78
+ command: |
79
+ archive="protoc-${PROTOC_VERSION}-linux-x86_64"
80
+ curl -sSL -o "/tmp/${archive}.zip" \
81
+ "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${archive}.zip"
82
+ unzip -q -o -d /usr/local "/tmp/${archive}.zip" 'bin/*' 'include/*'
83
+ protoc --version
84
+ - run:
85
+ name: Install gems
86
+ command: bundle install --jobs 4 --retry 3
87
+ - run:
88
+ name: Compile protos
89
+ command: bundle exec rake compile:spec compile:rpc
31
90
  - run:
32
- command: bundle install
91
+ name: Check compiled protos are committed
92
+ command: git diff --exit-code
33
93
  - run:
34
- command: bundle exec rake
94
+ name: Run specs
95
+ command: bundle exec rspec
35
96
 
36
97
  workflows:
37
98
  build_and_test:
38
99
  jobs:
100
+ - rubocop
39
101
  - test:
102
+ name: test-ruby-<< matrix.ruby >>-rails-<< matrix.rails >>
103
+ matrix:
104
+ parameters:
105
+ ruby: ["3.1", "3.2", "3.3", "3.4", "4.0"]
106
+ rails: ["6.0", "6.1", "7.0", "7.1", "7.2", "8.0", "8.1"]
107
+ exclude:
108
+ # Rails 8.0+ requires Ruby 3.2+.
109
+ - { ruby: "3.1", rails: "8.0" }
110
+ - { ruby: "3.1", rails: "8.1" }
111
+ # Rails 6.x does not load on Ruby 3.4+: it needs bigdecimal and
112
+ # logger, which are no longer default gems, and does not declare them.
113
+ - { ruby: "3.4", rails: "6.0" }
114
+ - { ruby: "3.4", rails: "6.1" }
115
+ - { ruby: "4.0", rails: "6.0" }
116
+ - { ruby: "4.0", rails: "6.1" }
117
+ - test-jruby:
118
+ name: test-jruby-<< matrix.jruby >>-rails-<< matrix.rails >>
40
119
  matrix:
41
120
  parameters:
42
- ruby-image:
43
- - circleci/jruby:9.2.6.0-jdk
44
- - circleci/jruby:9.1.17.0-jdk
45
- - circleci/ruby:2.5
46
- - circleci/ruby:2.7
121
+ # JRuby 9.4 is Ruby 3.1; JRuby 10.0 is Ruby 3.4.
122
+ jruby: ["9.4", "10.0"]
123
+ rails: ["6.1", "7.1", "7.2", "8.0", "8.1"]
124
+ exclude:
125
+ # Rails 8.0+ requires Ruby 3.2+.
126
+ - { jruby: "9.4", rails: "8.0" }
127
+ - { jruby: "9.4", rails: "8.1" }
128
+ # Rails 6.x does not load on Ruby 3.4 (base64 and bigdecimal are
129
+ # no longer default gems).
130
+ - { jruby: "10.0", rails: "6.1" }
data/.rubocop.yml CHANGED
@@ -2,6 +2,9 @@ inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
4
  DisplayCopNames: true
5
+ # The newest version RuboCop 0.81 knows. Without it, RuboCop reads the Ruby
6
+ # version from .ruby-version or Gemfile.lock and fails on Ruby 3.x.
7
+ TargetRubyVersion: 2.7
5
8
  Exclude:
6
9
  - 'spec/support/protos/*.pb.rb'
7
10
  - 'varint_prof.rb'
@@ -79,3 +82,8 @@ Style/TrivialAccessors:
79
82
  Style/Encoding:
80
83
  Exclude:
81
84
  - '**/*.pb.rb'
85
+
86
+ # TargetRubyVersion (2.7) cannot match required_ruby_version (>= 3.1), because
87
+ # RuboCop 0.81 does not know Ruby 3.x.
88
+ Gemspec/RequiredRubyVersion:
89
+ Enabled: false
data/.rubocop_todo.yml CHANGED
@@ -6,6 +6,12 @@
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ # RuboCop 0.81 validates this file alone, before it merges it into
10
+ # .rubocop.yml, so it needs its own TargetRubyVersion. Keep it equal to the
11
+ # value in .rubocop.yml.
12
+ AllCops:
13
+ TargetRubyVersion: 2.7
14
+
9
15
  # Offense count: 1
10
16
  # Cop supports --auto-correct.
11
17
  # Configuration parameters: TreatCommentsAsGroupSeparators, Include.
data/CHANGES.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Stable (3.8.x)
2
2
 
3
+ 3.10.10
4
+ -------
5
+ - Decode repeated scalar fields by the wire type of each record, not by the
6
+ `packed` declaration. Packed input (the proto3 default in Go, Java, C++ and
7
+ google-protobuf) for a field without `packed` no longer decodes bools as
8
+ `false`, raises on integers and enums, or drops all but the first
9
+ fixed32/float/double value. (#438, #330, #414)
10
+ - Skip a record whose wire type does not match its known field, as the
11
+ reference parsers do. Before, such a record could decode to a wrong value
12
+ or raise `NoMethodError`/`TypeError` for string, bytes, message, and map
13
+ fields.
14
+ - Decode any non-zero bool varint as `true`.
15
+ - An `int32`, `uint32`, or `sint32` field keeps the low 32 bits of a wider
16
+ varint, as the reference parsers do. Before, a peer that sent an int64 value
17
+ for the field (a compatible schema change) made the whole decode raise
18
+ `TypeError`.
19
+ - Skip a group (wire types 3 and 4, from proto2 groups or editions `DELIMITED`
20
+ encoding) as an unknown field. Before, the decode raised
21
+ `Protobuf::InvalidWireType`.
22
+ - Support Ruby 4.0. CircleCI also tests Ruby 4.0, JRuby 9.4, and JRuby 10.0.
23
+ - Require Ruby >= 3.1 and ActiveSupport >= 6.0.
24
+ - Add `Protobuf::Rpc::ApplicationService`, a base class for application services.
25
+ - `Service.implemented_services` walks all descendants, not only direct subclasses.
26
+ - Add `Protobuf::Railtie`. Set `config.protobuf.services_path` to load that
27
+ directory when the RPC server binds, so dynamic discovery sees lazily loaded
28
+ services.
29
+ - Service filters and `rescue_from` handlers now pass from a parent class to
30
+ its subclasses.
31
+ - Add `PB_SERVICE_PARENT` to set the parent class of generated services.
32
+ - CircleCI tests Ruby 3.1 to 3.4 with Rails 6.0 to 8.1, and runs RuboCop.
33
+
3
34
 
4
35
  3.10.0
5
36
  ------
data/Gemfile CHANGED
@@ -2,19 +2,18 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ # CI sets RAILS_VERSION (for example "~> 6.1.0") to test each supported Rails.
6
+ # railties is also a gemspec development dependency, so only activesupport is
7
+ # pinned here and railties follows it.
8
+ if (rails_version = ENV['RAILS_VERSION'])
9
+ gem 'activesupport', rails_version
10
+ end
11
+
5
12
  group :development do
6
13
  # debuggers only work in MRI
7
14
  if RUBY_ENGINE.to_sym == :ruby
8
- if RUBY_VERSION < '2.0.0'
9
- gem 'pry-debugger'
10
- elsif RUBY_VERSION < '2.4.0'
11
- gem 'pry', '~> 0.12.0'
12
- gem 'pry-byebug'
13
- else
14
- gem 'pry', '~> 0.13.0'
15
- gem 'pry-byebug', '~> 3.9.0'
16
- end
17
-
15
+ gem 'pry'
16
+ gem 'pry-byebug'
18
17
  gem 'pry-stack_explorer'
19
18
 
20
19
  gem 'ruby-prof'
@@ -63,7 +63,14 @@ module Protobuf
63
63
  def define_set_method!
64
64
  end
65
65
 
66
- def set_without_options(message_instance, bytes)
66
+ # Same rules as the set methods in BaseFieldObjectDefinitions: a packable
67
+ # repeated field decodes by the record's wire type, not its declaration.
68
+ def set_without_options(message_instance, bytes, record_wire_type = nil)
69
+ if repeated? && packable?
70
+ return set_packable_without_options(message_instance[name], bytes, record_wire_type)
71
+ end
72
+
73
+ return if record_wire_type && record_wire_type != wire_type
67
74
  return message_instance[name] = decode(bytes) unless repeated?
68
75
 
69
76
  if map?
@@ -75,9 +82,14 @@ module Protobuf
75
82
  return hash[entry.key]
76
83
  end
77
84
 
78
- return message_instance[name] << decode(bytes) unless packed?
85
+ message_instance[name] << decode(bytes)
86
+ end
87
+
88
+ def set_packable_without_options(array, bytes, record_wire_type)
89
+ record_wire_type ||= packed? ? ::Protobuf::WireType::LENGTH_DELIMITED : wire_type
90
+ return array << decode(bytes) if record_wire_type == wire_type
91
+ return unless record_wire_type == ::Protobuf::WireType::LENGTH_DELIMITED
79
92
 
80
- array = message_instance[name]
81
93
  stream = StringIO.new(bytes)
82
94
 
83
95
  if wire_type == ::Protobuf::WireType::VARINT
@@ -92,7 +104,7 @@ module Protobuf
92
104
  # Sets a MessageField that is known to be an option.
93
105
  # We must allow fields to be set one at a time, as option syntax allows us to
94
106
  # set each field within the option using a separate "option" line.
95
- def set_with_options(message_instance, bytes)
107
+ def set_with_options(message_instance, bytes, record_wire_type = nil)
96
108
  if message_instance[name].is_a?(::Protobuf::Message)
97
109
  gp = Google::Protobuf
98
110
  if message_instance.is_a?(gp::EnumOptions) || message_instance.is_a?(gp::EnumValueOptions) ||
@@ -109,7 +121,7 @@ module Protobuf
109
121
  end
110
122
  end
111
123
 
112
- set_without_options(message_instance, bytes)
124
+ set_without_options(message_instance, bytes, record_wire_type)
113
125
  end
114
126
  alias_method :set, :set_with_options
115
127
 
@@ -1,7 +1,8 @@
1
1
  module Protobuf
2
2
  class Decoder
3
3
 
4
- # Read bytes from +stream+ and pass to +message+ object.
4
+ # Read bytes from +stream+ and yield (tag, bytes, wire_type) for each field.
5
+ # A block that takes only (tag, bytes) still works: blocks drop extra arguments.
5
6
  def self.decode_each_field(stream)
6
7
  until stream.eof?
7
8
  bits = Varint.decode(stream)
@@ -17,12 +18,42 @@ module Protobuf
17
18
  stream.read(8)
18
19
  elsif wire_type == ::Protobuf::WireType::FIXED32
19
20
  stream.read(4)
21
+ elsif wire_type == ::Protobuf::WireType::START_GROUP
22
+ skip_group(stream, tag)
23
+ next
20
24
  else
21
25
  fail InvalidWireType, wire_type
22
26
  end
23
27
 
24
- yield(tag, bytes)
28
+ yield(tag, bytes, wire_type)
25
29
  end
26
30
  end
31
+
32
+ # Skip a group (wire types 3 and 4), with any groups inside it. This gem
33
+ # has no group fields, so the rule for an unknown field applies, as in the
34
+ # reference parsers: skip it. A group without its end raises InvalidWireType.
35
+ def self.skip_group(stream, group_tag)
36
+ open_tags = [group_tag]
37
+ until open_tags.empty?
38
+ fail InvalidWireType, ::Protobuf::WireType::START_GROUP if stream.eof?
39
+
40
+ bits = Varint.decode(stream)
41
+ wire_type = bits & 0x07
42
+ tag = bits >> 3
43
+
44
+ case wire_type
45
+ when ::Protobuf::WireType::VARINT then Varint.decode(stream)
46
+ when ::Protobuf::WireType::LENGTH_DELIMITED then stream.read(Varint.decode(stream))
47
+ when ::Protobuf::WireType::FIXED64 then stream.read(8)
48
+ when ::Protobuf::WireType::FIXED32 then stream.read(4)
49
+ when ::Protobuf::WireType::START_GROUP then open_tags.push(tag)
50
+ when ::Protobuf::WireType::END_GROUP
51
+ fail InvalidWireType, wire_type unless open_tags.pop == tag
52
+ else
53
+ fail InvalidWireType, wire_type
54
+ end
55
+ end
56
+ end
57
+ private_class_method :skip_group
27
58
  end
28
59
  end
@@ -287,8 +287,8 @@ module Protobuf
287
287
  def define_set_method!
288
288
  @set_method = if map?
289
289
  OBJECT_MODULE::MapSetMethod.new(self)
290
- elsif repeated? && packed?
291
- OBJECT_MODULE::RepeatedPackedSetMethod.new(self)
290
+ elsif repeated? && packable?
291
+ OBJECT_MODULE::RepeatedPackableSetMethod.new(self)
292
292
  elsif repeated?
293
293
  OBJECT_MODULE::RepeatedNotPackedSetMethod.new(self)
294
294
  else
@@ -296,8 +296,16 @@ module Protobuf
296
296
  end
297
297
  end
298
298
 
299
- def set(message_instance, bytes)
300
- @set_method.call(message_instance, bytes)
299
+ # Whether the field's type can use the packed encoding (scalar numeric
300
+ # types). This is about the type, not about the `packed` option.
301
+ def packable?
302
+ ::Protobuf::Field::BaseField::PACKED_TYPES.include?(wire_type)
303
+ end
304
+
305
+ # +wire_type+ is the wire type of the record that +bytes+ came from. It is
306
+ # nil for callers that do not know it; then the field declaration decides.
307
+ def set(message_instance, bytes, wire_type = nil)
308
+ @set_method.call(message_instance, bytes, wire_type)
301
309
  end
302
310
 
303
311
  def tag_encoded
@@ -372,7 +380,7 @@ module Protobuf
372
380
  end
373
381
 
374
382
  def validate_packed_field
375
- if packed? && ! ::Protobuf::Field::BaseField::PACKED_TYPES.include?(wire_type)
383
+ if packed? && !packable?
376
384
  fail "Can't use packed encoding for '#{type_class}' type"
377
385
  end
378
386
  end
@@ -116,9 +116,13 @@ module Protobuf
116
116
  def initialize(selph)
117
117
  @selph = selph
118
118
  @name = selph.name
119
+ @wire_type = selph.wire_type
119
120
  end
120
121
 
121
- def call(message_instance, bytes)
122
+ # A record whose wire type differs from the field's is skipped, as the
123
+ # protobuf spec and the reference parsers do.
124
+ def call(message_instance, bytes, wire_type = nil)
125
+ return if wire_type && wire_type != @wire_type
122
126
  message_instance.set_field(@name, @selph.decode(bytes), true, @selph)
123
127
  end
124
128
  end
@@ -127,9 +131,11 @@ module Protobuf
127
131
  def initialize(selph)
128
132
  @selph = selph
129
133
  @name = selph.name
134
+ @wire_type = selph.wire_type
130
135
  end
131
136
 
132
- def call(message_instance, bytes)
137
+ def call(message_instance, bytes, wire_type = nil)
138
+ return if wire_type && wire_type != @wire_type
133
139
  hash = message_instance[@name]
134
140
  entry = @selph.decode(bytes)
135
141
  # decoded value could be nil for an
@@ -139,26 +145,54 @@ module Protobuf
139
145
  end
140
146
  end
141
147
 
148
+ # Repeated string, bytes, and message fields. These are never packed.
142
149
  class RepeatedNotPackedSetMethod
143
150
  def initialize(selph)
144
151
  @selph = selph
145
152
  @name = selph.name
153
+ @wire_type = selph.wire_type
146
154
  end
147
155
 
148
- def call(message_instance, bytes)
156
+ def call(message_instance, bytes, wire_type = nil)
157
+ return if wire_type && wire_type != @wire_type
149
158
  message_instance[@name] << @selph.decode(bytes)
150
159
  end
151
160
  end
152
161
 
153
- class RepeatedPackedSetMethod
162
+ # Repeated scalar numeric fields (varint, fixed32, fixed64). The protobuf
163
+ # spec requires a parser to accept both the packed and the unpacked
164
+ # encoding, whatever the field declares. The wire type of each record
165
+ # selects the decoding: LENGTH_DELIMITED is a packed run, and the field's
166
+ # own wire type is one value. A record without a wire type (an old
167
+ # caller of BaseField#set) falls back to the declaration.
168
+ class RepeatedPackableSetMethod
169
+ LENGTH_DELIMITED = ::Protobuf::WireType::LENGTH_DELIMITED
170
+
154
171
  def initialize(selph)
155
172
  @selph = selph
156
173
  @name = selph.name
157
174
  @wire_type = selph.wire_type
175
+ @packed = selph.packed?
176
+ end
177
+
178
+ def call(message_instance, bytes, wire_type = nil)
179
+ # Most common first: one unpacked value, as this gem encodes by default.
180
+ if wire_type == @wire_type
181
+ message_instance[@name] << @selph.decode(bytes)
182
+ elsif wire_type == LENGTH_DELIMITED
183
+ decode_packed(message_instance[@name], bytes)
184
+ elsif wire_type.nil?
185
+ if @packed
186
+ decode_packed(message_instance[@name], bytes)
187
+ else
188
+ message_instance[@name] << @selph.decode(bytes)
189
+ end
190
+ end
158
191
  end
159
192
 
160
- def call(message_instance, bytes)
161
- array = message_instance[@name]
193
+ private
194
+
195
+ def decode_packed(array, bytes)
162
196
  stream = ::StringIO.new(bytes)
163
197
 
164
198
  if @wire_type == ::Protobuf::WireType::VARINT
@@ -38,8 +38,9 @@ module Protobuf
38
38
  end
39
39
  end
40
40
 
41
+ # Any non-zero varint is true, as in the reference parsers.
41
42
  def decode(value)
42
- value == ONE
43
+ value != 0
43
44
  end
44
45
 
45
46
  def encode(value)
@@ -16,6 +16,18 @@ module Protobuf
16
16
  INT32_MIN
17
17
  end
18
18
 
19
+ ##
20
+ # Public Instance Methods
21
+ #
22
+
23
+ # Keep the low 32 bits, as the reference parsers do. A peer that sends
24
+ # an int64 or uint64 value for this field (a compatible schema change)
25
+ # then decodes instead of raising.
26
+ def decode(value)
27
+ value &= 0xffff_ffff
28
+ value >= 0x8000_0000 ? value - 0x1_0000_0000 : value
29
+ end
30
+
19
31
  end
20
32
  end
21
33
  end
@@ -16,6 +16,16 @@ module Protobuf
16
16
  INT32_MIN
17
17
  end
18
18
 
19
+ ##
20
+ # Public Instance Methods
21
+ #
22
+
23
+ # Keep the low 32 bits before the zigzag decode, as the reference
24
+ # parsers do. A peer that sends a sint64 value then decodes.
25
+ def decode(value)
26
+ super(value & 0xffff_ffff)
27
+ end
28
+
19
29
  end
20
30
  end
21
31
  end
@@ -16,6 +16,15 @@ module Protobuf
16
16
  0
17
17
  end
18
18
 
19
+ ##
20
+ # Public Instance Methods
21
+ #
22
+
23
+ # Keep the low 32 bits, as the reference parsers do.
24
+ def decode(value)
25
+ value & 0xffff_ffff
26
+ end
27
+
19
28
  end
20
29
  end
21
30
  end
@@ -82,12 +82,21 @@ module Protobuf
82
82
  when :enum then
83
83
  PARENT_CLASS_ENUM
84
84
  when :service then
85
- PARENT_CLASS_SERVICE
85
+ service_parent_class
86
86
  else
87
87
  fail "Unknown parent class type #{type}: #{caller[0..5].join("\n")}"
88
88
  end
89
89
  end
90
90
 
91
+ # PB_SERVICE_PARENT sets the parent class of generated services, for
92
+ # example `ApplicationService` or `::Protobuf::Rpc::ApplicationService`.
93
+ # The parent class must be loaded before the generated file.
94
+ #
95
+ def service_parent_class
96
+ parent = ENV['PB_SERVICE_PARENT'].to_s.strip
97
+ parent.empty? ? PARENT_CLASS_SERVICE : parent
98
+ end
99
+
91
100
  # Print a class or module block, indicated by type.
92
101
  # If a class, can be given a parent class to inherit from.
93
102
  # If a block is given, call the block from within an indent block.
@@ -38,8 +38,8 @@ module Protobuf
38
38
  # Decode the given stream into this message.
39
39
  #
40
40
  def decode_from(stream)
41
- ::Protobuf::Decoder.decode_each_field(stream) do |tag, bytes|
42
- set_field_bytes(tag, bytes)
41
+ ::Protobuf::Decoder.decode_each_field(stream) do |tag, bytes, wire_type|
42
+ set_field_bytes(tag, bytes, wire_type)
43
43
  end
44
44
 
45
45
  self
@@ -75,9 +75,9 @@ module Protobuf
75
75
 
76
76
  private
77
77
 
78
- def set_field_bytes(tag, bytes)
78
+ def set_field_bytes(tag, bytes, wire_type = nil)
79
79
  field = _protobuf_message_field[tag]
80
- field.set(self, bytes) if field
80
+ field.set(self, bytes, wire_type) if field
81
81
  end
82
82
 
83
83
  end
@@ -0,0 +1,48 @@
1
+ require 'rails/railtie'
2
+ require 'protobuf/service_loader'
3
+
4
+ module Protobuf
5
+ # RPC discovery (`Service.implemented_services`) walks `descendants`, which
6
+ # only sees loaded classes, and Zeitwerk loads lazily. This railtie loads the
7
+ # configured services directory one time, when the RPC server is about to
8
+ # bind. Web, console, and test processes never pay that cost.
9
+ #
10
+ # Opt in from config/application.rb:
11
+ #
12
+ # config.protobuf.services_path = 'app/services/rpc'
13
+ #
14
+ # A relative path resolves against Rails.root.
15
+ class Railtie < ::Rails::Railtie
16
+ config.protobuf = ::ActiveSupport::OrderedOptions.new
17
+ config.protobuf.services_path = nil
18
+
19
+ initializer 'protobuf.load_services' do |app|
20
+ ::ActiveSupport::Notifications.subscribe('before_server_bind') do
21
+ ::Protobuf::Railtie.load_services(app)
22
+ end
23
+ end
24
+
25
+ def self.services_path(app)
26
+ configured = app.config.protobuf.services_path.to_s
27
+ return if configured.empty?
28
+
29
+ ::File.expand_path(configured, app.root.to_s)
30
+ end
31
+
32
+ def self.load_services(app)
33
+ path = services_path(app)
34
+ return unless path && ::File.directory?(path)
35
+
36
+ loader = ::Rails.autoloaders.main # nil with the classic autoloader
37
+ if loader.respond_to?(:eager_load_dir) # Zeitwerk 2.6.2+
38
+ begin
39
+ return loader.eager_load_dir(path)
40
+ rescue ::Zeitwerk::Error
41
+ # The main autoloader does not manage this path. Fall through.
42
+ end
43
+ end
44
+
45
+ ::Protobuf::ServiceLoader.load_services(path)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ require 'protobuf/rpc/service'
2
+
3
+ module Protobuf
4
+ module Rpc
5
+ # Base class for application services, like `ApplicationController`.
6
+ # Filters and `rescue_from` handlers defined here pass to every subclass.
7
+ # Generate services with `PB_SERVICE_PARENT=::Protobuf::Rpc::ApplicationService`
8
+ # to inherit from it.
9
+ class ApplicationService < Service
10
+ end
11
+ end
12
+ end