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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'deimos/schema_backends/proto_schema_registry'
4
4
  require_relative "#{__dir__}/../gen/sample/v1/sample_pb"
5
+ require_relative "#{__dir__}/../gen/sample/v1/sample_key_pb"
5
6
 
6
7
  RSpec.describe Deimos::SchemaBackends::ProtoSchemaRegistry do
7
8
  let(:payload) do
@@ -16,32 +17,234 @@ RSpec.describe Deimos::SchemaBackends::ProtoSchemaRegistry do
16
17
  str_map: { 'foo' => 'bar' }
17
18
  )
18
19
  end
20
+ let(:schema_registry) { instance_double(SchemaRegistry::Client) }
21
+ let(:key_schema_registry) { instance_double(SchemaRegistry::Client) }
19
22
 
20
23
  let(:backend) { described_class.new(schema: 'sample.v1.SampleMessage') }
21
24
 
22
- specify('#encode_key') do
23
- expect(backend.encode_key(nil, 789)).to eq('789')
24
- expect(backend.encode_key(nil, 'string')).to eq('string')
25
- expect(backend.encode_key(nil, { foo: 'bar' })).to eq('{"foo":"bar"}')
26
- expect(backend.encode_key(:foo, 'bar')).to eq('bar')
25
+ before(:each) do
26
+ allow(described_class).to receive_messages(schema_registry: schema_registry,
27
+ key_schema_registry: key_schema_registry)
27
28
  end
28
29
 
29
- specify('#decode_key') do
30
- expect(backend.decode_key('789', nil)).to eq(789)
31
- expect(backend.decode_key('{"foo":"bar"}', :foo)).to eq('bar')
32
- expect(backend.decode_key('{"foo":"bar"}', nil)).to eq({ 'foo' => 'bar' })
30
+ describe 'payload encoding and decoding' do
31
+ it 'should encode and decode payloads correctly' do
32
+ allow(schema_registry).to receive_messages(encode: 'encoded-payload', decode: payload)
33
+
34
+ # Encode
35
+ encoded = backend.encode(payload, topic: 'topic')
36
+ expect(encoded).to eq('encoded-payload')
37
+ expect(schema_registry).to have_received(:encode).with(payload, subject: 'topic-value')
38
+
39
+ # Decode
40
+ decoded = backend.decode(encoded)
41
+ expect(decoded).to eq(payload)
42
+ expect(schema_registry).to have_received(:decode).with('encoded-payload')
43
+ end
44
+
45
+ it 'should encode hash payloads by converting to protobuf message' do
46
+ hash_payload = {
47
+ str: 'string',
48
+ num: 123,
49
+ flag: true
50
+ }
51
+ allow(schema_registry).to receive(:encode).and_return('encoded-payload')
52
+
53
+ backend.encode(hash_payload, topic: 'topic')
54
+
55
+ # Should have converted hash to protobuf message before encoding
56
+ expect(schema_registry).to have_received(:encode) do |arg, **kwargs|
57
+ expect(arg).to be_a(Sample::V1::SampleMessage)
58
+ expect(arg.str).to eq('string')
59
+ expect(arg.num).to eq(123)
60
+ expect(arg.flag).to be(true)
61
+ expect(kwargs).to eq(subject: 'topic-value')
62
+ end
63
+ end
33
64
  end
34
65
 
