schema_registry_client 0.0.8 → 0.0.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.
@@ -1,31 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'google/protobuf'
4
- require 'google/protobuf/well_known_types'
5
- require 'google/protobuf/descriptor_pb'
6
- require 'schema_registry_client/output/proto_text'
7
- require 'schema_registry_client/schema/base'
8
- require 'schema_registry_client/wire'
3
+ require "google/protobuf"
4
+ require "google/protobuf/well_known_types"
5
+ require "google/protobuf/descriptor_pb"
6
+ require "schema_registry_client/output/proto_text"
7
+ require "schema_registry_client/schema/base"
8
+ require "schema_registry_client/wire"
9
9
 
10
10
  module SchemaRegistry
11
11
  module Schema
12
12
  class Protobuf < Base
13
13
  def self.schema_type
14
- 'PROTOBUF'
14
+ "PROTOBUF"
15
15
  end
16
16
 
17
17
  def schema_text(message, schema_name: nil)
18
18
  file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
19
- message
20
- else
21
- message.class.descriptor.file_descriptor
22
- end
19
+ message
20
+ else
21
+ message.class.descriptor.file_descriptor
22
+ end
23
23
  SchemaRegistry::Output::ProtoText.output(file_descriptor.to_proto)
24
24
  end
25
25
 
26
26
  def encode(message, stream, schema_name: nil)
27
27
  _, indexes = find_index(message.class.descriptor.to_proto,
28
- message.class.descriptor.file_descriptor.to_proto.message_type)
28
+ message.class.descriptor.file_descriptor.to_proto.message_type)
29
29
 
30
30
  if indexes == [0]
31
31
  SchemaRegistry::Wire.write_int(stream, 0)
@@ -59,7 +59,7 @@ module SchemaRegistry
59
59
  all_files = ObjectSpace.each_object(Google::Protobuf::FileDescriptor).to_a
60
60
  all_files.each do |file_desc|
61
61
  file_path = file_desc.name
62
- next if file_path.start_with?('google/protobuf/') # skip built-in protos
62
+ next if file_path.start_with?("google/protobuf/") # skip built-in protos
63
63
 
64
64
  @all_schemas[file_path] = file_desc
65
65
  end
@@ -70,13 +70,13 @@ module SchemaRegistry
70
70
 
71
71
  load_schemas! unless @all_schemas&.any?
72
72
  file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
73
- message
74
- else
75
- message.class.descriptor.file_descriptor
76
- end
73
+ message
74
+ else
75
+ message.class.descriptor.file_descriptor
76
+ end
77
77
 
78
78
  deps = file_descriptor.to_proto.dependency.to_a
79
- .reject { |d| d.start_with?('google/protobuf/') }
79
+ .reject { |d| d.start_with?("google/protobuf/") }
80
80
  deps.to_h do |dep|
81
81
  dependency_schema = @all_schemas[dep]
82
82
  [dependency_schema.name, dependency_schema]
@@ -118,12 +118,12 @@ module SchemaRegistry
118
118
  descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(full_name)
119
119
  unless descriptor
120
120
  msg = "Could not find schema for #{full_name}. " \
121
- 'Make sure the corresponding .proto file has been compiled and loaded.'
121
+ "Make sure the corresponding .proto file has been compiled and loaded."
122
122
  raise msg
123
123
  end
124
124
 
125
125
  path = find_descriptor(indexes, descriptor.file_descriptor.to_proto.message_type)
126
- correct_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("#{package}.#{path.join('.')}")
126
+ correct_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("#{package}.#{path.join(".")}")
127
127
  correct_message.msgclass.decode(encoded)
128
128
  end
