schema_registry_client 0.0.7 → 0.0.8

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,60 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "schema_registry_client/schema/base"
4
- require "schema_registry_client/avro_schema_store"
3
+ require 'schema_registry_client/schema/base'
4
+ require 'schema_registry_client/avro_schema_store'
5
5
 
6
6
  module SchemaRegistry
7
7
  module Schema
8
8
  class Avro < Base
9
- DEFAULT_SCHEMAS_PATH = "./schemas"
9
+ DEFAULT_SCHEMAS_PATH = './schemas'
10
10
 
11
- class << self
12
- def schema_type
13
- "AVRO"
14
- end
11
+ def self.schema_type
12
+ 'AVRO'
13
+ end
15
14
 
16
- def schema_store
17
- @schema_store ||= SchemaRegistry::AvroSchemaStore.new(
18
- path: SchemaRegistry.avro_schema_path || DEFAULT_SCHEMAS_PATH
19
- )
20
- @schema_store.load_schemas!
21
- @schema_store
22
- end
15
+ # @param schema_store [SchemaRegistry::AvroSchemaStore, nil]
16
+ def initialize(schema_store: nil)
17
+ @schema_store = schema_store
18
+ end
23
19
 
24
- def schema_text(_message, schema_name: nil)
25
- schema_store.find_text(schema_name)
26
- end
20
+ # @return [SchemaRegistry::AvroSchemaStore]
21
+ def schema_store
22
+ @schema_store ||= SchemaRegistry::AvroSchemaStore.new(
23
+ path: SchemaRegistry.avro_schema_path || DEFAULT_SCHEMAS_PATH
24
+ )
25
+ @schema_store.load_schemas!
26
+ @schema_store
27
+ end
27
28
 
28
- def encode(message, stream, schema_name: nil)
29
- validate_options = {recursive: true,
30
- encoded: false,
31
- fail_on_extra_fields: true}
32
- schema = schema_store.find(schema_name)
29
+ def schema_text(_message, schema_name: nil)
30
+ schema_store.find_text(schema_name)
31
+ end
33
32
 
34
- ::Avro::SchemaValidator.validate!(schema, message, **validate_options)
33
+ def encode(message, stream, schema_name: nil)
34
+ validate_options = { recursive: true,
35
+ encoded: false,
36
+ fail_on_extra_fields: true }
37
+ schema = schema_store.find(schema_name)
35
38
 
36
- writer = ::Avro::IO::DatumWriter.new(schema)
37
- encoder = ::Avro::IO::BinaryEncoder.new(stream)
38
- writer.write(message, encoder)
39
- end
39
+ ::Avro::SchemaValidator.validate!(schema, message, **validate_options)
40
40
 
41
- def decode(stream, schema_text)
42
- # Parse the schema text from the registry into an Avro schema object
43
- JSON.parse(schema_text)
44
- writers_schema = ::Avro::Schema.parse(schema_text)
41
+ writer = ::Avro::IO::DatumWriter.new(schema)
42
+ encoder = ::Avro::IO::BinaryEncoder.new(stream)
43
+ writer.write(message, encoder)
44
+ end
45
45
 
46
- decoder = ::Avro::IO::BinaryDecoder.new(stream)
46
+ def decode(stream, schema_text)
47
+ # Parse the schema text from the registry into an Avro schema object
48
+ JSON.parse(schema_text)
49
+ writers_schema = ::Avro::Schema.parse(schema_text)
47
50
 
48
- # Try to find the reader schema locally, fall back to writer schema
49
- readers_schema = begin
50
- schema_store.find(writers_schema.fullname)
51
- rescue
52
- writers_schema
53
- end
51
+ decoder = ::Avro::IO::BinaryDecoder.new(stream)
54
52
 
55
- reader = ::Avro::IO::DatumReader.new(writers_schema, readers_schema)
56
- reader.read(decoder)
53
+ # Try to find the reader schema locally, fall back to writer schema
54
+ readers_schema = begin
55
+ schema_store.find(writers_schema.fullname)
56
+ rescue StandardError
57
+ writers_schema
57
58
  end
59
+
60
+ reader = ::Avro::IO::DatumReader.new(writers_schema, readers_schema)
61
+ reader.read(decoder)
58
62
  end
59
63
  end
