deimos-ruby 1.4.0.pre.beta7 → 1.5.0.pre.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +13 -0
  4. data/Gemfile.lock +140 -58
  5. data/README.md +38 -11
  6. data/Rakefile +2 -2
  7. data/deimos-ruby.gemspec +3 -2
  8. data/docs/CONFIGURATION.md +1 -0
  9. data/docs/DATABASE_BACKEND.md +1 -1
  10. data/lib/deimos/active_record_consumer.rb +11 -12
  11. data/lib/deimos/active_record_producer.rb +2 -2
  12. data/lib/deimos/backends/base.rb +32 -0
  13. data/lib/deimos/backends/db.rb +6 -1
  14. data/lib/deimos/backends/kafka.rb +1 -1
  15. data/lib/deimos/backends/kafka_async.rb +1 -1
  16. data/lib/deimos/backends/test.rb +20 -0
  17. data/lib/deimos/base_consumer.rb +7 -7
  18. data/lib/deimos/batch_consumer.rb +0 -1
  19. data/lib/deimos/config/configuration.rb +4 -0
  20. data/lib/deimos/consumer.rb +0 -2
  21. data/lib/deimos/kafka_source.rb +1 -1
  22. data/lib/deimos/kafka_topic_info.rb +1 -1
  23. data/lib/deimos/message.rb +7 -7
  24. data/lib/deimos/producer.rb +10 -12
  25. data/lib/deimos/schema_backends/avro_base.rb +108 -0
  26. data/lib/deimos/schema_backends/avro_local.rb +30 -0
  27. data/lib/deimos/{schema_coercer.rb → schema_backends/avro_schema_coercer.rb} +39 -51
  28. data/lib/deimos/schema_backends/avro_schema_registry.rb +34 -0
  29. data/lib/deimos/schema_backends/avro_validation.rb +21 -0
  30. data/lib/deimos/schema_backends/base.rb +130 -0
  31. data/lib/deimos/schema_backends/mock.rb +42 -0
  32. data/lib/deimos/test_helpers.rb +42 -168
  33. data/lib/deimos/utils/db_producer.rb +5 -0
  34. data/lib/deimos/version.rb +1 -1
  35. data/lib/deimos.rb +22 -6
  36. data/lib/tasks/deimos.rake +1 -1
  37. data/spec/active_record_consumer_spec.rb +7 -0
  38. data/spec/{publish_backend_spec.rb → backends/base_spec.rb} +1 -1
  39. data/spec/backends/db_spec.rb +5 -0
  40. data/spec/batch_consumer_spec.rb +0 -8
  41. data/spec/config/configuration_spec.rb +20 -20
  42. data/spec/consumer_spec.rb +0 -1
  43. data/spec/deimos_spec.rb +0 -4
  44. data/spec/kafka_source_spec.rb +8 -0
  45. data/spec/producer_spec.rb +23 -37
  46. data/spec/rake_spec.rb +19 -0
  47. data/spec/schema_backends/avro_base_shared.rb +174 -0
  48. data/spec/schema_backends/avro_local_spec.rb +32 -0
  49. data/spec/schema_backends/avro_schema_registry_spec.rb +32 -0
  50. data/spec/schema_backends/avro_validation_spec.rb +24 -0
  51. data/spec/schema_backends/base_spec.rb +29 -0
  52. data/spec/spec_helper.rb +6 -0
  53. data/spec/utils/db_producer_spec.rb +10 -0
  54. metadata +56 -33
  55. data/lib/deimos/avro_data_coder.rb +0 -89
  56. data/lib/deimos/avro_data_decoder.rb +0 -36
  57. data/lib/deimos/avro_data_encoder.rb +0 -51
  58. data/lib/deimos/monkey_patches/schema_store.rb +0 -19
  59. data/lib/deimos/publish_backend.rb +0 -30
  60. data/spec/avro_data_decoder_spec.rb +0 -18
  61. data/spec/avro_data_encoder_spec.rb +0 -37
  62. data/spec/updateable_schema_store_spec.rb +0 -36
@@ -6,20 +6,20 @@ module Deimos
6
6
  include SharedConfig
7
7
 
8
8
  class << self
9
- # @return [AvroDataEncoder]
9
+ # @return [Deimos::SchemaBackends::Base]
10
10
  def decoder