35
- it 'should encode and decode correctly' do
36
- proto_turf = instance_double(ProtoTurf)
37
- allow(proto_turf).to receive_messages(encode: 'encoded-payload', decode: payload)
38
- allow(described_class).to receive(:proto_turf).and_return(proto_turf)
39
- results = backend.encode(payload, topic: 'topic')
40
- expect(results).to eq('encoded-payload')
41
- results = backend.decode(results)
42
- expect(results).to eq(payload)
43
- expect(proto_turf).to have_received(:encode).with(payload, subject: 'topic')
44
- expect(proto_turf).to have_received(:decode).with('encoded-payload')
66
+ describe 'key encoding with key_config field (auto-generated JSON schema)' do
67
+ it 'should encode a simple field key using JSON schema' do
68
+ key_hash = { 'str' => 'test-key' }
69
+
70
+ allow(key_schema_registry).to receive(:encode).and_return('encoded-key')
71
+
72
+ # encode_key is called from encode_proto_key with the hash
73
+ encoded = backend.encode_key('str', 'test-key', topic: 'my-topic')
74
+
75
+ expect(key_schema_registry).to have_received(:encode).with(
76
+ key_hash,
77
+ subject: 'my-topic-key',
78
+ schema_text: anything
79
+ )
80
+ expect(encoded).to eq('encoded-key')
81
+ end
82
+
83
+ it 'should encode a hash key with field extraction using JSON schema' do
84
+ full_payload = { 'str' => 'test-key', 'num' => 123 }
85
+ key_hash = { 'str' => 'test-key' }
86
+
87
+ allow(key_schema_registry).to receive(:encode).and_return('encoded-key')
88
+
89
+ encoded = backend.encode_key('str', full_payload, topic: 'my-topic')
90
+
91
+ expect(key_schema_registry).to have_received(:encode).with(
92
+ key_hash,
93
+ subject: 'my-topic-key',
94
+ schema_text: anything
95
+ )
96
+ expect(encoded).to eq('encoded-key')
97
+ end
98
+
99
+ it 'should decode a JSON schema key' do
100
+ decoded_json = { 'str' => 'test-key' }
101
+ allow(key_schema_registry).to receive(:decode).and_return(decoded_json)
102
+
103
+ result = backend.decode_key('encoded-key', 'str')
104
+
105
+ expect(key_schema_registry).to have_received(:decode).with('encoded-key')
106
+ expect(result).to eq('test-key')
107
+ end
108
+
109
+ it 'should decode a JSON schema key without field extraction' do
110
+ decoded_json = { 'str' => 'test-key', 'num' => 123 }
111
+ allow(key_schema_registry).to receive(:decode).and_return(decoded_json)
112
+
113
+ result = backend.decode_key('encoded-key', nil)
114
+
115
+ expect(key_schema_registry).to have_received(:decode).with('encoded-key')
116
+ expect(result).to eq(decoded_json)
117
+ end
45
118
  end
46
119
 