60
64
  end
@@ -5,39 +5,37 @@ module SchemaRegistry
5
5
  class MissingImplementationError < StandardError; end
6
6
 
7
7
  class Base
8
- class << self
9
- # @param message [Object]
10
- # @param schema_name [String]
11
- # @return [String]
12
- def schema_text(_message, schema_name: nil)
13
- raise MissingImplementationError, "Subclasses must implement schema_text"
14
- end
8
+ # @param message [Object]
9
+ # @param schema_name [String]
10
+ # @return [String]
11
+ def schema_text(_message, schema_name: nil)
12
+ raise MissingImplementationError, 'Subclasses must implement schema_text'
13
+ end
15
14
 
16
- # @return [String]
17
- def schema_type
18
- raise MissingImplementationError, "Subclasses must implement schema_type"
19
- end
15
+ # @return [String]
16
+ def self.schema_type
17
+ raise MissingImplementationError, 'Subclasses must implement schema_type'
18
+ end
20
19
 
21
- # @param message [Object]
22
- # @param stream [StringIO]
23
- # @param schema_name [String]
24
- def encode(_message, _stream, schema_name: nil)
25
- raise MissingImplementationError, "Subclasses must implement encode"
26
- end
20
+ # @param message [Object]
21
+ # @param stream [StringIO]
22
+ # @param schema_name [String]
23
+ def encode(_message, _stream, schema_name: nil)
24
+ raise MissingImplementationError, 'Subclasses must implement encode'
25
+ end
27
26
 
28
- # @param stream [StringIO]
29
- # @param schema_text [String]
30
- # @param registry [Object]
31
- # @return [Object]
32
- def decode(_stream, _schema_text)
33
- raise MissingImplementationError, "Subclasses must implement decode"
34
- end
27
+ # @param stream [StringIO]
28
+ # @param schema_text [String]
29
+ # @param registry [Object]
30
+ # @return [Object]
31
+ def decode(_stream, _schema_text)
32
+ raise MissingImplementationError, 'Subclasses must implement decode'
33
+ end
35
34
 
36
- # @param message [Object]
37
- # @return [Hash<String, String>]
38
- def dependencies(_message)
39
- {}
40
- end
35
+ # @param message [Object]
36
+ # @return [Hash<String, String>]
37
+ def dependencies(_message)
38
+ {}
41
39
  end
42
40
  end
43
41
  end
@@ -1,29 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "schema_registry_client/schema/base"
4
- require "schema_registry_client/output/json_schema"
3
+ require 'schema_registry_client/schema/base'
4
+ require 'schema_registry_client/output/json_schema'
5
5
 
6
6
  module SchemaRegistry
7
7
  module Schema
8
8
  class ProtoJsonSchema < Base
9
- class << self
10
- def schema_type
11
- "JSON"
12
- end
9
+ def self.schema_type
10
+ 'JSON'
11
+ end
13
12
 
14
- def schema_text(message, schema_name: nil)
15
- SchemaRegistry::Output::JsonSchema.output(message.class.descriptor.to_proto)
16
- end
13
+ def schema_text(message, schema_name: nil)
14
+ SchemaRegistry::Output::JsonSchema.output(message.class.descriptor.to_proto)
15
+ end
17
16
 
18
- def encode(message, stream, schema_name: nil)
19
- json = message.to_h.sort.to_h.to_json
20
- stream.write(json)
21
- end
17
+ def encode(message, stream, schema_name: nil)
18
+ json = message.to_h.sort.to_h.to_json
19
+ stream.write(json)
20
+ end
22
21
 
23
- def decode(stream, _schema_text)
24
- json = stream.read
25
- JSON.parse(json)
26
- end
22
+ def decode(stream, _schema_text)
23
+ json = stream.read
24
+ JSON.parse(json)
27
25
  end
28
26
  end
29
27
  end
@@ -1,130 +1,130 @@
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
- class << self
14
- def schema_type
15
- "PROTOBUF"
16
- end
13
+ def self.schema_type
14
+ 'PROTOBUF'
15
+ end
17
16
 