11
- @decoder ||= AvroDataDecoder.new(schema: config[:schema],
12
- namespace: config[:namespace])
11
+ @decoder ||= Deimos.schema_backend(schema: config[:schema],
12
+ namespace: config[:namespace])
13
13
  end
14
14
 
15
- # @return [AvroDataEncoder]
15
+ # @return [Deimos::SchemaBackends::Base]
16
16
  def key_decoder
17
- @key_decoder ||= AvroDataDecoder.new(schema: config[:key_schema],
18
- namespace: config[:namespace])
17
+ @key_decoder ||= Deimos.schema_backend(schema: config[:key_schema],
18
+ namespace: config[:namespace])
19
19
  end
20
20
  end
21
21
 
22
- # Helper method to decode an Avro-encoded key.
22
+ # Helper method to decode an encoded key.
23
23
  # @param key [String]
24
24
  # @return [Object] the decoded key.
25
25
  def decode_key(key)
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'deimos/avro_data_decoder'
4
3
  require 'deimos/base_consumer'
5
4
  require 'phobos/batch_handler'
6
5
 
@@ -251,6 +251,10 @@ module Deimos
251
251
  end
252
252
 
253
253
  setting :schema do
254
+
255
+ # Backend class to use when encoding/decoding messages.
256
+ setting :backend, :mock
257
+
254
258
  # URL of the Confluent schema registry.
255
259
  # @return [String]
256
260
  setting :registry_url, 'http://localhost:8081'
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'deimos/avro_data_decoder'
4
3
  require 'deimos/base_consumer'
5
4
  require 'deimos/shared_config'
6
5
  require 'phobos/handler'
7
6
  require 'active_support/all'
8
- require 'ddtrace'
9
7
 
10
8
  # Class to consume messages coming from the pipeline topic
11
9
  # Note: According to the docs, instances of your handler will be created
@@ -41,7 +41,7 @@ module Deimos
41
41
  def send_kafka_event_on_destroy
42
42
  return unless self.class.kafka_config[:delete]
43
43
 
44
- self.class.kafka_producers.each { |p| p.send_event(self.deletion_payload) }
44
+ self.class.kafka_producers.each { |p| p.publish_list([self.deletion_payload]) }
45
45
  end
46
46
 
47
47
  # Payload to send after we are destroyed.
@@ -14,7 +14,7 @@ module Deimos
14
14
  # Try to create it - it's fine if it already exists
15
15
  begin
16
16
  self.create(topic: topic)
17
- rescue ActiveRecord::RecordNotUnique # rubocop:disable Lint/HandleExceptions
17
+ rescue ActiveRecord::RecordNotUnique # rubocop:disable Lint/SuppressedException
18
18
  # continue on
19
19
  end
20
20
 
@@ -18,23 +18,23 @@ module Deimos
18
18
 
19
19
  # Add message_id and timestamp default values if they are in the
20
20
  # schema and don't already have values.
21
- # @param schema [Avro::Schema]
22
- def add_fields(schema)
21
+ # @param fields [Array<String>] existing name fields in the schema.
22
+ def add_fields(fields)
23
23
  return if @payload.except(:payload_key, :partition_key).blank?
24
24
 
25
- if schema.fields.any? { |f| f.name == 'message_id' }
25
+ if fields.include?('message_id')
26
26
  @payload['message_id'] ||= SecureRandom.uuid
27
27
  end
28
- if schema.fields.any? { |f| f.name == 'timestamp' }
28
+ if fields.include?('timestamp')
29
29
  @payload['timestamp'] ||= Time.now.in_time_zone.to_s
30
30
  end
31
31
  end
32
32
 
33
- # @param schema [Avro::Schema]
34
- def coerce_fields(schema)
33
+ # @param encoder [Deimos::SchemaBackends::Base]
34
+ def coerce_fields(encoder)
35
35
  return if payload.nil?
36
36
 
37
- @payload = SchemaCoercer.new(schema).coerce(@payload)
37
+ @payload = encoder.coerce(@payload)
38
38
  end
39
39
 
40
40
  # @return [Hash]
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'deimos/avro_data_encoder'
4
3
  require 'deimos/message'
5
4
  require 'deimos/shared_config'
6
- require 'deimos/schema_coercer'
7
5
  require 'phobos/producer'
8
6
  require 'active_support/notifications'
9
7
 
