deimos-ruby 2.3.0 → 2.4.0.pre.beta2

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +2 -0
  4. data/CLAUDE.md +270 -0
  5. data/README.md +10 -5
  6. data/deimos-ruby.gemspec +2 -2
  7. data/lib/deimos/config/configuration.rb +1 -1
  8. data/lib/deimos/ext/producer_middleware.rb +2 -2
  9. data/lib/deimos/kafka_source.rb +1 -1
  10. data/lib/deimos/message.rb +11 -2
  11. data/lib/deimos/schema_backends/avro_base.rb +4 -6
  12. data/lib/deimos/schema_backends/avro_local.rb +13 -12
  13. data/lib/deimos/schema_backends/avro_schema_registry.rb +14 -15
  14. data/lib/deimos/schema_backends/avro_validation.rb +1 -1
  15. data/lib/deimos/schema_backends/base.rb +5 -4
  16. data/lib/deimos/schema_backends/mock.rb +1 -1
  17. data/lib/deimos/schema_backends/plain.rb +1 -1
  18. data/lib/deimos/schema_backends/proto_base.rb +36 -11
  19. data/lib/deimos/schema_backends/proto_local.rb +5 -5
  20. data/lib/deimos/schema_backends/proto_schema_registry.rb +37 -7
  21. data/lib/deimos/test_helpers.rb +7 -0
  22. data/lib/deimos/transcoder.rb +1 -1
  23. data/lib/deimos/utils/outbox_producer.rb +1 -1
  24. data/lib/deimos/version.rb +1 -1
  25. data/lib/deimos.rb +9 -1
  26. data/lib/generators/deimos/active_record_generator.rb +1 -1
  27. data/lib/generators/deimos/schema_class_generator.rb +3 -3
  28. data/lib/generators/deimos/v2_generator.rb +2 -2
  29. data/spec/gen/sample/v1/sample_key_pb.rb +17 -0
  30. data/spec/generators/schema_class_generator_spec.rb +1 -1
  31. data/spec/protos/sample/v1/sample_key.proto +7 -0
  32. data/spec/schema_backends/avro_base_shared.rb +1 -1
  33. data/spec/schema_backends/avro_local_spec.rb +1 -8
  34. data/spec/schema_backends/avro_schema_registry_spec.rb +7 -7
  35. data/spec/schema_backends/base_spec.rb +2 -2
  36. data/spec/schema_backends/proto_schema_registry_spec.rb +222 -19
  37. data/spec/spec_helper.rb +1 -1
  38. metadata +32 -35
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base'
4
- require 'proto_turf'
4
+ require 'schema_registry_client'
5
5
 
6
6
  module Deimos
7
7
  module SchemaBackends
@@ -24,26 +24,51 @@ module Deimos
24
24
  float: :float,
25
25
  message: :record
26
26
  }.freeze
27
+
27
28
  def proto_schema(schema=@schema)
28
- Google::Protobuf::DescriptorPool.generated_pool.lookup(schema)
29
+ proto = Google::Protobuf::DescriptorPool.generated_pool.lookup(schema)
30
+ if proto.nil?
31
+ raise "Could not find Protobuf schema '#{schema}'."
32
+ end
33
+
34
+ proto
29
35
  end
30
36
 
31
37
  # @override
32
38
  def encode_key(key_id, key, topic: nil)
33
- if key.is_a?(Hash)
34
- key_id ? key.with_indifferent_access[key_id].to_s : key.sort.to_h.to_json
39
+ if key.respond_to?(:to_h)
40
+ hash = if key_id
41
+ key_id.to_s.split('.')[...-1].each do |k|
42
+ key = key.with_indifferent_access[k]
43
+ end
44
+ key.to_h.with_indifferent_access.slice(key_id.split('.').last)
45
+ else
46
+ key.to_h.sort.to_h
47
+ end
48
+ self.encode_proto_key(hash, topic: topic, field: key_id)
49
+ elsif key_id
50
+ hash = { key_id.to_s.split('.').last => key }
51
+ self.encode_proto_key(hash, topic: topic, field: key_id)
35
52
  else
