deimos-ruby 1.10.2 → 1.12.0
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 +4 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +8 -8
- data/README.md +150 -16
- data/deimos-ruby.gemspec +1 -1
- data/docs/CONFIGURATION.md +4 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +1 -1
- data/lib/deimos/active_record_consume/message_consumption.rb +4 -3
- data/lib/deimos/active_record_consumer.rb +2 -2
- data/lib/deimos/active_record_producer.rb +3 -0
- data/lib/deimos/config/configuration.rb +29 -0
- data/lib/deimos/consume/batch_consumption.rb +2 -2
- data/lib/deimos/consume/message_consumption.rb +2 -2
- data/lib/deimos/consumer.rb +10 -0
- data/lib/deimos/producer.rb +4 -3
- data/lib/deimos/schema_backends/avro_base.rb +64 -1
- data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -1
- data/lib/deimos/schema_backends/base.rb +18 -2
- data/lib/deimos/schema_class/base.rb +67 -0
- data/lib/deimos/schema_class/enum.rb +24 -0
- data/lib/deimos/schema_class/record.rb +59 -0
- data/lib/deimos/shared_config.rb +5 -0
- data/lib/deimos/test_helpers.rb +70 -17
- data/lib/deimos/utils/schema_class.rb +29 -0
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +3 -0
- data/lib/generators/deimos/schema_class/templates/schema_class.rb.tt +15 -0
- data/lib/generators/deimos/schema_class/templates/schema_enum.rb.tt +21 -0
- data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +65 -0
- data/lib/generators/deimos/schema_class_generator.rb +247 -0
- data/lib/tasks/deimos.rake +8 -0
- data/spec/active_record_batch_consumer_spec.rb +120 -110
- data/spec/active_record_consumer_spec.rb +97 -88
- data/spec/active_record_producer_spec.rb +38 -27
- data/spec/batch_consumer_spec.rb +37 -28
- data/spec/config/configuration_spec.rb +10 -3
- data/spec/consumer_spec.rb +94 -83
- data/spec/generators/active_record_generator_spec.rb +1 -0
- data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +206 -0
- data/spec/generators/schema_class_generator_spec.rb +186 -0
- data/spec/producer_spec.rb +110 -0
- data/spec/schema_classes/generated.rb +156 -0
- data/spec/schema_classes/my_nested_schema.rb +114 -0
- data/spec/schema_classes/my_schema.rb +53 -0
- data/spec/schema_classes/my_schema_key.rb +35 -0
- data/spec/schema_classes/my_schema_with_complex_types.rb +172 -0
- data/spec/schemas/com/my-namespace/Generated.avsc +6 -0
- data/spec/schemas/com/my-namespace/MySchemaWithComplexTypes.avsc +95 -0
- data/spec/spec_helper.rb +6 -1
- metadata +28 -4
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# For testing the generated class.
|
4
|
+
RSpec.describe Schemas::MySchemaWithComplexTypes do
|
5
|
+
let(:payload_hash) do
|
6
|
+
{
|
7
|
+
test_id: 'test id',
|
8
|
+
test_float: 1.2,
|
9
|
+
test_string_array: %w(abc def),
|
10
|
+
test_int_array: [123, 456],
|
11
|
+
some_integer_map: { 'int_1' => 1, 'int_2' => 2 },
|
12
|
+
some_record: Schemas::ARecord.new(a_record_field: 'field 1'),
|
13
|
+
some_optional_record: Schemas::ARecord.new(a_record_field: 'field 2'),
|
14
|
+
some_record_array: [Schemas::ARecord.new(a_record_field: 'field 3'),
|
15
|
+
Schemas::ARecord.new(a_record_field: 'field 4')],
|
16
|
+
some_record_map: {
|
17
|
+
'record_1' => Schemas::ARecord.new(a_record_field: 'field 5'),
|
18
|
+
'record_2' => Schemas::ARecord.new(a_record_field: 'field 6')
|
19
|
+
},
|
20
|
+
some_enum_array: [Schemas::AnEnum.new('sym1'),
|
21
|
+
Schemas::AnEnum.new('sym2')]
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'class initialization' do
|
26
|
+
|
27
|
+
it 'should initialize the class from keyword arguments' do
|
28
|
+
klass = described_class.new(
|
29
|
+
test_id: payload_hash[:test_id],
|
30
|
+
test_float: payload_hash[:test_float],
|
31
|
+
test_string_array: payload_hash[:test_string_array],
|
32
|
+
some_record: payload_hash[:some_record],
|
33
|
+
some_optional_record: payload_hash[:some_optional_record],
|
34
|
+
some_record_array: payload_hash[:some_record_array],
|
35
|
+
some_record_map: payload_hash[:some_record_map],
|
36
|
+
some_enum_array: payload_hash[:some_enum_array]
|
37
|
+
)
|
38
|
+
expect(klass).to be_instance_of(described_class)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should initialize the class from a hash with symbols as keys' do
|
42
|
+
klass = described_class.new(**payload_hash)
|
43
|
+
expect(klass).to be_instance_of(described_class)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should initialize the class when missing attributes' do
|
47
|
+
payload_hash.delete(:test_id)
|
48
|
+
klass = described_class.new(**payload_hash)
|
49
|
+
expect(klass).to be_instance_of(described_class)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'base class methods' do
|
55
|
+
let(:klass) do
|
56
|
+
described_class.new(**payload_hash)
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:schema_fields) do
|
60
|
+
%w(test_id test_float test_int_array test_optional_int test_string_array some_integer_map some_record some_optional_record some_record_array some_record_map some_enum_array)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should return the name of the schema and namespace' do
|
64
|
+
expect(klass.schema).to eq('MySchemaWithComplexTypes')
|
65
|
+
expect(klass.namespace).to eq('com.my-namespace')
|
66
|
+
expect(klass.full_schema).to eq('com.my-namespace.MySchemaWithComplexTypes')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should return an array of all fields in the schema' do
|
70
|
+
expect(klass.schema_fields).to match_array(schema_fields)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return a json version of the payload' do
|
74
|
+
described_class.new(**payload_hash)
|
75
|
+
payload_h = {
|
76
|
+
'test_id' => 'test id',
|
77
|
+
'test_float' => 1.2,
|
78
|
+
'test_string_array' => %w(abc def),
|
79
|
+
'test_int_array' => [123, 456],
|
80
|
+
'test_optional_int' => 123,
|
81
|
+
'some_integer_map' => { 'int_1' => 1, 'int_2' => 2 },
|
82
|
+
'some_record' => { 'a_record_field' => 'field 1' },
|
83
|
+
'some_optional_record' => { 'a_record_field' => 'field 2' },
|
84
|
+
'some_record_array' => [
|
85
|
+
{ 'a_record_field' => 'field 3' },
|
86
|
+
{ 'a_record_field' => 'field 4' }
|
87
|
+
],
|
88
|
+
'some_record_map' => {
|
89
|
+
'record_1' => { 'a_record_field' => 'field 5' },
|
90
|
+
'record_2' => { 'a_record_field' => 'field 6' }
|
91
|
+
},
|
92
|
+
'some_enum_array' => %w(sym1 sym2)
|
93
|
+
}
|
94
|
+
|
95
|
+
expect(klass.as_json).to eq(payload_h)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should return a JSON string of the payload' do
|
99
|
+
s = '{"test_id":"test id","test_float":1.2,"test_string_array":["abc","def"],"test_int_array":[123,456],"test_optional_int":123,"some_integer_map":{"int_1":1,"int_2":2},"some_record":{"a_record_field":"field 1"},"some_optional_record":{"a_record_field":"field 2"},"some_record_array":[{"a_record_field":"field 3"},{"a_record_field":"field 4"}],"some_record_map":{"record_1":{"a_record_field":"field 5"},"record_2":{"a_record_field":"field 6"}},"some_enum_array":["sym1","sym2"]}'
|
100
|
+
expect(klass.to_json).to eq(s)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'defaults' do
|
105
|
+
it 'should set an_optional_int if it is not provided' do
|
106
|
+
payload_hash.delete(:an_optional_int)
|
107
|
+
klass = described_class.new(**payload_hash)
|
108
|
+
expect(klass.test_optional_int).to eq(123)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should set some_record if it is not provided' do
|
112
|
+
payload_hash.delete(:some_record)
|
113
|
+
klass = described_class.new(**payload_hash)
|
114
|
+
expect(klass.some_record).to eq(Schemas::ARecord.new(a_record_field: 'Test String'))
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should set some_record to nil' do
|
118
|
+
klass = described_class.new(**payload_hash.merge(some_record: nil))
|
119
|
+
expect(klass.some_record).to be_nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'getters and setters' do
|
124
|
+
let(:klass) do
|
125
|
+
described_class.new(**payload_hash)
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'when getting attributes' do
|
129
|
+
it 'should get of values of primitive types' do
|
130
|
+
expect(klass.test_id).to eq('test id')
|
131
|
+
expect(klass.test_float).to eq(1.2)
|
132
|
+
expect(klass.test_string_array).to eq(%w(abc def))
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should get the value of some_record_array' do
|
136
|
+
some_record_array = klass.some_record_array
|
137
|
+
expect(some_record_array.first).to be_instance_of(Schemas::ARecord)
|
138
|
+
expect(some_record_array.first.a_record_field).to eq('field 3')
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should get the value of some_record_map' do
|
142
|
+
some_record_map = klass.some_record_map
|
143
|
+
expect(some_record_map['record_1']).to be_instance_of(Schemas::ARecord)
|
144
|
+
expect(some_record_map['record_1'].a_record_field).to eq('field 5')
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'should get the value of some_enum_array' do
|
148
|
+
some_enum_array = klass.some_enum_array
|
149
|
+
expect(some_enum_array.first).to be_instance_of(Schemas::AnEnum)
|
150
|
+
expect(some_enum_array.first.an_enum).to eq('sym1')
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should get the value of some_record' do
|
154
|
+
record = klass.some_record
|
155
|
+
expect(record).to be_instance_of(Schemas::ARecord)
|
156
|
+
expect(record.a_record_field).to eq('field 1')
|
157
|
+
expect(record.to_h).to eq({ 'a_record_field' => 'field 1' })
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should support Hash-style element access of values' do
|
161
|
+
expect(klass['test_id']).to eq('test id')
|
162
|
+
expect(klass['test_float']).to eq(1.2)
|
163
|
+
expect(klass['test_string_array']).to eq(%w(abc def))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'when setting attributes' do
|
168
|
+
it 'should modify the value of test_id' do
|
169
|
+
expect(klass.test_id).to eq('test id')
|
170
|
+
|
171
|
+
klass.test_id = 'something different'
|
172
|
+
expect(klass.test_id).to eq('something different')
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should modify the value of some_optional_record' do
|
176
|
+
expect(klass.some_optional_record).to eq(Schemas::ARecord.new(a_record_field: 'field 2'))
|
177
|
+
klass.some_optional_record = Schemas::ARecord.new(a_record_field: 'new field')
|
178
|
+
|
179
|
+
expect(klass.some_optional_record).to eq(Schemas::ARecord.new(a_record_field: 'new field'))
|
180
|
+
expect(klass.some_optional_record.as_json).to eq({ 'a_record_field' => 'new field' })
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should accept a hash object inner records' do
|
184
|
+
klass.some_optional_record = { a_record_field: 'new field' }
|
185
|
+
expect(klass.some_optional_record).to eq(Schemas::ARecord.new(a_record_field: 'new field'))
|
186
|
+
expect(klass.some_optional_record.as_json).to eq({ 'a_record_field' => 'new field' })
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should modify the value of some_enum_array' do
|
190
|
+
klass.some_enum_array.first.an_enum = 'new_sym'
|
191
|
+
expect(klass.some_enum_array.first).to eq(Schemas::AnEnum.new('new_sym'))
|
192
|
+
|
193
|
+
klass.some_enum_array.second.an_enum = Schemas::AnEnum.new('other_sym')
|
194
|
+
expect(klass.some_enum_array.second.an_enum).to eq('other_sym')
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'should modify the value of some_record_map' do
|
198
|
+
klass.some_record_map['record_1'].a_record_field = 'new field'
|
199
|
+
expect(klass.some_record_map['record_1']).to eq(Schemas::ARecord.new(a_record_field: 'new field'))
|
200
|
+
|
201
|
+
klass.some_record_map['record_2'] = Schemas::ARecord.new(a_record_field: 'other field')
|
202
|
+
expect(klass.some_record_map['record_2']).to eq(Schemas::ARecord.new(a_record_field: 'other field'))
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'generators/deimos/schema_class_generator'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
RSpec.describe Deimos::Generators::SchemaClassGenerator do
|
7
|
+
let(:schema_class_path) { 'app/lib/schema_classes/com/my-namespace' }
|
8
|
+
let(:expected_files) { Dir['spec/schema_classes/*.rb'] }
|
9
|
+
let(:files) { Dir["#{schema_class_path}/*.rb"] }
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
Deimos.config.reset!
|
13
|
+
Deimos.configure do
|
14
|
+
schema.path 'spec/schemas/'
|
15
|
+
schema.generated_class_path 'app/lib/schema_classes'
|
16
|
+
schema.backend :avro_local
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:each) do
|
21
|
+
FileUtils.rm_rf(schema_class_path) if File.exist?(schema_class_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with a Consumers Schema' do
|
25
|
+
before(:each) do
|
26
|
+
Deimos.configure do
|
27
|
+
consumer do
|
28
|
+
class_name 'ConsumerTest::MyConsumer'
|
29
|
+
topic 'MyTopic'
|
30
|
+
schema 'Generated'
|
31
|
+
namespace 'com.my-namespace'
|
32
|
+
key_config field: :a_string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
described_class.start
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should generate the correct number of classes' do
|
39
|
+
expect(files.length).to eq(1)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should generate a schema class for generated' do
|
43
|
+
generated_path = files.select { |f| f =~ /generated/ }.first
|
44
|
+
expected_path = expected_files.select { |f| f =~ /generated/ }.first
|
45
|
+
|
46
|
+
expect(File.read(generated_path)).to eq(File.read(expected_path))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with a Consumers Schema with Complex types' do
|
51
|
+
before(:each) do
|
52
|
+
Deimos.configure do
|
53
|
+
consumer do
|
54
|
+
class_name 'ConsumerTest::MyConsumer'
|
55
|
+
topic 'MyTopic'
|
56
|
+
schema 'MySchemaWithComplexTypes'
|
57
|
+
namespace 'com.my-namespace'
|
58
|
+
key_config field: :a_string
|
59
|
+
end
|
60
|
+
end
|
61
|
+
described_class.start
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should generate the correct number of classes' do
|
65
|
+
expect(files.length).to eq(1)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should generate a schema class for my_schema_with_complex_types' do
|
69
|
+
generated_path = files.select { |f| f =~ /my_schema_with_complex_types/ }.first
|
70
|
+
expected_path = expected_files.select { |f| f =~ /my_schema_with_complex_types/ }.first
|
71
|
+
|
72
|
+
expect(File.read(generated_path)).to eq(File.read(expected_path))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with a Producers Schema and a Key' do
|
77
|
+
before(:each) do
|
78
|
+
Deimos.configure do
|
79
|
+
producer do
|
80
|
+
class_name 'ConsumerTest::MyConsumer'
|
81
|
+
topic 'MyTopic'
|
82
|
+
schema 'MySchema'
|
83
|
+
namespace 'com.my-namespace'
|
84
|
+
key_config schema: 'MySchema-key'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
described_class.start
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should generate the correct number of classes' do
|
91
|
+
expect(files.length).to eq(2)
|
92
|
+
end
|
93
|
+
|
94
|
+
%w(my_schema my_schema_key).each do |klass|
|
95
|
+
it "should generate a schema class for #{klass}" do
|
96
|
+
generated_path = files.select { |f| f =~ /#{klass}\.rb/ }.first
|
97
|
+
expected_path = expected_files.select { |f| f =~ /#{klass}\.rb/ }.first
|
98
|
+
|
99
|
+
expect(File.read(generated_path)).to eq(File.read(expected_path))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'with a Consumers Nested Schema' do
|
105
|
+
before(:each) do
|
106
|
+
Deimos.configure do
|
107
|
+
consumer do
|
108
|
+
class_name 'ConsumerTest::MyConsumer'
|
109
|
+
topic 'MyTopic'
|
110
|
+
schema 'MyNestedSchema'
|
111
|
+
namespace 'com.my-namespace'
|
112
|
+
key_config field: :test_id
|
113
|
+
end
|
114
|
+
end
|
115
|
+
described_class.start
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should generate the correct number of classes' do
|
119
|
+
expect(files.length).to eq(1)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should generate a schema class for my_nested_schema' do
|
123
|
+
generated_path = files.select { |f| f =~ /my_nested_schema/ }.first
|
124
|
+
expected_path = expected_files.select { |f| f =~ /my_nested_schema/ }.first
|
125
|
+
|
126
|
+
expect(File.read(generated_path)).to eq(File.read(expected_path))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'with a mix of Consumer and Producer Schemas' do
|
131
|
+
before(:each) do
|
132
|
+
Deimos.configure do
|
133
|
+
consumer do
|
134
|
+
class_name 'ConsumerTest::MyConsumer'
|
135
|
+
topic 'MyTopic'
|
136
|
+
schema 'Generated'
|
137
|
+
namespace 'com.my-namespace'
|
138
|
+
key_config field: :a_string
|
139
|
+
end
|
140
|
+
|
141
|
+
producer do
|
142
|
+
class_name 'ConsumerTest::MyConsumer'
|
143
|
+
topic 'MyTopic'
|
144
|
+
schema 'MySchema'
|
145
|
+
namespace 'com.my-namespace'
|
146
|
+
key_config schema: 'MySchema-key'
|
147
|
+
end
|
148
|
+
|
149
|
+
producer do
|
150
|
+
class_name 'ConsumerTest::MyConsumer'
|
151
|
+
topic 'MyTopic'
|
152
|
+
schema 'MyNestedSchema'
|
153
|
+
namespace 'com.my-namespace'
|
154
|
+
key_config field: :test_id
|
155
|
+
end
|
156
|
+
end
|
157
|
+
described_class.start
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should generate the correct number of classes' do
|
161
|
+
expect(files.length).to eq(4)
|
162
|
+
end
|
163
|
+
|
164
|
+
%w(generated my_schema my_schema_key my_nested_schema).each do |klass|
|
165
|
+
it "should generate a schema class for #{klass}" do
|
166
|
+
generated_path = files.select { |f| f =~ /#{klass}\.rb/ }.first
|
167
|
+
expected_path = expected_files.select { |f| f =~ /#{klass}\.rb/ }.first
|
168
|
+
|
169
|
+
expect(File.read(generated_path)).to eq(File.read(expected_path))
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'with non-avro schema backends' do
|
175
|
+
before(:each) do
|
176
|
+
Deimos.config.schema.backend :mock
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should fail to start schema class generation' do
|
180
|
+
expect {
|
181
|
+
described_class.start
|
182
|
+
}.to raise_error(message='Schema Class Generation requires an Avro-based Schema Backend')
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
data/spec/producer_spec.rb
CHANGED
@@ -390,6 +390,116 @@ module ProducerTest
|
|
390
390
|
}, '456', '4561')
|
391
391
|
end
|
392
392
|
|
393
|
+
context 'with Schema Class payloads' do
|
394
|
+
it 'should fail on invalid message with error handler' do
|
395
|
+
subscriber = Deimos.subscribe('produce') do |event|
|
396
|
+
expect(event.payload[:payloads]).to eq([{ 'invalid' => 'key' }])
|
397
|
+
end
|
398
|
+
expect(MyProducer.encoder).to receive(:validate).and_raise('OH NOES')
|
399
|
+
expect {
|
400
|
+
MyProducer.publish(Schemas::MySchema.new(test_id: 'foo', some_int: 'invalid'))
|
401
|
+
}.to raise_error('OH NOES')
|
402
|
+
Deimos.unsubscribe(subscriber)
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'should produce a message' do
|
406
|
+
expect(described_class).to receive(:produce_batch).once.with(
|
407
|
+
Deimos::Backends::Test,
|
408
|
+
[
|
409
|
+
Deimos::Message.new({ 'test_id' => 'foo', 'some_int' => 123 },
|
410
|
+
MyProducer,
|
411
|
+
topic: 'my-topic',
|
412
|
+
partition_key: 'foo',
|
413
|
+
key: 'foo'),
|
414
|
+
Deimos::Message.new({ 'test_id' => 'bar', 'some_int' => 124 },
|
415
|
+
MyProducer,
|
416
|
+
topic: 'my-topic',
|
417
|
+
partition_key: 'bar',
|
418
|
+
key: 'bar')
|
419
|
+
]
|
420
|
+
).and_call_original
|
421
|
+
|
422
|
+
MyProducer.publish_list(
|
423
|
+
[Schemas::MySchema.new(test_id: 'foo', some_int: 123),
|
424
|
+
Schemas::MySchema.new(test_id: 'bar', some_int: 124)]
|
425
|
+
)
|
426
|
+
expect('my-topic').to have_sent('test_id' => 'foo', 'some_int' => 123)
|
427
|
+
expect('your-topic').not_to have_sent('test_id' => 'foo', 'some_int' => 123)
|
428
|
+
expect('my-topic').not_to have_sent('test_id' => 'foo2', 'some_int' => 123)
|
429
|
+
end
|
430
|
+
|
431
|
+
it 'should not publish if publish disabled' do
|
432
|
+
expect(described_class).not_to receive(:produce_batch)
|
433
|
+
Deimos.configure { |c| c.producers.disabled = true }
|
434
|
+
MyProducer.publish_list(
|
435
|
+
[Schemas::MySchema.new(test_id: 'foo', some_int: 123),
|
436
|
+
Schemas::MySchema.new(test_id: 'bar', some_int: 124)]
|
437
|
+
)
|
438
|
+
expect(MyProducer.topic).not_to have_sent(anything)
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'should encode the key' do
|
442
|
+
Deimos.configure { |c| c.producers.topic_prefix = nil }
|
443
|
+
expect(MyProducer.encoder).to receive(:encode_key).with('test_id', 'foo', topic: 'my-topic-key')
|
444
|
+
expect(MyProducer.encoder).to receive(:encode_key).with('test_id', 'bar', topic: 'my-topic-key')
|
445
|
+
expect(MyProducer.encoder).to receive(:encode).with({
|
446
|
+
'test_id' => 'foo',
|
447
|
+
'some_int' => 123
|
448
|
+
}, { topic: 'my-topic-value' })
|
449
|
+
expect(MyProducer.encoder).to receive(:encode).with({
|
450
|
+
'test_id' => 'bar',
|
451
|
+
'some_int' => 124
|
452
|
+
}, { topic: 'my-topic-value' })
|
453
|
+
|
454
|
+
MyProducer.publish_list(
|
455
|
+
[Schemas::MySchema.new(test_id: 'foo', some_int: 123),
|
456
|
+
Schemas::MySchema.new(test_id: 'bar', some_int: 124)]
|
457
|
+
)
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'should encode with a schema' do
|
461
|
+
expect(MySchemaProducer.key_encoder).to receive(:encode).with({ 'test_id' => 'foo_key' },
|
462
|
+
{ topic: 'my-topic2-key' })
|
463
|
+
expect(MySchemaProducer.key_encoder).to receive(:encode).with({ 'test_id' => 'bar_key' },
|
464
|
+
{ topic: 'my-topic2-key' })
|
465
|
+
|
466
|
+
MySchemaProducer.publish_list(
|
467
|
+
[Schemas::MySchema.new(test_id: 'foo', some_int: 123, payload_key: { 'test_id' => 'foo_key' }),
|
468
|
+
Schemas::MySchema.new(test_id: 'bar', some_int: 124, payload_key: { 'test_id' => 'bar_key' })]
|
469
|
+
)
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'should properly encode and coerce values with a nested record' do
|
473
|
+
expect(MyNestedSchemaProducer.encoder).to receive(:encode_key).with('test_id', 'foo', topic: 'my-topic-key')
|
474
|
+
MyNestedSchemaProducer.publish(
|
475
|
+
Schemas::MyNestedSchema.new(
|
476
|
+
test_id: 'foo',
|
477
|
+
test_float: BigDecimal('123.456'),
|
478
|
+
test_array: ['1'],
|
479
|
+
some_nested_record: Schemas::MyNestedRecord.new(
|
480
|
+
some_int: 123,
|
481
|
+
some_float: BigDecimal('456.789'),
|
482
|
+
some_string: '123',
|
483
|
+
some_optional_int: nil
|
484
|
+
),
|
485
|
+
some_optional_record: nil
|
486
|
+
)
|
487
|
+
)
|
488
|
+
expect(MyNestedSchemaProducer.topic).to have_sent(
|
489
|
+
'test_id' => 'foo',
|
490
|
+
'test_float' => 123.456,
|
491
|
+
'test_array' => ['1'],
|
492
|
+
'some_nested_record' => {
|
493
|
+
'some_int' => 123,
|
494
|
+
'some_float' => 456.789,
|
495
|
+
'some_string' => '123',
|
496
|
+
'some_optional_int' => nil
|
497
|
+
},
|
498
|
+
'some_optional_record' => nil
|
499
|
+
)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
393
503
|
describe 'disabling' do
|
394
504
|
it 'should disable globally' do
|
395
505
|
Deimos.disable_producers do
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is autogenerated by Deimos, Do NOT modify
|
4
|
+
module Schemas
|
5
|
+
### Secondary Schema Classes ###
|
6
|
+
# Autogenerated Schema for Enum at com.my-namespace.AnEnum
|
7
|
+
class AnEnum < Deimos::SchemaClass::Enum
|
8
|
+
# @return ['sym1', 'sym2']
|
9
|
+
attr_accessor :an_enum
|
10
|
+
|
11
|
+
# :nodoc:
|
12
|
+
def initialize(an_enum)
|
13
|
+
super
|
14
|
+
self.an_enum = an_enum
|
15
|
+
end
|
16
|
+
|
17
|
+
# @override
|
18
|
+
def symbols
|
19
|
+
%w(sym1 sym2)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @override
|
23
|
+
def to_h
|
24
|
+
@an_enum
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Autogenerated Schema for Record at com.my-namespace.ARecord
|
29
|
+
class ARecord < Deimos::SchemaClass::Record
|
30
|
+
### Attribute Accessors ###
|
31
|
+
# @param value [String]
|
32
|
+
attr_accessor :a_record_field
|
33
|
+
|
34
|
+
# @override
|
35
|
+
def initialize(a_record_field: nil)
|
36
|
+
super
|
37
|
+
self.a_record_field = a_record_field
|
38
|
+
end
|
39
|
+
|
40
|
+
# @override
|
41
|
+
def schema
|
42
|
+
'ARecord'
|
43
|
+
end
|
44
|
+
|
45
|
+
# @override
|
46
|
+
def namespace
|
47
|
+
'com.my-namespace'
|
48
|
+
end
|
49
|
+
|
50
|
+
# @override
|
51
|
+
def to_h
|
52
|
+
{
|
53
|
+
'a_record_field' => @a_record_field
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
### Primary Schema Class ###
|
59
|
+
# Autogenerated Schema for Record at com.my-namespace.Generated
|
60
|
+
class Generated < Deimos::SchemaClass::Record
|
61
|
+
### Attribute Readers ###
|
62
|
+
# @return [AnEnum]
|
63
|
+
attr_reader :an_enum
|
64
|
+
# @return [ARecord]
|
65
|
+
attr_reader :a_record
|
66
|
+
|
67
|
+
### Attribute Accessors ###
|
68
|
+
# @param value [String]
|
69
|
+
attr_accessor :a_string
|
70
|
+
# @param value [Integer]
|
71
|
+
attr_accessor :a_int
|
72
|
+
# @param value [Integer]
|
73
|
+
attr_accessor :a_long
|
74
|
+
# @param value [Float]
|
75
|
+
attr_accessor :a_float
|
76
|
+
# @param value [Float]
|
77
|
+
attr_accessor :a_double
|
78
|
+
# @param value [nil, Integer]
|
79
|
+
attr_accessor :an_optional_int
|
80
|
+
# @param values [Array<Integer>]
|
81
|
+
attr_accessor :an_array
|
82
|
+
# @param values [Hash<String, String>]
|
83
|
+
attr_accessor :a_map
|
84
|
+
# @param value [String]
|
85
|
+
attr_accessor :timestamp
|
86
|
+
# @param value [String]
|
87
|
+
attr_accessor :message_id
|
88
|
+
|
89
|
+
### Attribute Writers ###
|
90
|
+
# @param value [AnEnum]
|
91
|
+
def an_enum=(value)
|
92
|
+
@an_enum = AnEnum.initialize_from_value(value)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param value [ARecord]
|
96
|
+
def a_record=(value)
|
97
|
+
@a_record = ARecord.initialize_from_value(value)
|
98
|
+
end
|
99
|
+
|
100
|
+
# @override
|
101
|
+
def initialize(a_string: nil,
|
102
|
+
a_int: nil,
|
103
|
+
a_long: nil,
|
104
|
+
a_float: nil,
|
105
|
+
a_double: nil,
|
106
|
+
an_optional_int: nil,
|
107
|
+
an_enum: nil,
|
108
|
+
an_array: nil,
|
109
|
+
a_map: nil,
|
110
|
+
timestamp: nil,
|
111
|
+
message_id: nil,
|
112
|
+
a_record: nil)
|
113
|
+
super
|
114
|
+
self.a_string = a_string
|
115
|
+
self.a_int = a_int
|
116
|
+
self.a_long = a_long
|
117
|
+
self.a_float = a_float
|
118
|
+
self.a_double = a_double
|
119
|
+
self.an_optional_int = an_optional_int
|
120
|
+
self.an_enum = an_enum
|
121
|
+
self.an_array = an_array
|
122
|
+
self.a_map = a_map
|
123
|
+
self.timestamp = timestamp
|
124
|
+
self.message_id = message_id
|
125
|
+
self.a_record = a_record
|
126
|
+
end
|
127
|
+
|
128
|
+
# @override
|
129
|
+
def schema
|
130
|
+
'Generated'
|
131
|
+
end
|
132
|
+
|
133
|
+
# @override
|
134
|
+
def namespace
|
135
|
+
'com.my-namespace'
|
136
|
+
end
|
137
|
+
|
138
|
+
# @override
|
139
|
+
def to_h
|
140
|
+
{
|
141
|
+
'a_string' => @a_string,
|
142
|
+
'a_int' => @a_int,
|
143
|
+
'a_long' => @a_long,
|
144
|
+
'a_float' => @a_float,
|
145
|
+
'a_double' => @a_double,
|
146
|
+
'an_optional_int' => @an_optional_int,
|
147
|
+
'an_enum' => @an_enum&.to_h,
|
148
|
+
'an_array' => @an_array,
|
149
|
+
'a_map' => @a_map,
|
150
|
+
'timestamp' => @timestamp,
|
151
|
+
'message_id' => @message_id,
|
152
|
+
'a_record' => @a_record&.to_h
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|