proto_turf 0.0.1 → 0.0.3

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: 748636abfd68b4fbf48cb1786c3d2617ae0b48400c23e14c9ff03786601a4dd8
4
- data.tar.gz: 9b3e76306ecefa2cfe6ca7b097b366d8145ec4c3a2c1dbe59b05625b1d1b37e7
3
+ metadata.gz: 9af9eb4df4dd0323090363e65378d08d4b9a55050eb0a08ea6ddfe2cd7bd8b59
4
+ data.tar.gz: 32fb8b5f560d5e49978930f5e863d5851efc17b288f75c26e45ee7d1cf8e98dd
5
5
  SHA512:
6
- metadata.gz: 58e153ed45709e81d7b1d6d2b35523282d866a20f925e549ace5930daf29d1f2afafadcdd48b383bc7f27e662e401aa72cabc7cbd9e4a3d5bc3abad3f121f0d2
7
- data.tar.gz: 5715d4b57c35db06292f30ec6228a19d4e089e65558cf0e2c0c698ec930fc471541a1b92d9743344a173a6b7b67ba2c8593bbe198bca5eb58841112d8f5339ce
6
+ metadata.gz: 3286859be78192d3495c3dc58e15dad50273aed4d611ddf579b9b27a16b01bbf0652d17af367192ed6dd9154a693a1991342439d53bde14777a5e61e5402a036
7
+ data.tar.gz: e8d941d3a7b153345e407ed62fb4e6201b93a65e01810f06cacb4fa616bbbf3c93b1456be349f3f53fd8e41d5a7f4fc4fcd0fad9fc380f8c7ff4ea101678fc18
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## UNRELEASED
9
+
10
+ # 0.0.3 - 2025-8-15
11
+
12
+ * Initial release.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,106 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ proto_turf (0.0.2)
5
+ excon
6
+ google-protobuf
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.8.7)
12
+ public_suffix (>= 2.0.2, < 7.0)
13
+ ast (2.4.3)
14
+ bigdecimal (3.2.2)
15
+ crack (1.0.0)
16
+ bigdecimal
17
+ rexml
18
+ diff-lcs (1.6.1)
19
+ excon (1.2.9)
20
+ logger
21
+ google-protobuf (4.30.2-arm64-darwin)
22
+ bigdecimal
23
+ rake (>= 13)
24
+ hashdiff (1.2.0)
25
+ json (2.13.2)
26
+ language_server-protocol (3.17.0.5)
27
+ lint_roller (1.1.0)
28
+ logger (1.7.0)
29
+ parallel (1.27.0)
30
+ parser (3.3.9.0)
31
+ ast (~> 2.4.1)
32
+ racc
33
+ prism (1.4.0)
34
+ public_suffix (6.0.2)
35
+ racc (1.8.1)
36
+ rainbow (3.1.1)
37
+ rake (13.3.0)
38
+ regexp_parser (2.11.2)
39
+ rexml (3.4.1)
40
+ rspec (3.13.0)
41
+ rspec-core (~> 3.13.0)
42
+ rspec-expectations (~> 3.13.0)
43
+ rspec-mocks (~> 3.13.0)
44
+ rspec-core (3.13.3)
45
+ rspec-support (~> 3.13.0)
46
+ rspec-expectations (3.13.4)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.13.0)
49
+ rspec-mocks (3.13.3)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.13.0)
52
+ rspec-support (3.13.3)
53
+ rubocop (1.75.8)
54
+ json (~> 2.3)
55
+ language_server-protocol (~> 3.17.0.2)
56
+ lint_roller (~> 1.1.0)
57
+ parallel (~> 1.10)
58
+ parser (>= 3.3.0.2)
59
+ rainbow (>= 2.2.2, < 4.0)
60
+ regexp_parser (>= 2.9.3, < 3.0)
61
+ rubocop-ast (>= 1.44.0, < 2.0)
62
+ ruby-progressbar (~> 1.7)
63
+ unicode-display_width (>= 2.4.0, < 4.0)
64
+ rubocop-ast (1.46.0)
65
+ parser (>= 3.3.7.2)
66
+ prism (~> 1.4)
67
+ rubocop-performance (1.25.0)
68
+ lint_roller (~> 1.1)
69
+ rubocop (>= 1.75.0, < 2.0)
70
+ rubocop-ast (>= 1.38.0, < 2.0)
71
+ ruby-progressbar (1.13.0)
72
+ standard (1.50.0)
73
+ language_server-protocol (~> 3.17.0.2)
74
+ lint_roller (~> 1.0)
75
+ rubocop (~> 1.75.5)
76
+ standard-custom (~> 1.0.0)
77
+ standard-performance (~> 1.8)
78
+ standard-custom (1.0.2)
79
+ lint_roller (~> 1.0)
80
+ rubocop (~> 1.50)
81
+ standard-performance (1.8.0)
82
+ lint_roller (~> 1.1)
83
+ rubocop-performance (~> 1.25.0)
84
+ standardrb (1.0.1)
85
+ standard
86
+ unicode-display_width (3.1.5)
87
+ unicode-emoji (~> 4.0, >= 4.0.4)
88
+ unicode-emoji (4.0.4)
89
+ webmock (3.25.1)
90
+ addressable (>= 2.8.0)
91
+ crack (>= 0.3.2)
92
+ hashdiff (>= 0.4.0, < 2.0.0)
93
+
94
+ PLATFORMS
95
+ arm64-darwin
96
+
97
+ DEPENDENCIES
98
+ bundler (~> 2.0)
99
+ proto_turf!
100
+ rake (~> 13.0)
101
+ rspec (~> 3.2)
102
+ standardrb
103
+ webmock
104
+
105
+ BUNDLED WITH
106
+ 2.6.7
data/README.md CHANGED
@@ -27,7 +27,7 @@ Example usage:
27
27
  ```ruby
28
28
  require 'proto_turf'
29
29
 
30
- proto_turf = ProtoTurf.new(registry_url: 'http://localhost:8081')
30
+ proto_turf = ProtoTurf.new(registry_url: 'http://localhost:8081', schema_paths: ['path/to/protos'])
31
31
  message = MyProto::MyMessage.new(field1: 'value1', field2: 42)
32
32
  encoded = proto_turf.encode(message, subject: 'my-subject')
33
33
 
@@ -38,11 +38,15 @@ decoded_proto_message = proto_turf.decode(encoded_string)
38
38
 
39
39
  If you're using [buf](https://buf.build/) to manage your Protobuf definitions, you should run `buf export` before using `proto_turf` to ensure that all the dependencies are available as `.proto` files in your project. The actual proto text is needed when registering the schema with the Schema Registry.
40
40
 
41
+ Because `buf export` overwrites/deletes existing files, you should run it into a different directory and provide both as `schema_paths` to the `ProtoTurf` constructor.
42
+
41
43
  ## Notes about usage
42
44
 
43
- * For now, this library only supports a single message per `.proto` file that is registered with the Schema Registry. You can reference as many other files and messages as you like from that message, but keeping it to one message per file simplifies the workflow significantly.
44
45
  * When decoding, this library does *not* attempt to fully parse the Proto definition stored on the schema registry and generate dynamic classes. Instead, it simply parses out the package and message and assumes that the reader has the message available in the descriptor pool. Any compatibility issues should be detected through normal means, i.e. just by instantiating the message and seeing if any errors are raised.
45
46
 
46
- ## TODO
47
+ ### Regenerating test protos
48
+ Run the following to regenerate:
47
49
 
48
- * Support multi-message proto files
50
+ ```sh
51
+ protoc -I spec/schemas --ruby_out=spec/gen --ruby_opt=paths=source_relative spec/schemas/**/*.proto
52
+ ```
@@ -1,5 +1,4 @@
1
1
  class ProtoTurf::CachedConfluentSchemaRegistry
2
-
3
2
  # @param upstream [ProtoTurf::ConfluentSchemaRegistry]
4
3
  def initialize(upstream)
5
4
  @upstream = upstream
@@ -9,7 +8,7 @@ class ProtoTurf::CachedConfluentSchemaRegistry
9
8
  end
10
9
 
11
10
  # Delegate the following methods to the upstream
12
- %i(subject_versions schema_subject_versions).each do |name|
11
+ %i[subject_versions schema_subject_versions].each do |name|
13
12
  define_method(name) do |*args|
14
13
  instance_variable_get(:@upstream).send(name, *args)
15
14
  end
@@ -29,14 +28,14 @@ class ProtoTurf::CachedConfluentSchemaRegistry
29
28
  return @versions_by_subject_and_id[key] if @versions_by_subject_and_id[key]
30
29
 
31
30
  results = @upstream.schema_subject_versions(id)
32
- @versions_by_subject_and_id[key] = results&.find { |r| r['subject'] == subject }&.dig('version')
31
+ @versions_by_subject_and_id[key] = results&.find { |r| r["subject"] == subject }&.dig("version")
33
32
  end
34
33
 
35
34
  # @param subject [String] the subject to check
36
35
  # @param schema [String] the schema text to check
37
36
  # @return [Boolean] true if we know the schema has been registered for that subject.
38
37
  def registered?(subject, schema)
39
- @ids_by_schema[[subject, schema]].present?
38
+ @ids_by_schema[[subject, schema]] && !@ids_by_schema[[subject, schema]].empty?
40
39
  end
41
40
 
42
41
  # @param subject [String] the subject to register the schema under
@@ -47,5 +46,4 @@ class ProtoTurf::CachedConfluentSchemaRegistry
47
46
 
48
47
  @ids_by_schema[key] ||= @upstream.register(subject, schema, references: references)
49
48
  end
50
-
51
49
  end
@@ -1,8 +1,7 @@
1
- require 'excon'
1
+ require "excon"
2
2
 
3
3
  class ProtoTurf
4
4
  class ConfluentSchemaRegistry
5
-
6
5
  CONTENT_TYPE = "application/vnd.schemaregistry.v1+json".freeze
7
6
 
8
7
  def initialize(
@@ -24,7 +23,7 @@ class ProtoTurf
24
23
  retry_limit: nil
25
24
  )
26
25
  @path_prefix = path_prefix
27
- @schema_context_prefix = schema_context.nil? ? '' : ":.#{schema_context}:"
26
+ @schema_context_prefix = schema_context.nil? ? "" : ":.#{schema_context}:"
28
27
  @schema_context_options = schema_context.nil? ? {} : {query: {subject: @schema_context_prefix}}
29
28
  @logger = logger
30
29
  headers = Excon.defaults[:headers].merge(
@@ -57,7 +56,7 @@ class ProtoTurf
57
56
  # @return [String] the schema string stored in the registry for the given id
58
57
  def fetch(id)
59
58
  @logger.info "Fetching schema with id #{id}"
60
- data = get("/schemas/ids/#{id}", idempotent: true, **@schema_context_options, )
59
+ data = get("/schemas/ids/#{id}", idempotent: true, **@schema_context_options)
61
60
  data.fetch("schema")
62
61
  end
63
62
 
@@ -73,9 +72,9 @@ class ProtoTurf
73
72
  # @return [Integer] the ID of the registered schema
74
73
  def register(subject, schema, references: [])
75
74
  data = post("/subjects/#{@schema_context_prefix}#{CGI.escapeURIComponent(subject)}/versions",
76
- body: { schemaType: 'PROTOBUF',
77
- references: references,
78
- schema: schema.to_s }.to_json)
75
+ body: {schemaType: "PROTOBUF",
76
+ references: references,
77
+ schema: schema.to_s}.to_json)
79
78
 
80
79
  id = data.fetch("id")
81
80
 
@@ -105,7 +104,7 @@ class ProtoTurf
105
104
  end
106
105
 
107
106
  def request(path, **options)
108
- options = { expects: 200 }.merge!(options)
107
+ options = {expects: 200}.merge!(options)
109
108
  path = File.join(@path_prefix, path) unless @path_prefix.nil?
110
109
  response = @connection.request(path: path, **options)
111
110
  JSON.parse(response.body)
@@ -113,6 +112,5 @@ class ProtoTurf
113
112
  @logger.error("Error while requesting #{path}: #{e.response.body}")
114
113
  raise
115
114
  end
116
-
117
115
  end
118
116
  end
@@ -1,3 +1,3 @@
1
1
  class ProtoTurf
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/proto_turf.rb CHANGED
@@ -1,6 +1,10 @@
1
- require 'logger'
2
- require 'proto_turf/confluent_schema_registry'
3
- require 'proto_turf/cached_confluent_schema_registry'
1
+ require "logger"
2
+ require "google/protobuf"
3
+ require "google/protobuf/well_known_types"
4
+ require "google/protobuf/descriptor_pb"
5
+ require "json"
6
+ require "proto_turf/confluent_schema_registry"
7
+ require "proto_turf/cached_confluent_schema_registry"
4
8
 
5
9
  class ProtoTurf
6
10
  # Provides a way to encode and decode messages without having to embed schemas
@@ -15,14 +19,13 @@ class ProtoTurf
15
19
  # https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
16
20
  MAGIC_BYTE = [0].pack("C").freeze
17
21
 
18
-
19
22
  # Instantiate a new ProtoTurf instance with the given configuration.
20
23
  #
21
24
  # registry - A schema registry object that responds to all methods in the
22
25
  # ProtoTurf::ConfluentSchemaRegistry interface.
23
26
  # registry_url - The String URL of the schema registry that should be used.
24
27
  # schema_context - Schema registry context name (optional)
25
- # schemas_path - The String file system path where local schemas are stored.
28
+ # schema_paths - The String file system path where local schemas are stored.
26
29
  # registry_path_prefix - The String URL path prefix used to namespace schema registry requests (optional).
27
30
  # logger - The Logger that should be used to log information (optional).
28
31
  # proxy - Forward the request via proxy (optional).
@@ -40,7 +43,7 @@ class ProtoTurf
40
43
  registry: nil,
41
44
  registry_url: nil,
42
45
  schema_context: nil,
43
- schemas_path: nil,
46
+ schema_paths: nil,
44
47
  registry_path_prefix: nil,
45
48
  logger: nil,
46
49
  proxy: nil,
@@ -57,7 +60,7 @@ class ProtoTurf
57
60
  retry_limit: nil
58
61
  )
59
62
  @logger = logger || Logger.new($stderr)
60
- @path = schemas_path
63
+ @paths = Array(schema_paths)
61
64
  @registry = registry || ProtoTurf::CachedConfluentSchemaRegistry.new(
62
65
  ProtoTurf::ConfluentSchemaRegistry.new(
63
66
  registry_url,
@@ -90,7 +93,8 @@ class ProtoTurf
90
93
  def encode(message, subject: nil)
91
94
  load_schemas! if @all_schemas.empty?
92
95
 
93
- id = register_schema(message.class.descriptor.file_descriptor, subject: subject)
96
+ file_descriptor = message.class.descriptor.file_descriptor
97
+ id = register_schema(file_descriptor, subject: subject)
94
98
 
95
99
  stream = StringIO.new
96
100
  # Always start with the magic byte.
@@ -99,9 +103,15 @@ class ProtoTurf
99
103
  # The schema id is encoded as a 4-byte big-endian integer.
100
104
  stream.write([id].pack("N"))
101
105
 
102
- # For now, we're only going to support a single message per schema. See
103
- # https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
104
- write_int(stream, 0)
106
+ _, indexes = find_index(message.class.descriptor.to_proto,
107
+ file_descriptor.to_proto.message_type)
108
+
109
+ if indexes == [0]
110
+ write_int(stream, 0)
111
+ else
112
+ write_int(stream, indexes.length)
113
+ indexes.each { |i| write_int(stream, i) }
114
+ end
105
115
 
106
116
  # Now we write the actual message.
107
117
  stream.write(message.to_proto)
@@ -131,21 +141,53 @@ class ProtoTurf
131
141
  end
132
142
 
133
143
  # The schema id is a 4-byte big-endian integer.
134
- schema_id = stream.read(4).unpack("N").first
144
+ schema_id = stream.read(4).unpack1("N")
135
145
 
136
146
  # For now, we're only going to support a single message per schema. See
137
147
  # https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
138
- read_int(stream)
148
+ index_length = read_int(stream)
149
+ indexes = []
150
+ if index_length.zero?
151
+ indexes.push(0)
152
+ else
153
+ index_length.times do
154
+ indexes.push(read_int(stream))
155
+ end
156
+ end
139
157
 
140
158
  schema = @registry.fetch(schema_id)
141
159
  encoded = stream.read
142
- decode_protobuf(schema, encoded)
160
+ decode_protobuf(schema, encoded, indexes)
143
161
  rescue Excon::Error::NotFound
144
162
  raise SchemaNotFoundError.new("Schema with id: #{schema_id} is not found on registry")
145
163
  end
146
164
 
147
165
  private
148
166
 
167
+ def find_index(descriptor, messages, indexes = [])
168
+ messages.each_with_index do |sub_descriptor, i|
169
+ if sub_descriptor == descriptor
170
+ indexes.push(i)
171
+ return [true, indexes]
172
+ else
173
+ found, found_indexes = find_index(descriptor, sub_descriptor.nested_type, indexes + [i])
174
+ return [true, found_indexes] if found
175
+ end
176
+ end
177
+ []
178
+ end
179
+
180
+ def find_descriptor(indexes, messages)
181
+ first_index = indexes.shift
182
+ message = messages[first_index]
183
+ path = [message.name]
184
+ while indexes.length.positive?
185
+ message = message.nested_type[indexes.shift]
186
+ path.push(message.name)
187
+ end
188
+ path
189
+ end
190
+
149
191
  # Write an int with zig-zag encoding. Copied from Avro.
150
192
  def write_int(stream, n)
151
193
  n = (n << 1) ^ (n >> 63)
@@ -169,7 +211,7 @@ class ProtoTurf
169
211
  (n >> 1) ^ -(n & 1)
170
212
  end
171
213
 
172
- def decode_protobuf(schema, encoded)
214
+ def decode_protobuf(schema, encoded, indexes)
173
215
  # get the package
174
216
  package = schema.match(/package (\S+);/)[1]
175
217
  # get the first message in the protobuf text
@@ -181,7 +223,9 @@ class ProtoTurf
181
223
  unless descriptor
182
224
  raise "Could not find schema for #{full_name}. Make sure the corresponding .proto file has been compiled and loaded."
183
225
  end
184
- descriptor.msgclass.decode(encoded)
226
+ path = find_descriptor(indexes, descriptor.file_descriptor.to_proto.message_type)
227
+ correct_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("#{package}.#{path.join(".")}")
228
+ correct_message.msgclass.decode(encoded)
185
229
  end
186
230
 
187
231
  def register_schema(file_descriptor, subject: nil)
@@ -189,7 +233,7 @@ class ProtoTurf
189
233
  return if @registry.registered?(file_descriptor.name, subject)
190
234
 
191
235
  # register dependencies first
192
- dependencies = file_descriptor.to_proto.dependency.to_a.reject { |d| d.start_with?('google/protobuf/') }
236
+ dependencies = file_descriptor.to_proto.dependency.to_a.reject { |d| d.start_with?("google/protobuf/") }
193
237
  versions = dependencies.map do |dependency|
194
238
  dependency_descriptor = @all_schemas[dependency]
195
239
  result = register_schema(dependency_descriptor, subject: dependency_descriptor.name)
@@ -197,20 +241,22 @@ class ProtoTurf
197
241
  end
198
242
 
199
243
  @registry.register(subject,
200
- schema_text(file_descriptor),
201
- references: dependencies.map.with_index do |dependency, i|
202
- {
203
- name: dependency,
204
- subject: dependency,
205
- version: versions[i]
206
- }
207
- end
208
- )
244
+ schema_text(file_descriptor),
245
+ references: dependencies.map.with_index do |dependency, i|
246
+ {
247
+ name: dependency,
248
+ subject: dependency,
249
+ version: versions[i]
250
+ }
251
+ end)
209
252
  end
210
253
 
211
254
  def schema_text(file_descriptor)
212
- filename = "#{@path}/#{file_descriptor.name}"
213
- File.exist?(filename) ? File.read(filename) : ""
255
+ @paths.each do |path|
256
+ filename = "#{path}/#{file_descriptor.name}"
257
+ return File.read(filename) if File.exist?(filename)
258
+ end
259
+ ""
214
260
  end
215
261
 
216
262
  def load_schemas!
@@ -220,10 +266,9 @@ class ProtoTurf
220
266
  all_messages.each do |m|
221
267
  file_desc = m.descriptor.file_descriptor
222
268
  file_path = file_desc.name
223
- next if file_path.start_with?('google/protobuf/') # skip built-in protos
269
+ next if file_path.start_with?("google/protobuf/") # skip built-in protos
224
270
 
225
271
  @all_schemas[file_path] = file_desc
226
272
  end
227
273
  end
228
-
229
274
  end
data/proto_turf.gemspec CHANGED
@@ -1,27 +1,28 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("../lib", __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'proto_turf/version'
3
+ require "proto_turf/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "proto_turf"
8
- spec.version = ProtoTurf::VERSION
9
- spec.authors = ["Daniel Orner"]
10
- spec.email = ["daniel.orner@flipp.com"]
11
- spec.summary = "Support for Protobuf files in Confluent Schema Registry"
12
- spec.homepage = "https://github.com/flipp-oss/proto_turf"
13
- spec.license = "MIT"
6
+ spec.name = "proto_turf"
7
+ spec.version = ProtoTurf::VERSION
8
+ spec.authors = ["Daniel Orner"]
9
+ spec.email = ["daniel.orner@flipp.com"]
10
+ spec.summary = "Support for Protobuf files in Confluent Schema Registry"
11
+ spec.homepage = "https://github.com/flipp-oss/proto_turf"
12
+ spec.license = "MIT"
14
13
 
15
14
  spec.metadata["rubygems_mfa_required"] = "true"
16
15
 
17
- spec.files = `git ls-files -z`.split("\x0")
18
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
18
  spec.require_paths = ["lib"]
21
19
 
22
20
  spec.add_dependency "google-protobuf"
21
+ spec.add_dependency "excon"
23
22
 
24
23
  spec.add_development_dependency "bundler", "~> 2.0"
25
24
  spec.add_development_dependency "rake", "~> 13.0"
26
25
  spec.add_development_dependency "rspec", "~> 3.2"
26
+ spec.add_development_dependency "webmock"
27
+ spec.add_development_dependency "standardrb"
27
28
  end
@@ -0,0 +1,37 @@
1
+ RSpec.describe "encoding" do
2
+ let(:proto_turf) do
3
+ ProtoTurf.new(
4
+ registry_url: "http://localhost:8081",
5
+ schema_paths: ["spec/schemas"]
6
+ )
7
+ end
8
+
9
+ it "should decode a simple message" do
10
+ schema = File.read("#{__dir__}/schemas/simple/simple.proto")
11
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/15")
12
+ .to_return_json(body: {schema: schema})
13
+ msg = Simple::V1::SimpleMessage.new(name: "my name")
14
+ encoded = "\u0000\u0000\u0000\u0000\u000F\u0000" + msg.to_proto
15
+ expect(proto_turf.decode(encoded)).to eq(msg)
16
+
17
+ # if we do it again we should not see any more requests
18
+ expect(proto_turf.decode(encoded)).to eq(msg)
19
+
20
+ expect(stub).to have_been_requested.once
21
+ end
22
+
23
+ it "should decode a complex message" do
24
+ schema = File.read("#{__dir__}/schemas/referenced/referer.proto")
25
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/20")
26
+ .to_return_json(body: {schema: schema})
27
+ msg = Referenced::V1::MessageB::MessageBA.new(
28
+ simple: Simple::V1::SimpleMessage.new(name: "my name")
29
+ )
30
+ encoded = "\u0000\u0000\u0000\u0000\u0014\u0004\u0002\u0000" + msg.to_proto
31
+ expect(proto_turf.decode(encoded)).to eq(msg)
32
+
33
+ # if we do it again we should not see any more requests
34
+ expect(proto_turf.decode(encoded)).to eq(msg)
35
+ expect(stub).to have_been_requested.once
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ RSpec.describe "encoding" do
2
+ let(:proto_turf) do
3
+ ProtoTurf.new(
4
+ registry_url: "http://localhost:8081",
5
+ schema_paths: ["spec/schemas"]
6
+ )
7
+ end
8
+
9
+ it "should encode a simple message" do
10
+ schema = File.read("#{__dir__}/schemas/simple/simple.proto")
11
+ stub = stub_request(:post, "http://localhost:8081/subjects/simple/versions")
12
+ .with(body: {"schemaType" => "PROTOBUF",
13
+ "references" => [],
14
+ "schema" => schema}).to_return_json(body: {id: 15})
15
+ msg = Simple::V1::SimpleMessage.new(name: "my name")
16
+ encoded = proto_turf.encode(msg, subject: "simple")
17
+ expect(encoded).to eq("\u0000\u0000\u0000\u0000\u000F\u0000" + msg.to_proto)
18
+
19
+ # if we do it again we should not see any more requests
20
+ encoded2 = proto_turf.encode(msg, subject: "simple")
21
+ expect(encoded2).to eq(encoded)
22
+
23
+ expect(stub).to have_been_requested.once
24
+ end
25
+
26
+ it "should encode a complex message" do
27
+ schema = File.read("#{__dir__}/schemas/referenced/referer.proto")
28
+ dep_schema = File.read("#{__dir__}/schemas/simple/simple.proto")
29
+ dep_stub = stub_request(:post, "http://localhost:8081/subjects/simple%2Fsimple.proto/versions")
30
+ .with(body: {"schemaType" => "PROTOBUF",
31
+ "references" => [],
32
+ "schema" => dep_schema}).to_return_json(body: {id: 15})
33
+ version_stub = stub_request(:get, "http://localhost:8081/schemas/ids/15/versions")
34
+ .to_return_json(body: [{version: 1, subject: "simple/simple.proto"}])
35
+ stub = stub_request(:post, "http://localhost:8081/subjects/referenced/versions")
36
+ .with(body: {"schemaType" => "PROTOBUF",
37
+ "references" => [
38
+ {
39
+ name: "simple/simple.proto",
40
+ subject: "simple/simple.proto",
41
+ version: 1
42
+ }
43
+ ],
44
+ "schema" => schema}).to_return_json(body: {id: 20})
45
+ msg = Referenced::V1::MessageB::MessageBA.new(
46
+ simple: Simple::V1::SimpleMessage.new(name: "my name")
47
+ )
48
+ encoded = proto_turf.encode(msg, subject: "referenced")
49
+ expect(encoded).to eq("\u0000\u0000\u0000\u0000\u0014\u0004\u0002\u0000" + msg.to_proto)
50
+
51
+ # if we do it again we should not see any more requests
52
+ encoded2 = proto_turf.encode(msg, subject: "referenced")
53
+ expect(encoded2).to eq(encoded)
54
+ expect(stub).to have_been_requested.once
55
+ expect(dep_stub).to have_been_requested.once
56
+ expect(version_stub).to have_been_requested.once
57
+ end
58
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
4
+ # source: referenced/referer.proto
5
+
6
+ require "google/protobuf"
7
+
8
+ require "simple/simple_pb"
9
+
10
+ descriptor_data = "\n\x18referenced/referer.proto\x12\rreferenced.v1\x1a\x13simple/simple.proto\"j\n\x08MessageA\x1a\x43\n\tMessageAA\x12\x0c\n\x04name\x18\x01 \x01(\t\x12(\n\x06simple\x18\x02 \x01(\x0b\x32\x18.simple.v1.SimpleMessage\x1a\x19\n\tMessageAB\x12\x0c\n\x04name\x18\x01 \x01(\t\"j\n\x08MessageB\x1a\x43\n\tMessageBA\x12\x0c\n\x04name\x18\x01 \x01(\t\x12(\n\x06simple\x18\x02 \x01(\x0b\x32\x18.simple.v1.SimpleMessage\x1a\x19\n\tMessageBB\x12\x0c\n\x04name\x18\x01 \x01(\tb\x06proto3"
11
+
12
+ pool = Google::Protobuf::DescriptorPool.generated_pool
13
+ pool.add_serialized_file(descriptor_data)
14
+
15
+ module Referenced
16
+ module V1
17
+ MessageA = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("referenced.v1.MessageA").msgclass
18
+ MessageA::MessageAA = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("referenced.v1.MessageA.MessageAA").msgclass
19
+ MessageA::MessageAB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("referenced.v1.MessageA.MessageAB").msgclass
20
+ MessageB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("referenced.v1.MessageB").msgclass
21
+ MessageB::MessageBA = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("referenced.v1.MessageB.MessageBA").msgclass
22
+ MessageB::MessageBB = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("referenced.v1.MessageB.MessageBB").msgclass
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
4
+ # source: simple/simple.proto
5
+
6
+ require "google/protobuf"
7
+
8
+ descriptor_data = "\n\x13simple/simple.proto\x12\tsimple.v1\"\x1d\n\rSimpleMessage\x12\x0c\n\x04name\x18\x01 \x01(\tb\x06proto3"
9
+
10
+ pool = Google::Protobuf::DescriptorPool.generated_pool
11
+ pool.add_serialized_file(descriptor_data)
12
+
13
+ module Simple
14
+ module V1
15
+ SimpleMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("simple.v1.SimpleMessage").msgclass
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ syntax = "proto3";
2
+
3
+ package referenced.v1;
4
+ import "simple/simple.proto";
5
+
6
+ message MessageA {
7
+ message MessageAA {
8
+ string name = 1;
9
+ simple.v1.SimpleMessage simple = 2;
10
+ }
11
+ message MessageAB {
12
+ string name = 1;
13
+ }
14
+ }
15
+
16
+ message MessageB {
17
+ message MessageBA {
18
+ string name = 1;
19
+ simple.v1.SimpleMessage simple = 2;
20
+ }
21
+ message MessageBB {
22
+ string name = 1;
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ syntax = "proto3";
2
+
3
+ package simple.v1;
4
+
5
+ message SimpleMessage {
6
+ string name = 1;
7
+ }
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
4
+ $LOAD_PATH.unshift(File.expand_path("gen", __dir__))
5
+ Dir["#{__dir__}/gen/**/*.rb"].each { |file| require file }
6
+ require "proto_turf"
7
+ require "webmock/rspec"
8
+
9
+ RSpec.configure do |config|
10
+ config.full_backtrace = true
11
+
12
+ config.shared_context_metadata_behavior = :apply_to_host_groups
13
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: proto_turf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-14 00:00:00.000000000 Z
11
+ date: 2025-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: excon
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +80,34 @@ dependencies:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
82
  version: '3.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: standardrb
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
69
111
  description:
70
112
  email:
71
113
  - daniel.orner@flipp.com
@@ -73,6 +115,10 @@ executables: []
73
115
  extensions: []
74
116
  extra_rdoc_files: []
75
117
  files:
118
+ - ".rspec"
119
+ - CHANGELOG.md
120
+ - Gemfile
121
+ - Gemfile.lock
76
122
  - LICENSE
77
123
  - README.md
78
124
  - lib/proto_turf.rb
@@ -80,6 +126,13 @@ files:
80
126
  - lib/proto_turf/confluent_schema_registry.rb
81
127
  - lib/proto_turf/version.rb
82
128
  - proto_turf.gemspec
129
+ - spec/decoding_spec.rb
130
+ - spec/encoding_spec.rb
131
+ - spec/gen/referenced/referer_pb.rb
132
+ - spec/gen/simple/simple_pb.rb
133
+ - spec/schemas/referenced/referer.proto
134
+ - spec/schemas/simple/simple.proto
135
+ - spec/spec_helper.rb
83
136
  homepage: https://github.com/flipp-oss/proto_turf
84
137
  licenses:
85
138
  - MIT