18
- def schema_text(message, schema_name: nil)
19
- file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
20
- message
21
- else
22
- message.class.descriptor.file_descriptor
23
- end
24
- SchemaRegistry::Output::ProtoText.output(file_descriptor.to_proto)
17
+ def schema_text(message, schema_name: nil)
18
+ file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
19
+ message
20
+ else
21
+ message.class.descriptor.file_descriptor
22
+ end
23
+ SchemaRegistry::Output::ProtoText.output(file_descriptor.to_proto)
24
+ end
25
+
26
+ def encode(message, stream, schema_name: nil)
27
+ _, indexes = find_index(message.class.descriptor.to_proto,
28
+ message.class.descriptor.file_descriptor.to_proto.message_type)
29
+
30
+ if indexes == [0]
31
+ SchemaRegistry::Wire.write_int(stream, 0)
32
+ else
33
+ SchemaRegistry::Wire.write_int(stream, indexes.length)
34
+ indexes.each { |i| SchemaRegistry::Wire.write_int(stream, i) }
25
35
  end
26
36
 
27
- def encode(message, stream, schema_name: nil)
28
- _, indexes = find_index(message.class.descriptor.to_proto,
29
- message.class.descriptor.file_descriptor.to_proto.message_type)
37
+ # Now we write the actual message.
38
+ stream.write(message.to_proto)
39
+ end
30
40
 
31
- if indexes == [0]
32
- SchemaRegistry::Wire.write_int(stream, 0)
33
- else
34
- SchemaRegistry::Wire.write_int(stream, indexes.length)
35
- indexes.each { |i| SchemaRegistry::Wire.write_int(stream, i) }
41
+ def decode(stream, schema_text)
42
+ # See https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
43
+ index_length = SchemaRegistry::Wire.read_int(stream)
44
+ indexes = []
45
+ if index_length.zero?
46
+ indexes.push(0)
47
+ else
48
+ index_length.times do
49
+ indexes.push(SchemaRegistry::Wire.read_int(stream))
36
50
  end
37
-
38
- # Now we write the actual message.
39
- stream.write(message.to_proto)
40
51
  end
41
52
 
42
- def decode(stream, schema_text)
43
- # See https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/index.html#wire-format
44
- index_length = SchemaRegistry::Wire.read_int(stream)
45
- indexes = []
46
- if index_length.zero?
47
- indexes.push(0)
48
- else
49
- index_length.times do
50
- indexes.push(SchemaRegistry::Wire.read_int(stream))
51
- end
52
- end
53
+ encoded = stream.read
54
+ decode_protobuf(schema_text, encoded, indexes)
55
+ end
53
56
 
54
- encoded = stream.read
55
- decode_protobuf(schema_text, encoded, indexes)
56
- end
57
+ def load_schemas!
58
+ @all_schemas = {}
59
+ all_files = ObjectSpace.each_object(Google::Protobuf::FileDescriptor).to_a
60
+ all_files.each do |file_desc|
61
+ file_path = file_desc.name
62
+ next if file_path.start_with?('google/protobuf/') # skip built-in protos
57
63
 
58
- def load_schemas!
59
- @all_schemas = {}
60
- all_files = ObjectSpace.each_object(Google::Protobuf::FileDescriptor).to_a
61
- all_files.each do |file_desc|
62
- file_path = file_desc.name
63
- next if file_path.start_with?("google/protobuf/") # skip built-in protos
64
+ @all_schemas[file_path] = file_desc
65
+ end
66
+ end
64
67
 
65
- @all_schemas[file_path] = file_desc
66
- end
68
+ def dependencies(message)
69
+ return [] if message.nil?
70
+
71
+ load_schemas! unless @all_schemas&.any?
72
+ file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
73
+ message
74
+ else
75
+ message.class.descriptor.file_descriptor
76
+ end
77
+
78
+ deps = file_descriptor.to_proto.dependency.to_a
79
+ .reject { |d| d.start_with?('google/protobuf/') }
80
+ deps.to_h do |dep|
81
+ dependency_schema = @all_schemas[dep]
82
+ [dependency_schema.name, dependency_schema]
67
83
  end
84
+ end
68
85
 
69
- def dependencies(message)
70
- load_schemas! unless @all_schemas&.any?
71
- file_descriptor = if message.is_a?(Google::Protobuf::FileDescriptor)
72
- message
86
+ def find_index(descriptor, messages, indexes = [])
87
+ messages.each_with_index do |sub_descriptor, i|
88
+ if sub_descriptor == descriptor
89
+ indexes.push(i)
90
+ return [true, indexes]
73
91
  else