36
53
  key.to_s
37
54
  end
38
55
  end
39
56
 
57
+ # @param hash [Hash]
58
+ # @return [String]
59
+ def encode_proto_key(hash, topic: nil, field: nil)
60
+ hash.sort.to_h.to_json
61
+ end
62
+
63
+ def decode_proto_key(payload)
64
+ JSON.parse(payload)
65
+ rescue StandardError
66
+ payload
67
+ end
68
+
40
69
  # @override
41
70
  def decode_key(payload, key_id)
42
- val = begin
43
- JSON.parse(payload)
44
- rescue StandardError
45
- payload
46
- end
71
+ val = decode_proto_key(payload)
47
72
  key_id ? val[key_id.to_s] : val
48
73
  end
49
74
 
@@ -85,8 +110,8 @@ module Deimos
85
110
  :mock
86
111
  end
87
112
 
88
- def generate_key_schema(_field_name)
89
- raise 'Protobuf cannot generate key schemas! Please use field_config :plain'
113
+ def supports_key_schemas?
114
+ false
90
115
  end
91
116
 
92
117
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'proto_base'
4
- require 'proto_turf'
4
+ require 'schema_registry_client'
5
5
 
6
6
  module Deimos
7
7
  module SchemaBackends
@@ -14,14 +14,14 @@ module Deimos
14
14
  end
15
15
 
16
16
  # @override
17
- def encode_payload(payload, schema: nil, topic: nil)
17
+ def encode_payload(payload, schema: nil, subject: nil)
18
18
  msg = payload.is_a?(Hash) ? proto_schema.msgclass.new(**payload) : payload
19
19
  proto_schema.msgclass.encode(msg)
20
20
  end
21
21
 