@@ -143,23 +141,23 @@ module Deimos
143
141
  backend.publish(producer_class: self, messages: batch)
144
142
  end
145
143
 
146
- # @return [AvroDataEncoder]
144
+ # @return [Deimos::SchemaBackends::Base]
147
145
  def encoder
148
- @encoder ||= AvroDataEncoder.new(schema: config[:schema],
149
- namespace: config[:namespace])
146
+ @encoder ||= Deimos.schema_backend(schema: config[:schema],
147
+ namespace: config[:namespace])
150
148
  end
151
149
 
152
- # @return [AvroDataEncoder]
150
+ # @return [Deimos::SchemaBackends::Base]
153
151
  def key_encoder
154
- @key_encoder ||= AvroDataEncoder.new(schema: config[:key_schema],
155
- namespace: config[:namespace])
152
+ @key_encoder ||= Deimos.schema_backend(schema: config[:key_schema],
153
+ namespace: config[:namespace])
156
154
  end
157
155
 
158
156
  # Override this in active record producers to add
159
157
  # non-schema fields to check for updates
160
158
  # @return [Array<String>] fields to check for updates
161
159
  def watched_attributes
162
- self.encoder.avro_schema.fields.map(&:name)
160
+ self.encoder.schema_fields.map(&:name)
163
161
  end
164
162
 
165
163
  private
@@ -169,13 +167,13 @@ module Deimos
169
167
  # this violates the Law of Demeter but it has to happen in a very
170
168
  # specific order and requires a bunch of methods on the producer
171
169
  # to work correctly.
172
- message.add_fields(encoder.avro_schema)
170
+ message.add_fields(encoder.schema_fields.map(&:name))
173
171
  message.partition_key = self.partition_key(message.payload)
174
172
  message.key = _retrieve_key(message.payload)
175
173
  # need to do this before _coerce_fields because that might result
176
174
  # in an empty payload which is an *error* whereas this is intended.
177
175
  message.payload = nil if message.payload.blank?
178
- message.coerce_fields(encoder.avro_schema)
176
+ message.coerce_fields(encoder)
179
177
  message.encoded_key = _encode_key(message.key)
180
178
  message.topic = self.topic
181
179
  message.encoded_payload = if message.payload.nil?
@@ -200,7 +198,7 @@ module Deimos
200
198
  end
201
199
 
202
200
  if config[:key_field]
203
- encoder.encode_key(config[:key_field], key, "#{config[:topic]}-key")
201
+ encoder.encode_key(config[:key_field], key, topic: "#{config[:topic]}-key")
204
202
  elsif config[:key_schema]
205
203
  key_encoder.encode(key, topic: "#{config[:topic]}-key")