120
+ describe 'key encoding with key_config schema (separate Protobuf schema)' do
121
+ let(:key_backend) { described_class.new(schema: 'sample.v1.SampleMessageKey') }
122
+ let(:key_schema_registry_for_test) { instance_double(SchemaRegistry::Client) }
123
+ let(:schema_registry_for_test) { instance_double(SchemaRegistry::Client) }
124
+
125
+ before(:each) do
126
+ # For key_backend tests, mock both schema_registry instances
127
+ allow(described_class).to receive_messages(schema_registry: schema_registry_for_test,
128
+ key_schema_registry: key_schema_registry_for_test)
129
+ end
130
+
131
+ it 'should encode a protobuf key message using the key schema' do
132
+ key_msg = Sample::V1::SampleMessageKey.new(str: 'test-key')
133
+ # Since subject ends with '-key', encode_payload uses key_schema_registry
134
+ allow(key_schema_registry_for_test).to receive(:encode).and_return('encoded-key')
135
+
136
+ encoded = key_backend.encode(key_msg, topic: 'my-topic', is_key: true)
137
+
138
+ expect(key_schema_registry_for_test).to have_received(:encode).with(key_msg, subject: 'my-topic-key')
139
+ expect(encoded).to eq('encoded-key')
140
+ end
141
+
142
+ it 'should encode a hash key using the key schema' do
143
+ key_hash = { str: 'test-key' }
144
+ allow(key_schema_registry_for_test).to receive(:encode).and_return('encoded-key')
145
+
146
+ encoded = key_backend.encode(key_hash, topic: 'my-topic', is_key: true)
147
+
148
+ expect(key_schema_registry_for_test).to have_received(:encode) do |arg, **kwargs|
149
+ expect(arg).to be_a(Sample::V1::SampleMessageKey)
150
+ expect(arg.str).to eq('test-key')
151
+ expect(kwargs).to eq(subject: 'my-topic-key')
152
+ end
153
+ expect(encoded).to eq('encoded-key')
154
+ end
155
+
156
+ it 'should decode a protobuf key' do
157
+ key_msg = Sample::V1::SampleMessageKey.new(str: 'test-key')
158
+ allow(schema_registry_for_test).to receive(:decode).and_return(key_msg)
159
+
160
+ decoded = key_backend.decode('encoded-key')
161
+
162
+ expect(schema_registry_for_test).to have_received(:decode).with('encoded-key')
163
+ expect(decoded).to eq(key_msg)
164
+ end
165
+ end
166
+
167
+ describe 'backward compatibility with plain string/JSON keys' do
168
+ it 'should encode plain string keys as strings' do
169
+ expect(backend.encode_key(nil, 'simple-string', topic: 'my-topic')).to eq('simple-string')
170
+ end
171
+
172
+ it 'should encode integer keys as strings' do
173
+ expect(backend.encode_key(nil, 789, topic: 'my-topic')).to eq('789')
174
+ end
175
+
176
+ it 'should encode hash keys using JSON schema when no field specified' do
177
+ key_hash = { foo: 'bar', baz: 'qux' }
178
+ allow(key_schema_registry).to receive(:encode).and_return('encoded-json')
179
+
180
+ backend.encode_key(nil, key_hash, topic: 'my-topic')
181
+
182
+ # Should use encode_proto_key which calls key_schema_registry.encode
183
+ expect(key_schema_registry).to have_received(:encode)
184
+ end
185
+
186
+ it 'should decode JSON string keys to hashes' do
187
+ decoded_hash = { 'foo' => 'bar' }
188
+ allow(key_schema_registry).to receive(:decode).and_return(decoded_hash)
189
+
190
+ result = backend.decode_key('{"foo":"bar"}', nil)
191
+ expect(result).to eq(decoded_hash)
192
+ end
193
+
194
+ it 'should decode JSON keys with field extraction' do
195
+ decoded_hash = { 'foo' => 'bar', 'baz' => 'qux' }
196
+ allow(key_schema_registry).to receive(:decode).and_return(decoded_hash)
197
+
198
+ result = backend.decode_key('encoded-key', :foo)
199
+ expect(result).to eq('bar')
200
+ end
201
+ end
202
+
203
+ describe 'edge cases' do
204
+ it 'should handle nested field extraction for keys' do
205
+ # Use nested field from the actual schema: non_union_nested.nested_str
206
+ nested_payload = { 'non_union_nested' => { 'nested_str' => 'value', 'nested_num' => 123 }, 'str' => 'other' }
207
+ allow(key_schema_registry).to receive(:encode).and_return('encoded-key')
208
+
209
+ backend.encode_key('non_union_nested.nested_str', nested_payload, topic: 'my-topic')
210
+
211
+ # Should encode with just the nested_str field extracted from the nested message
212
+ expect(key_schema_registry).to have_received(:encode).with(
213
+ { 'nested_str' => 'value' },
214
+ subject: 'my-topic-key',
215
+ schema_text: anything
216
+ )
217
+ end
218
+
219
+ it 'should handle encoding payloads with subject suffix detection' do
220
+ allow(schema_registry).to receive(:encode).and_return('encoded-payload')
221
+ allow(key_schema_registry).to receive(:encode).and_return('encoded-payload')
222
+
223
+ # When subject ends with '-key', should use key_schema_registry
224
+ backend.encode_payload(payload, subject: 'my-topic-key')
225
+ expect(key_schema_registry).to have_received(:encode).with(payload, subject: 'my-topic-key')
226
+
227
+ # When subject doesn't end with '-key', should use schema_registry
228
+ backend.encode_payload(payload, subject: 'my-topic-value')
229
+ expect(schema_registry).to have_received(:encode).with(payload, subject: 'my-topic-value')
230
+ end
231
+
232
+ it 'should handle nil payloads gracefully' do
233
+ # encode with nil payload - schema_registry will handle the nil
234
+ allow(schema_registry).to receive(:encode).with(nil, subject: 'topic-value').and_return(nil)
235
+ expect(backend.encode(nil, topic: 'topic')).to be_nil
236
+
237
+ # decode returns nil for nil payload (base class checks this)
238
+ expect(backend.decode(nil)).to be_nil
239
+ end
240
+ end
241
+
242
+ describe 'schema field introspection' do
243
+ it 'should return schema fields from protobuf descriptor' do
244
+ fields = backend.schema_fields
245
+
246
+ expect(fields).to be_an(Array)
247
+ expect(fields.map(&:name)).to include('str', 'num', 'str_arr', 'flag', 'timestamp')
248
+ end
249
+ end
47
250
  end