22
- # @return [ProtoTurf]
23
- def self.proto_turf
24
- @proto_turf ||= ProtoTurf.new(
22
+ # @return [SchemaRegistry::Client]
23
+ def self.schema_registry
24
+ @schema_registry ||= SchemaRegistry::Client.new(
25
25
  registry_url: Deimos.config.schema.registry_url,
26
26
  logger: Karafka.logger
27
27
  )
@@ -1,31 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'proto_base'
4
- require 'proto_turf'
4
+ require 'schema_registry_client'
5
5
 
6
6
  module Deimos
7
7
  module SchemaBackends
8
8
  # Encode / decode using the Protobuf schema registry.
9
9
  class ProtoSchemaRegistry < ProtoBase
10
10
 
11
+ # @override
12
+ def self.mock_backend
13
+ :proto_local
14
+ end
15
+
11
16
  # @override
12
17
  def decode_payload(payload, schema:)
13
- self.class.proto_turf.decode(payload)
18
+ self.class.schema_registry.decode(payload)
14
19
  end
15
20
 
16
21
  # @override
17
- def encode_payload(payload, schema: nil, topic: nil)
22
+ def encode_payload(payload, schema: nil, subject: nil)
18
23
  msg = payload.is_a?(Hash) ? proto_schema.msgclass.new(**payload) : payload
19
- self.class.proto_turf.encode(msg, subject: topic)
24
+ encoder = subject&.ends_with?('-key') ? self.class.key_schema_registry : self.class.schema_registry
25
+ encoder.encode(msg, subject: subject)
26
+ end
27
+
28
+ # @override
29
+ def encode_proto_key(key, topic: nil, field: nil)
30
+ schema_text = SchemaRegistry::Output::JsonSchema.output(proto_schema.to_proto, path: field)
31
+ self.class.key_schema_registry.encode(key, subject: "#{topic}-key", schema_text: schema_text)
32
+ end
33
+
34
+ # @override
35
+ def decode_proto_key(payload)
36
+ self.class.key_schema_registry.decode(payload)
20
37
  end
21
38
 
22
- # @return [ProtoTurf]
23
- def self.proto_turf
24
- @proto_turf ||= ProtoTurf.new(
39
+ # @return [SchemaRegistry::Client]
40
+ def self.schema_registry
41
+ @schema_registry ||= SchemaRegistry::Client.new(
25
42
  registry_url: Deimos.config.schema.registry_url,
43
+ user: Deimos.config.schema.user,
44
+ password: Deimos.config.schema.password,
26
45
  logger: Karafka.logger
27
46
  )
28
47
  end
48
+
49
+ def self.key_schema_registry
50
+ @key_schema_registry ||= SchemaRegistry::Client.new(
51
+ registry_url: Deimos.config.schema.registry_url,
52
+ user: Deimos.config.schema.user,
53
+ password: Deimos.config.schema.password,
54
+ logger: Karafka.logger,
55
+ schema_type: SchemaRegistry::Schema::ProtoJsonSchema
56
+ )
57
+ end
58
+
29
59
  end
30
60
  end
31
61
  end
@@ -50,6 +50,13 @@ module Deimos
50
50
  warn("unit_test! is deprecated and can be replaced by setting Deimos's schema backend " \
51
51
  'to `:avro_validation`. All other test behavior is provided by Karafka.')
52
52
  end
53
+
54
+ def with_mock_backends
55
+ Deimos.mock_backends = true
56
+ yield
57
+ Deimos.mock_backends = false
58
+ end
59
+
53
60
  end
54
61
 
55
62
  # get the difference of 2 hashes.
@@ -36,7 +36,7 @@ module Deimos
36
36
  if self.key_field
37
37
  self.backend.encode_key(self.key_field, key, topic: @topic)
38
38
  else
39
- self.backend.encode(key, topic: @topic)
39
+ self.backend.encode(key, topic: @topic, is_key: true)
40
40
  end
41
41
  end
42
42
 
@@ -205,7 +205,7 @@ module Deimos
205
205
  batch_size = batch.size
206
206
  current_index = 0
207
207
 
208
- batch[current_index..-1].in_groups_of(batch_size, false).each do |group|
208
+ batch[current_index..].in_groups_of(batch_size, false).each do |group|
209
209
  @logger.debug("Publishing #{group.size} messages to #{@current_topic}")
210
210
  Deimos.producer_for(@current_topic).produce_many_sync(group)
211
211
  current_index += group.size
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '2.3.0'
4
+ VERSION = '2.4.0-beta2'
5
5
  end
data/lib/deimos.rb CHANGED
@@ -54,6 +54,9 @@ module Deimos
54
54
 
55
55
  class << self
56
56
 
57
+ # @return [Boolean] for use in unit tests
58
+ attr_accessor :mock_backends
59
+
57
60
  # @param backend [Symbol, nil]
58
61
  # @return [Class<Deimos::SchemaBackends::Base>]
59
62
  def schema_backend_class(backend: nil)
@@ -61,7 +64,12 @@ module Deimos
61
64
 
62
65
  require "deimos/schema_backends/#{backend}"
63
66
 
64
- "Deimos::SchemaBackends::#{backend.to_s.classify}".constantize
67
+ klass = "Deimos::SchemaBackends::#{backend.to_s.classify}".constantize
68
+ if self.mock_backends
69
+ require "deimos/schema_backends/#{klass.mock_backend}"
70
+ klass = "Deimos::SchemaBackends::#{klass.mock_backend.to_s.classify}".constantize
71
+ end
72
+ klass
65
73
  end
66
74
 
67
75
  # @param schema [String, Symbol]
@@ -48,7 +48,7 @@ module Deimos
48
48
  # @return [String]
49
49
  def schema
50
50
  last_dot = self.full_schema.rindex('.')
51
- self.full_schema[(last_dot + 1)..-1]
51
+ self.full_schema[(last_dot + 1)..]
52
52
  end
53
53
 
54
54
  # @return [String]
@@ -13,7 +13,7 @@ module Deimos
13
13
  # @return [Array<Symbol>]
14
14
  SPECIAL_TYPES = %i(record enum).freeze
15
15
  # @return [String]
16
- INITIALIZE_WHITESPACE = "\n#{' ' * 19}"
16
+ INITIALIZE_WHITESPACE = "\n#{' ' * 19}".freeze
17
17
  # @return [Array<String>]
18
18
  IGNORE_DEFAULTS = %w(message_id timestamp).freeze
19
19
  # @return [String]
@@ -192,7 +192,7 @@ module Deimos
192
192
 
193
193
  def generate_from_schema_files(found_schemas)
194
194
  path = Deimos.config.schema.path || Deimos.config.schema.paths[:avro].first
195
- schema_store = AvroTurf::MutableSchemaStore.new(path: path)
195
+ schema_store = SchemaRegistry::AvroSchemaStore.new(path: path)
196
196
  schema_store.load_schemas!
197
197
  schema_store.schemas.values.sort_by { |s| "#{s.namespace}#{s.name}" }.each do |schema|
198
198
  name = "#{schema.namespace}.#{schema.name}"
@@ -265,7 +265,7 @@ module Deimos
265
265
  end
266
266
 
267
267
  result = "def initialize(_from_message: false, #{arguments.first}"
268
- arguments[1..-1].each_with_index do |arg, _i|
268
+ arguments[1..].each_with_index do |arg, _i|
269
269
  result += ",#{INITIALIZE_WHITESPACE}#{arg}"
270
270
  end
271
271
  "#{result})"
@@ -119,7 +119,7 @@ module Deimos
119
119
  end
120
120
 
121
121
  def consumer_configs
122
- deimos_config.consumer_objects.group_by(&:group_id).map { |group_id, consumers|
122
+ deimos_config.consumer_objects.group_by(&:group_id).to_h do |group_id, consumers|
123
123
  [group_id, consumers.map do |consumer|
124
124
  kafka_configs = {}
125
125
  kafka_configs['auto.offset.reset'] = consumer.start_from_beginning ? 'earliest' : 'latest'
@@ -159,7 +159,7 @@ module Deimos
159
159
  configs[:each_message] = true unless consumer.delivery.to_s == 'inline_batch'
160
160
  configs
161
161
  end]
162
- }.to_h
162
+ end
163
163
  end
164
164
 
165
165
  def producer_configs
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: sample/v1/sample_key.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+
8
+ descriptor_data = "\n\x1asample/v1/sample_key.proto\x12\tsample.v1\"\x1f\n\x10SampleMessageKey\x12\x0b\n\x03str\x18\x01 \x01(\tb\x06proto3"
9
+
10
+ pool = ::Google::Protobuf::DescriptorPool.generated_pool
11
+ pool.add_serialized_file(descriptor_data)
12
+
13
+ module Sample
14
+ module V1
15
+ SampleMessageKey = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("sample.v1.SampleMessageKey").msgclass
16
+ end
17
+ end
@@ -20,7 +20,7 @@ end
20
20
 
21
21
  RSpec.describe Deimos::Generators::SchemaClassGenerator do
22
22
  let(:schema_class_path) { 'spec/app/lib/schema_classes' }
23
- let(:files) { Dir["#{schema_class_path}/**/*.rb"].map { |f| [f, File.read(f)]}.to_h }
23
+ let(:files) { Dir["#{schema_class_path}/**/*.rb"].to_h { |f| [f, File.read(f)]} }
24
24
 
25
25
  before(:each) do
26
26
  Deimos.config.reset!
@@ -0,0 +1,7 @@
1
+ syntax = "proto3";
2
+
3
+ package sample.v1;
4
+
5
+ message SampleMessageKey {
6
+ string str = 1;
7
+ }
@@ -66,7 +66,7 @@ RSpec.shared_examples_for('an Avro backend') do
66
66
  expect(backend.encode_key('test_id', 1, topic: 'topic')).to eq('itsme')
67
67
  expect(backend).to have_received(:encode).
68
68
  with({ 'test_id' => 1 }, { schema: 'MySchema_key', topic: 'topic' })
69
- expect(backend.schema_store.find('MySchema_key', 'com.my-namespace').to_avro).
69
+ expect(backend.schema_store.find('com.my-namespace.MySchema_key').to_avro).
70
70
  to eq(
71
71
  'doc' => 'Key for com.my-namespace.MySchema - autogenerated by Deimos',
72
72
  'fields' => [
@@ -15,17 +15,10 @@ RSpec.describe Deimos::SchemaBackends::AvroLocal do
15
15
  it_should_behave_like 'an Avro backend'
16
16
 
17
17
  it 'should encode and decode correctly' do
18
- avro_turf = instance_double(AvroTurf)
19
- allow(avro_turf).to receive_messages(encode: 'encoded-payload', decode: payload)
20
- allow(backend).to receive(:avro_turf).and_return(avro_turf)
21
18
  results = backend.encode(payload)
22
- expect(results).to eq('encoded-payload')
19
+ expect(results).to start_with("Obj\u0001\u0004\u0014avro.codec\bnull\u0016avro.schema\x94")
23
20
  results = backend.decode(results)
24
21
  expect(results).to eq(payload)
25
- expect(avro_turf).to have_received(:encode).
26
- with(payload, schema_name: 'MySchema', namespace: 'com.my-namespace')
27
- expect(avro_turf).to have_received(:decode).
28
- with('encoded-payload', schema_name: 'MySchema', namespace: 'com.my-namespace')
29
22
  end
30
23
 
31
24
  end
@@ -15,17 +15,17 @@ RSpec.describe Deimos::SchemaBackends::AvroSchemaRegistry do
15
15
  it_should_behave_like 'an Avro backend'
16
16
 
17
17
  it 'should encode and decode correctly' do
18
- avro_turf = instance_double(AvroTurf::Messaging)
19
- allow(avro_turf).to receive_messages(encode: 'encoded-payload', decode: payload)
20
- allow(backend).to receive(:avro_turf_messaging).and_return(avro_turf)
18
+ schema_registry = instance_double(SchemaRegistry::Client)
19
+ allow(schema_registry).to receive_messages(encode: 'encoded-payload', decode: payload)
20
+ allow(backend).to receive(:schema_registry).and_return(schema_registry)
21
21
  results = backend.encode(payload, topic: 'topic')
22
22
  expect(results).to eq('encoded-payload')
23
23
  results = backend.decode(results)
24
24
  expect(results).to eq(payload)
25
- expect(avro_turf).to have_received(:encode).
26
- with(payload, schema_name: 'MySchema', subject: 'topic')
27
- expect(avro_turf).to have_received(:decode).
28
- with('encoded-payload', schema_name: 'MySchema')
25
+ expect(schema_registry).to have_received(:encode).
26
+ with(payload, schema_name: 'com.my-namespace.MySchema', subject: 'topic-value')
27
+ expect(schema_registry).to have_received(:decode).
28
+ with('encoded-payload')
29
29
  end
30
30
 
31
31
  end
@@ -6,13 +6,13 @@ describe Deimos::SchemaBackends::Base do
6
6
 
7
7
  it 'should validate on encode' do
8
8
  expect(backend).to receive(:validate).with(payload, schema: 'schema')
9
- expect(backend).to receive(:encode_payload).with(payload, schema: 'schema', topic: 'topic')
9
+ expect(backend).to receive(:encode_payload).with(payload, schema: 'schema', subject: 'topic-value')
10
10
  backend.encode(payload, topic: 'topic')
11
11
  end
12
12
 
13
13
  it 'should validate and encode a passed schema' do
14
14
  expect(backend).to receive(:validate).with(payload, schema: 'schema2')
15
- expect(backend).to receive(:encode_payload).with(payload, schema: 'schema2', topic: 'topic')
15
+ expect(backend).to receive(:encode_payload).with(payload, schema: 'schema2', subject: 'topic-value')
16
16
  backend.encode(payload, schema: 'schema2', topic: 'topic')
17
17
  end
18
18