74
- message.class.descriptor.file_descriptor
75
- end
76
-
77
- deps = file_descriptor.to_proto.dependency.to_a
78
- .reject { |d| d.start_with?("google/protobuf/") }
79
- deps.to_h do |dep|
80
- dependency_schema = @all_schemas[dep]
81
- [dependency_schema.name, dependency_schema]
92
+ found, found_indexes = find_index(descriptor, sub_descriptor.nested_type, indexes + [i])
93
+ return [true, found_indexes] if found
82
94
  end
83
95
  end
96
+ []
97
+ end
84
98
 
85
- def find_index(descriptor, messages, indexes = [])
86
- messages.each_with_index do |sub_descriptor, i|
87
- if sub_descriptor == descriptor
88
- indexes.push(i)
89
- return [true, indexes]
90
- else
91
- found, found_indexes = find_index(descriptor, sub_descriptor.nested_type, indexes + [i])
92
- return [true, found_indexes] if found
93
- end
94
- end
95
- []
99
+ def find_descriptor(indexes, messages)
100
+ first_index = indexes.shift
101
+ message = messages[first_index]
102
+ path = [message.name]
103
+ while indexes.length.positive?
104
+ message = message.nested_type[indexes.shift]
105
+ path.push(message.name)
96
106
  end
107
+ path
108
+ end
97
109
 
98
- def find_descriptor(indexes, messages)
99
- first_index = indexes.shift
100
- message = messages[first_index]
101
- path = [message.name]
102
- while indexes.length.positive?
103
- message = message.nested_type[indexes.shift]
104
- path.push(message.name)
105
- end
106
- path
110
+ def decode_protobuf(schema, encoded, indexes)
111
+ # get the package
112
+ package = schema.match(/package (\S+);/)[1]
113
+ # get the first message in the protobuf text
114
+ # TODO - get the correct message based on schema index
115
+ message_name = schema.match(/message (\w+) {/)[1]
116
+ # look up the descriptor
117
+ full_name = "#{package}.#{message_name}"
118
+ descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(full_name)
119
+ unless descriptor
120
+ msg = "Could not find schema for #{full_name}. " \
121
+ 'Make sure the corresponding .proto file has been compiled and loaded.'
122
+ raise msg
107
123
  end
108
124
 
109
- def decode_protobuf(schema, encoded, indexes)
110
- # get the package
111
- package = schema.match(/package (\S+);/)[1]
112
- # get the first message in the protobuf text
113
- # TODO - get the correct message based on schema index
114
- message_name = schema.match(/message (\w+) {/)[1]
115
- # look up the descriptor
116
- full_name = "#{package}.#{message_name}"
117
- descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(full_name)
118
- unless descriptor
119
- msg = "Could not find schema for #{full_name}. " \
120
- "Make sure the corresponding .proto file has been compiled and loaded."
121
- raise msg
122
- end
123
-
124
- path = find_descriptor(indexes, descriptor.file_descriptor.to_proto.message_type)
125
- correct_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("#{package}.#{path.join(".")}")
126
- correct_message.msgclass.decode(encoded)
127
- end
125
+ path = find_descriptor(indexes, descriptor.file_descriptor.to_proto.message_type)
126
+ correct_message = Google::Protobuf::DescriptorPool.generated_pool.lookup("#{package}.#{path.join('.')}")
127
+ correct_message.msgclass.decode(encoded)
128
128
  end
129
129
  end
130
130
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SchemaRegistry
4
- VERSION = "0.0.7"
4
+ VERSION = '0.0.8'
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
  #
@@ -66,7 +66,7 @@ module SchemaRegistry
66
66
  client_key_data: nil,
67
67
  connect_timeout: nil,
68
68
  resolv_resolver: nil,
69
- schema_type: SchemaRegistry::Schema::Protobuf
69
+ schema_type: SchemaRegistry::Schema::Protobuf.new
70
70
  )
71
71
  @logger = logger || Logger.new($stderr)
72
72
  @registry = registry || SchemaRegistry::CachedConfluentSchemaRegistry.new(
@@ -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,17 +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.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
-
158
157
  end
159
-
160
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