data/spec/spec_helper.rb CHANGED
@@ -16,7 +16,7 @@ require 'handlers/my_consumer'
16
16
  require 'rspec/rails'
17
17
  require 'rspec/snapshot'
18
18
  require 'karafka/testing/rspec/helpers'
19
- Dir['./spec/schemas/**/*.rb'].sort.each { |f| require f }
19
+ Dir['./spec/schemas/**/*.rb'].each { |f| require f }
20
20
 
21
21
  # Constants used for consumer specs
22
22
  SCHEMA_CLASS_SETTINGS = { off: false, on: true }.freeze
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0.pre.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
@@ -9,26 +9,6 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: avro_turf
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '1.4'
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '2'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- version: '1.4'
29
- - - "<"
30
- - !ruby/object:Gem::Version
31
- version: '2'
32
12
  - !ruby/object:Gem::Dependency
33
13
  name: benchmark
34
14
  requirement: !ruby/object:Gem::Requirement
@@ -71,6 +51,20 @@ dependencies:
71
51
  - - "~>"
72
52
  - !ruby/object:Gem::Version
73
53
  version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: schema_registry_client
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
74
68
  - !ruby/object:Gem::Dependency
75
69
  name: sigurd
76
70
  requirement: !ruby/object:Gem::Requirement
@@ -231,20 +225,6 @@ dependencies:
231
225
  - - "~>"
232
226
  - !ruby/object:Gem::Version
233
227
  version: '1.1'
234
- - !ruby/object:Gem::Dependency
235
- name: proto_turf
236
- requirement: !ruby/object:Gem::Requirement
237
- requirements:
238
- - - ">="
239
- - !ruby/object:Gem::Version
240
- version: '0'
241
- type: :development
242
- prerelease: false
243
- version_requirements: !ruby/object:Gem::Requirement
244
- requirements:
245
- - - ">="
246
- - !ruby/object:Gem::Version
247
- version: '0'
248
228
  - !ruby/object:Gem::Dependency
249
229
  name: rails
250
230
  requirement: !ruby/object:Gem::Requirement
@@ -357,6 +337,20 @@ dependencies:
357
337
  - - '='
358
338
  - !ruby/object:Gem::Version
359
339
  version: '3.8'
340
+ - !ruby/object:Gem::Dependency
341
+ name: schema_registry_client
342
+ requirement: !ruby/object:Gem::Requirement
343
+ requirements:
344
+ - - ">="
345
+ - !ruby/object:Gem::Version
346
+ version: '0'
347
+ type: :development
348
+ prerelease: false
349
+ version_requirements: !ruby/object:Gem::Requirement
350
+ requirements:
351
+ - - ">="
352
+ - !ruby/object:Gem::Version
353
+ version: '0'
360
354
  - !ruby/object:Gem::Dependency
361
355
  name: sord
362
356
  requirement: !ruby/object:Gem::Requirement
@@ -458,6 +452,7 @@ files:
458
452
  - ".rubocop_todo.yml"
459
453
  - ".tool-versions"
460
454
  - CHANGELOG.md
455
+ - CLAUDE.md
461
456
  - CODE_OF_CONDUCT.md
462
457
  - Dockerfile
463
458
  - Gemfile
@@ -583,6 +578,7 @@ files:
583
578
  - spec/batch_consumer_spec.rb
584
579
  - spec/consumer_spec.rb
585
580
  - spec/deimos_spec.rb
581
+ - spec/gen/sample/v1/sample_key_pb.rb
586
582
  - spec/gen/sample/v1/sample_pb.rb
587
583
  - spec/generators/active_record_generator_spec.rb
588
584
  - spec/generators/schema_class/my_schema_spec.rb
@@ -599,6 +595,7 @@ files:
599
595
  - spec/message_spec.rb
600
596
  - spec/producer_spec.rb
601
597
  - spec/protos/sample/v1/sample.proto
598
+ - spec/protos/sample/v1/sample_key.proto
602
599
  - spec/rake_spec.rb
603
600
  - spec/schema_backends/avro_base_shared.rb
604
601
  - spec/schema_backends/avro_local_spec.rb