129
129
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchemaRegistry
4
- VERSION = '0.0.8'
4
+ VERSION = "0.0.10"
5
5
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
4
- require 'json'
5
- require 'schema_registry_client/confluent_schema_registry'
6
- require 'schema_registry_client/cached_confluent_schema_registry'
7
- require 'schema_registry_client/schema/protobuf'
8
- require 'schema_registry_client/schema/proto_json_schema'
9
- require 'schema_registry_client/schema/avro'
3
+ require "logger"
4
+ require "json"
5
+ require "schema_registry_client/confluent_schema_registry"
6
+ require "schema_registry_client/cached_confluent_schema_registry"
7
+ require "schema_registry_client/schema/protobuf"
8
+ require "schema_registry_client/schema/proto_json_schema"
9
+ require "schema_registry_client/schema/avro"
10
10
 
11
11
  module SchemaRegistry
12
12
  class SchemaNotFoundError < StandardError; end
@@ -27,7 +27,7 @@ module SchemaRegistry
27
27
  # 1: https://github.com/confluentinc/schema-registry
28
28
  # https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-protobuf.html
29
29
  # https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
30
- MAGIC_BYTE = [0].pack('C').freeze
30
+ MAGIC_BYTE = [0].pack("C").freeze
31
31
 
32
32
  # Instantiate a new SchemaRegistry instance with the given configuration.
33
33
  #
@@ -104,7 +104,7 @@ module SchemaRegistry
104
104
  stream.write(MAGIC_BYTE)
105
105
 
106
106
  # The schema id is encoded as a 4-byte big-endian integer.
107
- stream.write([id].pack('N'))
107
+ stream.write([id].pack("N"))
108
108
 
109
109
  @schema.encode(message, stream, schema_name: schema_name)
110
110
  stream.string
@@ -123,7 +123,7 @@ module SchemaRegistry
123
123
  raise "Expected data to begin with a magic byte, got `#{magic_byte.inspect}`" if magic_byte != MAGIC_BYTE
124
124
 
125
125
  # The schema id is a 4-byte big-endian integer.
126
- schema_id = stream.read(4).unpack1('N')
126
+ schema_id = stream.read(4).unpack1("N")
127
127
  schema = @registry.fetch(schema_id)
128
128
  @schema.decode(stream, schema)
129
129
  rescue Excon::Error::NotFound
@@ -144,15 +144,15 @@ module SchemaRegistry
144
144
  end
145
145
 
146
146
  @registry.register(subject,
147
- schema_text,
148
- references: dependencies.keys.map.with_index do |dependency, i|
149
- {
150
- name: dependency,
151
- subject: dependency,
152
- version: versions[i]
153
- }
154
- end,
155
- schema_type: @schema.class.schema_type)
147
+ schema_text,
148
+ references: dependencies.keys.map.with_index do |dependency, i|
149
+ {
150
+ name: dependency,
151
+ subject: dependency,
152
+ version: versions[i]
153
+ }
154
+ end,
155
+ schema_type: @schema.class.schema_type)
156
156
  end
157
157
  end
158
158
  end