206
204
  else
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require 'avro'
5
+ require 'avro_turf'
6
+ require 'avro_turf/mutable_schema_store'
7
+ require_relative 'avro_schema_coercer'
8
+
9
+ module Deimos
10
+ module SchemaBackends
11
+ # Encode / decode using Avro, either locally or via schema registry.
12
+ class AvroBase < Base
13
+ attr_accessor :schema_store
14
+
15
+ # @override
16
+ def initialize(schema:, namespace:)
17
+ super(schema: schema, namespace: namespace)
18
+ @schema_store = AvroTurf::MutableSchemaStore.new(path: Deimos.config.schema.path)
19
+ end
20
+
21
+ # @override
22
+ def encode_key(key_id, key, topic: nil)
23
+ @key_schema ||= _generate_key_schema(key_id)
24
+ field_name = _field_name_from_schema(@key_schema)
25
+ payload = { field_name => key }
26
+ encode(payload, schema: @key_schema['name'], topic: topic)
27
+ end
28
+
29
+ # @override
30
+ def decode_key(payload, key_id)
31
+ @key_schema ||= _generate_key_schema(key_id)
32
+ field_name = _field_name_from_schema(@key_schema)
33
+ decode(payload, schema: @key_schema['name'])[field_name]
34
+ end
35
+
36
+ # @override
37
+ def coerce_field(field, value)
38
+ AvroSchemaCoercer.new(avro_schema).coerce_type(field.type, value)
39
+ end
40
+
41
+ # @override
42
+ def schema_fields
43
+ avro_schema.fields.map { |field| SchemaField.new(field.name, field.type) }
44
+ end
45
+
46
+ # @override
47
+ def validate(payload, schema:)
48
+ Avro::SchemaValidator.validate!(avro_schema(schema), payload,
49
+ recursive: true,
50
+ fail_on_extra_fields: true)
51
+ end
52
+
53
+ # @override
54
+ def self.mock_backend
55
+ :avro_validation
56
+ end
57
+
58
+ private
59
+
60
+ # @param schema [String]
61
+ # @return [Avro::Schema]
62
+ def avro_schema(schema=nil)
63
+ schema ||= @schema
64
+ @schema_store.find(schema, @namespace)
65
+ end
66
+
67
+ # Generate a key schema from the given value schema and key ID. This
68
+ # is used when encoding or decoding keys from an existing value schema.
69
+ # @param key_id [Symbol]
70
+ # @return [Hash]
71
+ def _generate_key_schema(key_id)
72
+ key_field = avro_schema.fields.find { |f| f.name == key_id.to_s }
73
+ name = _key_schema_name(@schema)
74
+ key_schema = {
75
+ 'type' => 'record',
76
+ 'name' => name,
77
+ 'namespace' => @namespace,
78
+ 'doc' => "Key for #{@namespace}.#{@schema} - autogenerated by Deimos",
79
+ 'fields' => [
80
+ {
81
+ 'name' => key_id,
82
+ 'type' => key_field.type.type_sym.to_s
83
+ }
84
+ ]
85
+ }
86
+ @schema_store.add_schema(key_schema)
87
+ key_schema
88
+ end
89
+
90
+ # @param value_schema [Hash]
91
+ # @return [String]
92
+ def _field_name_from_schema(value_schema)
93
+ raise "Schema #{@schema} not found!" if value_schema.nil?
94
+ if value_schema['fields'].nil? || value_schema['fields'].empty?
95
+ raise "Schema #{@schema} has no fields!"
96
+ end
97
+
98
+ value_schema['fields'][0]['name']
99
+ end
100
+
101
+ # @param schema [String]
102
+ # @return [String]
103
+ def _key_schema_name(schema)
104
+ "#{schema}_key"
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'avro_base'
4
+
5
+ module Deimos
6
+ module SchemaBackends
7
+ # Encode / decode using local Avro encoding.
8
+ class AvroLocal < AvroBase
9
+ # @override
10
+ def decode_payload(payload, schema:)
11
+ avro_turf.decode(payload, schema_name: schema, namespace: @namespace)
12
+ end
13
+
14
+ # @override
15
+ def encode_payload(payload, schema: nil, topic: nil)
16
+ avro_turf.encode(payload, schema_name: schema, namespace: @namespace)
17
+ end
18
+
19
+ private
20
+
21
+ # @return [AvroTurf]
22
+ def avro_turf
23
+ @avro_turf ||= AvroTurf.new(
24
+ schemas_path: Deimos.config.schema.path,
25
+ schema_store: @schema_store
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,66 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/time'
4
+
3
5
  module Deimos
4
6
  # Class to coerce values in a payload to match a schema.
5
- class SchemaCoercer
7
+ class AvroSchemaCoercer
6
8
  # @param schema [Avro::Schema]
7
9
  def initialize(schema)
8
10
  @schema = schema
9
11
  end
10
12
 
11
- # @param payload [Hash]
12
- # @return [HashWithIndifferentAccess]
13
- def coerce(payload)
14
- result = {}
15
- @schema.fields.each do |field|
16
- name = field.name
17
- next unless payload.key?(name)
18
-
19
- val = payload[name]
20
- result[name] = _coerce_type(field.type, val)
21
- end
22
- result.with_indifferent_access
23
- end
24
-
25
- private
26
-
27
- # @param val [String]
28
- # @return [Boolean]
29
- def _is_integer_string?(val)
30
- return false unless val.is_a?(String)
31
-
32
- begin
33
- true if Integer(val)
34
- rescue StandardError
35
- false
36
- end
37
- end
38
-
39
- # @param val [String]
40
- # @return [Boolean]
41
- def _is_float_string?(val)
42
- return false unless val.is_a?(String)
43
-
44
- begin
45
- true if Float(val)
46
- rescue StandardError
47
- false
48
- end
49
- end
50
-
51
- # @param val [Object]
52
- # @return [Boolean]
53
- def _is_to_s_defined?(val)
54
- return false if val.nil?
55
-
56
- Object.instance_method(:to_s).bind(val).call != val.to_s
57
- end
58
-
59
13
  # @param type [Symbol]
60
14
  # @param val [Object]
61
15
  # @return [Object]
62
- def _coerce_type(type, val)
63
- int_classes = [Time, DateTime, ActiveSupport::TimeWithZone]
16
+ def coerce_type(type, val)
17
+ int_classes = [Time, ActiveSupport::TimeWithZone]
64
18
  field_type = type.type.to_sym
65
19
  if field_type == :union
66
20
  union_types = type.schemas.map { |s| s.type.to_sym }
@@ -104,5 +58,39 @@ module Deimos
104
58
  val
105
59
  end
106
60
  end
61
+
62
+ private
63
+
64
+ # @param val [String]
65
+ # @return [Boolean]
66
+ def _is_integer_string?(val)
67
+ return false unless val.is_a?(String)
68
+
69
+ begin
70
+ true if Integer(val)
71
+ rescue StandardError
72
+ false
73
+ end
74
+ end
75
+
76
+ # @param val [String]
77
+ # @return [Boolean]
78
+ def _is_float_string?(val)
79
+ return false unless val.is_a?(String)
80
+
81
+ begin
82
+ true if Float(val)
83
+ rescue StandardError
84
+ false
85
+ end
86
+ end
87
+
88
+ # @param val [Object]
89
+ # @return [Boolean]
90
+ def _is_to_s_defined?(val)
91
+ return false if val.nil?
92
+
93
+ Object.instance_method(:to_s).bind(val).call != val.to_s
94
+ end
107
95
  end
108
96
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'avro_base'
4
+ require_relative 'avro_validation'
5
+ require 'avro_turf/messaging'
6
+
7
+ module Deimos
8
+ module SchemaBackends
9
+ # Encode / decode using the Avro schema registry.
10
+ class AvroSchemaRegistry < AvroBase
11
+ # @override
12
+ def decode_payload(payload, schema:)
13
+ avro_turf_messaging.decode(payload, schema_name: schema)
14
+ end
15
+
16
+ # @override
17
+ def encode_payload(payload, schema: nil, topic: nil)
18
+ avro_turf_messaging.encode(payload, schema_name: schema, subject: topic)
19
+ end
20
+
21
+ private
22
+
23
+ # @return [AvroTurf::Messaging]
24
+ def avro_turf_messaging
25
+ @avro_turf_messaging ||= AvroTurf::Messaging.new(
26
+ schema_store: @schema_store,
27
+ registry_url: Deimos.config.schema.registry_url,
28
+ schemas_path: Deimos.config.schema.path,
29
+ namespace: @namespace
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'avro_base'
4
+
5
+ module Deimos
6
+ module SchemaBackends
7
+ # Leave Ruby hashes as is but validate them against the schema.
8
+ # Useful for unit tests.
9
+ class AvroValidation < AvroBase
10
+ # @override
11
+ def decode_payload(payload, schema: nil)
12
+ payload.with_indifferent_access
13
+ end
14
+
15
+ # @override
16
+ def encode_payload(payload, schema: nil, topic: nil)
17
+ payload.with_indifferent_access
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deimos
4
+ # Represents a field in the schema.
5
+ class SchemaField
6
+ attr_accessor :name, :type
7
+
8
+ # @param name [String]
9
+ # @param type [Object]
10
+ def initialize(name, type)
11
+ @name = name
12
+ @type = type
13
+ end
14
+ end
15
+
16
+ module SchemaBackends
17
+ # Base class for encoding / decoding.
18
+ class Base
19
+ attr_accessor :schema, :namespace, :key_schema
20
+
21
+ # @param schema [String|Symbol]
22
+ # @param namespace [String]
23
+ def initialize(schema:, namespace: nil)
24
+ @schema = schema
25
+ @namespace = namespace
26
+ end
27
+
28
+ # Encode a payload with a schema. Public method.
29
+ # @param payload [Hash]
30
+ # @param schema [Symbol|String]
31
+ # @param topic [String]
32
+ # @return [String]
33
+ def encode(payload, schema: nil, topic: nil)
34
+ validate(payload, schema: schema || @schema)
35
+ encode_payload(payload, schema: schema || @schema, topic: topic)
36
+ end
37
+
38
+ # Decode a payload with a schema. Public method.
39
+ # @param payload [String]
40
+ # @param schema [Symbol|String]
41
+ # @return [Hash]
42
+ def decode(payload, schema: nil)
43
+ decode_payload(payload, schema: schema || @schema)
44
+ end
45
+
46
+ # Given a hash, coerce its types to our schema. To be defined by subclass.
47
+ # @param payload [Hash]
48
+ # @return [Hash]
49
+ def coerce(payload)
50
+ result = {}
51
+ self.schema_fields.each do |field|
52
+ name = field.name
53
+ next unless payload.key?(name)
54
+
55
+ val = payload[name]
56
+ result[name] = coerce_field(field, val)
57
+ end
58
+ result.with_indifferent_access
59
+ end
60
+
61
+ # Indicate a class which should act as a mocked version of this backend.
62
+ # This class should perform all validations but not actually do any
63
+ # encoding.
64
+ # Note that the "mock" version (e.g. avro_validation) should return
65
+ # its own symbol when this is called, since it may be called multiple
66
+ # times depending on the order of RSpec helpers.
67
+ # @return [Symbol]
68
+ def self.mock_backend
69
+ :mock
70
+ end
71
+
72
+ # Encode a payload. To be defined by subclass.
73
+ # @param payload [Hash]
74
+ # @param schema [Symbol|String]
75
+ # @param topic [String]
76
+ # @return [String]
77
+ def encode_payload(_payload, schema:, topic: nil)
78
+ raise NotImplementedError
79
+ end
80
+
81
+ # Decode a payload. To be defined by subclass.
82
+ # @param payload [String]
83
+ # @param schema [String|Symbol]
84
+ # @return [Hash]
85
+ def decode_payload(_payload, schema:)
86
+ raise NotImplementedError
87
+ end
88
+
89
+ # Validate that a payload matches the schema. To be defined by subclass.
90
+ # @param payload [Hash]
91
+ # @param schema [String|Symbol]
92
+ def validate(_payload, schema:)
93
+ raise NotImplementedError
94
+ end
95
+
96
+ # List of field names belonging to the schema. To be defined by subclass.
97
+ # @return [Array<SchemaField>]
98
+ def schema_fields
99
+ raise NotImplementedError
100
+ end
101
+
102
+ # Given a value and a field definition (as defined by whatever the
103
+ # underlying schema library is), coerce the given value to
104
+ # the given field type.
105
+ # @param field [SchemaField]
106
+ # @param value [Object]
107
+ # @return [Object]
108
+ def coerce_field(_field, _value)
109
+ raise NotImplementedError
110
+ end
111
+
112
+ # Encode a message key. To be defined by subclass.
113
+ # @param key [String|Hash] the value to use as the key.
114
+ # @param key_id [Symbol|String] the field name of the key.
115
+ # @param topic [String]
116
+ # @return [String]
117
+ def encode_key(_key, _key_id, topic: nil)
118
+ raise NotImplementedError
119
+ end
120
+
121
+ # Decode a message key. To be defined by subclass.
122
+ # @param payload [Hash] the message itself.
123
+ # @param key_id [Symbol|String] the field in the message to decode.
124
+ # @return [String]
125
+ def decode_key(_payload, _key_id)
126
+ raise NotImplementedError
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deimos
4
+ module SchemaBackends
5
+ # Mock implementation of a schema backend that does no encoding or validation.
6
+ class Mock < Base
7
+ # @override
8
+ def decode_payload(payload, schema:)
9
+ payload.is_a?(String) ? 'payload-decoded' : payload.map { |k, v| [k, "decoded-#{v}"] }
10
+ end
11
+
12
+ # @override
13
+ def encode_payload(payload, schema:, topic: nil)
14
+ payload.is_a?(String) ? 'payload-encoded' : payload.map { |k, v| [k, "encoded-#{v}"] }
15
+ end
16
+
17
+ # @override
18
+ def validate(_payload, schema:)
19
+ end
20
+
21
+ # @override
22
+ def schema_fields
23
+ []
24
+ end
25
+
26
+ # @override
27
+ def coerce_field(_type, value)
28
+ value
29
+ end
30
+
31
+ # @override
32
+ def encode_key(key_id, key)
33
+ { key_id => key }
34
+ end
35
+
36
+ # @override
37
+ def decode_key(payload, key_id)
38
+ payload[key_id]
39
+ end
40
+ end
41
+ end
42
+ end