deimos-ruby 1.4.0.pre.beta7 → 1.5.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 (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