@@ -1,33 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('lib', __dir__)
3
+ lib = File.expand_path("lib", __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'schema_registry_client/version'
5
+ require "schema_registry_client/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = 'schema_registry_client'
8
+ spec.name = "schema_registry_client"
9
9
  spec.version = SchemaRegistry::VERSION
10
- spec.authors = ['Daniel Orner']
11
- spec.email = ['daniel.orner@flipp.com']
12
- spec.summary = 'Confluent Schema Registry client with support for Avro and Protobuf'
13
- spec.homepage = 'https://github.com/flipp-oss/schema_registry_client'
14
- spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 3.0'
10
+ spec.authors = ["Daniel Orner"]
11
+ spec.email = ["daniel.orner@flipp.com"]
12
+ spec.summary = "Confluent Schema Registry client with support for Avro and Protobuf"
13
+ spec.homepage = "https://github.com/flipp-oss/schema_registry_client"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0"
16
16
 
17
- spec.metadata['rubygems_mfa_required'] = 'true'
17
+ spec.metadata["rubygems_mfa_required"] = "true"
18
18
 
19
19
  spec.files = `git ls-files -z`.split("\x0")
20
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
- spec.require_paths = ['lib']
21
+ spec.require_paths = ["lib"]
22
22
 
23
- spec.add_dependency 'avro'
24
- spec.add_dependency 'excon'
25
- spec.add_dependency 'google-protobuf'
23
+ spec.add_dependency "avro"
24
+ spec.add_dependency "excon"
25
+ spec.add_dependency "google-protobuf"
26
26
 
27
- spec.add_development_dependency 'bundler', '~> 2.0'
28
- spec.add_development_dependency 'rake', '~> 13.0'
29
- spec.add_development_dependency 'rspec', '~> 3.2'
30
- spec.add_development_dependency 'simplecov'
31
- spec.add_development_dependency 'standardrb'
32
- spec.add_development_dependency 'webmock'
27
+ spec.add_development_dependency "bundler", "~> 2.0"
28
+ spec.add_development_dependency "rake", "~> 13.0"
29
+ spec.add_development_dependency "rspec", "~> 3.2"
30
+ spec.add_development_dependency "simplecov"
31
+ spec.add_development_dependency "standardrb"
32
+ spec.add_development_dependency "webmock"
33
33
  end
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- RSpec.describe 'decoding' do
3
+ RSpec.describe "decoding" do
4
4
  let(:schema_registry_client) do
5
5
  SchemaRegistry::Client.new(
6
- registry_url: 'http://localhost:8081'
6
+ registry_url: "http://localhost:8081"
7
7
  )
8
8
  end
9
9
 
10
- it 'should decode a simple message' do
10
+ it "should decode a simple message" do
11
11
  schema = File.read("#{__dir__}/schemas/simple/simple.proto")
12
- stub = stub_request(:get, 'http://localhost:8081/schemas/ids/15')
13
- .to_return_json(body: { schema: schema })
14
- msg = Simple::V1::SimpleMessage.new(name: 'my name')
12
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/15")
13
+ .to_return_json(body: {schema: schema})
14
+ msg = Simple::V1::SimpleMessage.new(name: "my name")
15
15
  encoded = "\u0000\u0000\u0000\u0000\u000F\u0000#{msg.to_proto}"
16
16
  expect(schema_registry_client.decode(encoded)).to eq(msg)
17
17
 
@@ -21,12 +21,12 @@ RSpec.describe 'decoding' do
21
21
  expect(stub).to have_been_requested.once
22
22
  end
23
23
 
24
- it 'should decode a complex message' do
24
+ it "should decode a complex message" do
25
25
  schema = File.read("#{__dir__}/schemas/referenced/referer.proto")
26
- stub = stub_request(:get, 'http://localhost:8081/schemas/ids/20')
27
- .to_return_json(body: { schema: schema })
26
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/20")
27
+ .to_return_json(body: {schema: schema})
28
28
  msg = Referenced::V1::MessageB::MessageBA.new(
29
- simple: Simple::V1::SimpleMessage.new(name: 'my name')
29
+ simple: Simple::V1::SimpleMessage.new(name: "my name")
30
30
  )
31
31
  encoded = "\u0000\u0000\u0000\u0000\u0014\u0004\u0002\u0000#{msg.to_proto}"
32
32
  expect(schema_registry_client.decode(encoded)).to eq(msg)
@@ -36,20 +36,20 @@ RSpec.describe 'decoding' do
36
36
  expect(stub).to have_been_requested.once
37
37
  end
38
38
 
39
- describe 'with JSON' do
39
+ describe "with JSON" do
40
40
  let(:schema_registry_client) do
41
41
  SchemaRegistry::Client.new(
42
- registry_url: 'http://localhost:8081',
42
+ registry_url: "http://localhost:8081",
43
43
  schema_type: SchemaRegistry::Schema::ProtoJsonSchema.new
44
44
  )
45
45
  end
46
46
 
47
- it 'should decode a simple message' do
47
+ it "should decode a simple message" do
48
48
  schema = File.read("#{__dir__}/schemas/simple/simple.json")
49
- stub = stub_request(:get, 'http://localhost:8081/schemas/ids/15')
50
- .to_return_json(body: { schema: schema })
49
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/15")
50
+ .to_return_json(body: {schema: schema})
51
51
  encoded = "\u0000\u0000\u0000\u0000\u000F{\"name\":\"my name\"}"
52
- msg = { 'name' => 'my name' }
52
+ msg = {"name" => "my name"}
53
53
  expect(schema_registry_client.decode(encoded)).to eq(msg)
54
54
 
55
55
  # if we do it again we should not see any more requests
@@ -59,11 +59,11 @@ RSpec.describe 'decoding' do
59
59
  end
60
60
  end
61
61
 
62
- describe 'with Avro' do
62
+ describe "with Avro" do
63
63
  let(:schema_registry_client) do
64
64
  SchemaRegistry.avro_schema_path = "#{__dir__}/schemas"
65
65
  SchemaRegistry::Client.new(
66
- registry_url: 'http://localhost:8081',
66
+ registry_url: "http://localhost:8081",
67
67
  schema_type: SchemaRegistry::Schema::Avro.new
68
68
  )
69
69
  end
@@ -72,16 +72,16 @@ RSpec.describe 'decoding' do
72
72
  SchemaRegistry.avro_schema_path = nil
73
73
  end
74
74
 
75
- it 'should decode a simple message' do
75
+ it "should decode a simple message" do
76
76
  schema = File.read("#{__dir__}/schemas/simple/v1/SimpleMessage.avsc")
77
- stub = stub_request(:get, 'http://localhost:8081/schemas/ids/15')
78
- .to_return_json(body: { schema: schema })
77
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/15")
78
+ .to_return_json(body: {schema: schema})
79
79
 
80
80
  # Avro-encoded data: "my name" as string (length 0x0E + bytes)
81
81
  encoded = "\u0000\u0000\u0000\u0000\u000F\u000Emy name"
82
82
  decoded = schema_registry_client.decode(encoded)
83
83
 
84
- expect(decoded).to eq({ 'name' => 'my name' })
84
+ expect(decoded).to eq({"name" => "my name"})
85
85
 
86
86
  # if we do it again we should not see any more requests
87
87
  expect(schema_registry_client.decode(encoded)).to eq(decoded)
@@ -89,40 +89,40 @@ RSpec.describe 'decoding' do
89
89
  expect(stub).to have_been_requested.once
90
90
  end
91
91
 
92
- it 'should decode a complex message with nested record' do
92
+ it "should decode a complex message with nested record" do
93
93
  schema = File.read("#{__dir__}/schemas/referenced/v1/MessageBA.avsc")
94
- stub = stub_request(:get, 'http://localhost:8081/schemas/ids/20')
95
- .to_return_json(body: { schema: schema })
94
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/20")
95
+ .to_return_json(body: {schema: schema})
96
96
 
97
97
  # Avro-encoded nested record
98
98
  encoded = "\u0000\u0000\u0000\u0000\u0014\u000Emy name"
99
99
  decoded = schema_registry_client.decode(encoded)
100
100
 
101
101
  expect(decoded).to eq({
102
- 'simple' => {
103
- 'name' => 'my name'
104
- }
105
- })
102
+ "simple" => {
103
+ "name" => "my name"
104
+ }
105
+ })
106
106
 
107
107
  # if we do it again we should not see any more requests
108
108
  expect(schema_registry_client.decode(encoded)).to eq(decoded)
109
109
  expect(stub).to have_been_requested.once
110
110
  end
111
111
 
112
- it 'should decode a message with multiple fields' do
112
+ it "should decode a message with multiple fields" do
113
113
  multi_schema = {
114
- 'type' => 'record',
115
- 'name' => 'MultiFieldMessage',
116
- 'namespace' => 'test.v1',
117
- 'fields' => [
118
- { 'name' => 'name', 'type' => 'string' },
119
- { 'name' => 'age', 'type' => 'int' }
114
+ "type" => "record",
115
+ "name" => "MultiFieldMessage",
116
+ "namespace" => "test.v1",
117
+ "fields" => [
118
+ {"name" => "name", "type" => "string"},
119
+ {"name" => "age", "type" => "int"}
120
120
  ]
121
121
  }
122
122
  schema_json = JSON.generate(multi_schema)
123
123
 
124
- stub = stub_request(:get, 'http://localhost:8081/schemas/ids/25')
125
- .to_return_json(body: { schema: schema_json })
124
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/25")
125
+ .to_return_json(body: {schema: schema_json})
126
126
 
127
127
  # Manually encode the message for testing
128
128
  # Alice = 0x0A (length 5*2) + "Alice" bytes
@@ -130,39 +130,39 @@ RSpec.describe 'decoding' do
130
130
  encoded = "\u0000\u0000\u0000\u0000\u0019\u000AAlice\u003C"
131
131
  decoded = schema_registry_client.decode(encoded)
132
132
 
133
- expect(decoded).to eq({ 'name' => 'Alice', 'age' => 30 })
133
+ expect(decoded).to eq({"name" => "Alice", "age" => 30})
134
134
 
135
135
  expect(stub).to have_been_requested.once
136
136
  end
137
137
 
138
- it 'should handle schema evolution with reader schema' do
138
+ it "should handle schema evolution with reader schema" do
139
139
  # Writer schema (what was used to encode) - has an additional field with default
140
140
  writer_schema = {
141
- 'type' => 'record',
142
- 'name' => 'SimpleMessage',
143
- 'namespace' => 'simple.v1',
144
- 'fields' => [
145
- { 'name' => 'name', 'type' => 'string' },
146
- { 'name' => 'age', 'type' => 'int', 'default' => 0 }
141
+ "type" => "record",
142
+ "name" => "SimpleMessage",
143
+ "namespace" => "simple.v1",
144
+ "fields" => [
145
+ {"name" => "name", "type" => "string"},
146
+ {"name" => "age", "type" => "int", "default" => 0}
147
147
  ]
148
148
  }
149
149
 
150
150
  # Reader schema (what we have locally) - doesn't have the age field
151
151
  # This simulates reading old data with a newer schema or vice versa
152
152
 
153
- stub = stub_request(:get, 'http://localhost:8081/schemas/ids/15')
154
- .to_return_json(body: { schema: JSON.generate(writer_schema) })
153
+ stub = stub_request(:get, "http://localhost:8081/schemas/ids/15")
154
+ .to_return_json(body: {schema: JSON.generate(writer_schema)})
155
155
 
156
156
  # Encoded with writer schema: "my name" (0x0E + bytes) + age 25 (zigzag encoded as 50 = 0x32)
157
157
  encoded = "\u0000\u0000\u0000\u0000\u000F\u000Emy name\u0032"
158
158
  decoded = schema_registry_client.decode(encoded)
159
159
 
160
160
  # Decoded value should only have 'name' from reader schema, age is ignored
161
- expect(decoded).to eq({ 'name' => 'my name' })
161
+ expect(decoded).to eq({"name" => "my name"})
162
162
  expect(stub).to have_been_requested.once
163
163
  end
164
164
 
165
- it 'should raise error for invalid magic byte' do
165
+ it "should raise error for invalid magic byte" do
166
166
  # Wrong magic byte (0x01 instead of 0x00)
167
167
  encoded = "\u0001\u0000\u0000\u0000\u000F\u000Emy name"
168
168
 
@@ -171,7 +171,7 @@ RSpec.describe 'decoding' do
171
171
  end.to raise_error(/Expected data to begin with a magic byte/)
172
172
  end
173
173
 
174
- it 'should raise error for unknown schema id' do
174
+ it "should raise error for unknown schema id" do
175
175
  # Schema ID 999 is not stubbed, so decoding should fail
176
176
  encoded = "\u0000\u0000\u0000\u0003\u00E7\u000Emy name"
177
177