deimos-temp-fork 0.0.1

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 (146) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +83 -0
  3. data/.gitignore +41 -0
  4. data/.gitmodules +0 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +333 -0
  7. data/.ruby-gemset +1 -0
  8. data/.ruby-version +1 -0
  9. data/CHANGELOG.md +349 -0
  10. data/CODE_OF_CONDUCT.md +77 -0
  11. data/Dockerfile +23 -0
  12. data/Gemfile +6 -0
  13. data/Gemfile.lock +286 -0
  14. data/Guardfile +22 -0
  15. data/LICENSE.md +195 -0
  16. data/README.md +1099 -0
  17. data/Rakefile +13 -0
  18. data/bin/deimos +4 -0
  19. data/deimos-ruby.gemspec +44 -0
  20. data/docker-compose.yml +71 -0
  21. data/docs/ARCHITECTURE.md +140 -0
  22. data/docs/CONFIGURATION.md +236 -0
  23. data/docs/DATABASE_BACKEND.md +147 -0
  24. data/docs/INTEGRATION_TESTS.md +52 -0
  25. data/docs/PULL_REQUEST_TEMPLATE.md +35 -0
  26. data/docs/UPGRADING.md +128 -0
  27. data/lib/deimos-temp-fork.rb +95 -0
  28. data/lib/deimos/active_record_consume/batch_consumption.rb +164 -0
  29. data/lib/deimos/active_record_consume/batch_slicer.rb +27 -0
  30. data/lib/deimos/active_record_consume/message_consumption.rb +79 -0
  31. data/lib/deimos/active_record_consume/schema_model_converter.rb +52 -0
  32. data/lib/deimos/active_record_consumer.rb +67 -0
  33. data/lib/deimos/active_record_producer.rb +87 -0
  34. data/lib/deimos/backends/base.rb +32 -0
  35. data/lib/deimos/backends/db.rb +41 -0
  36. data/lib/deimos/backends/kafka.rb +33 -0
  37. data/lib/deimos/backends/kafka_async.rb +33 -0
  38. data/lib/deimos/backends/test.rb +20 -0
  39. data/lib/deimos/batch_consumer.rb +7 -0
  40. data/lib/deimos/config/configuration.rb +381 -0
  41. data/lib/deimos/config/phobos_config.rb +137 -0
  42. data/lib/deimos/consume/batch_consumption.rb +150 -0
  43. data/lib/deimos/consume/message_consumption.rb +94 -0
  44. data/lib/deimos/consumer.rb +104 -0
  45. data/lib/deimos/instrumentation.rb +76 -0
  46. data/lib/deimos/kafka_message.rb +60 -0
  47. data/lib/deimos/kafka_source.rb +128 -0
  48. data/lib/deimos/kafka_topic_info.rb +102 -0
  49. data/lib/deimos/message.rb +79 -0
  50. data/lib/deimos/metrics/datadog.rb +47 -0
  51. data/lib/deimos/metrics/mock.rb +39 -0
  52. data/lib/deimos/metrics/provider.rb +36 -0
  53. data/lib/deimos/monkey_patches/phobos_cli.rb +35 -0
  54. data/lib/deimos/monkey_patches/phobos_producer.rb +51 -0
  55. data/lib/deimos/poll_info.rb +9 -0
  56. data/lib/deimos/producer.rb +224 -0
  57. data/lib/deimos/railtie.rb +8 -0
  58. data/lib/deimos/schema_backends/avro_base.rb +140 -0
  59. data/lib/deimos/schema_backends/avro_local.rb +30 -0
  60. data/lib/deimos/schema_backends/avro_schema_coercer.rb +119 -0
  61. data/lib/deimos/schema_backends/avro_schema_registry.rb +34 -0
  62. data/lib/deimos/schema_backends/avro_validation.rb +21 -0
  63. data/lib/deimos/schema_backends/base.rb +150 -0
  64. data/lib/deimos/schema_backends/mock.rb +42 -0
  65. data/lib/deimos/shared_config.rb +63 -0
  66. data/lib/deimos/test_helpers.rb +360 -0
  67. data/lib/deimos/tracing/datadog.rb +35 -0
  68. data/lib/deimos/tracing/mock.rb +40 -0
  69. data/lib/deimos/tracing/provider.rb +29 -0
  70. data/lib/deimos/utils/db_poller.rb +150 -0
  71. data/lib/deimos/utils/db_producer.rb +243 -0
  72. data/lib/deimos/utils/deadlock_retry.rb +68 -0
  73. data/lib/deimos/utils/inline_consumer.rb +150 -0
  74. data/lib/deimos/utils/lag_reporter.rb +175 -0
  75. data/lib/deimos/utils/schema_controller_mixin.rb +115 -0
  76. data/lib/deimos/version.rb +5 -0
  77. data/lib/generators/deimos/active_record/templates/migration.rb.tt +28 -0
  78. data/lib/generators/deimos/active_record/templates/model.rb.tt +5 -0
  79. data/lib/generators/deimos/active_record_generator.rb +79 -0
  80. data/lib/generators/deimos/db_backend/templates/migration +25 -0
  81. data/lib/generators/deimos/db_backend/templates/rails3_migration +31 -0
  82. data/lib/generators/deimos/db_backend_generator.rb +48 -0
  83. data/lib/generators/deimos/db_poller/templates/migration +11 -0
  84. data/lib/generators/deimos/db_poller/templates/rails3_migration +16 -0
  85. data/lib/generators/deimos/db_poller_generator.rb +48 -0
  86. data/lib/tasks/deimos.rake +34 -0
  87. data/spec/active_record_batch_consumer_spec.rb +481 -0
  88. data/spec/active_record_consume/batch_slicer_spec.rb +42 -0
  89. data/spec/active_record_consume/schema_model_converter_spec.rb +105 -0
  90. data/spec/active_record_consumer_spec.rb +154 -0
  91. data/spec/active_record_producer_spec.rb +85 -0
  92. data/spec/backends/base_spec.rb +10 -0
  93. data/spec/backends/db_spec.rb +54 -0
  94. data/spec/backends/kafka_async_spec.rb +11 -0
  95. data/spec/backends/kafka_spec.rb +11 -0
  96. data/spec/batch_consumer_spec.rb +256 -0
  97. data/spec/config/configuration_spec.rb +248 -0
  98. data/spec/consumer_spec.rb +209 -0
  99. data/spec/deimos_spec.rb +169 -0
  100. data/spec/generators/active_record_generator_spec.rb +56 -0
  101. data/spec/handlers/my_batch_consumer.rb +10 -0
  102. data/spec/handlers/my_consumer.rb +10 -0
  103. data/spec/kafka_listener_spec.rb +55 -0
  104. data/spec/kafka_source_spec.rb +381 -0
  105. data/spec/kafka_topic_info_spec.rb +111 -0
  106. data/spec/message_spec.rb +19 -0
  107. data/spec/phobos.bad_db.yml +73 -0
  108. data/spec/phobos.yml +77 -0
  109. data/spec/producer_spec.rb +498 -0
  110. data/spec/rake_spec.rb +19 -0
  111. data/spec/schema_backends/avro_base_shared.rb +199 -0
  112. data/spec/schema_backends/avro_local_spec.rb +32 -0
  113. data/spec/schema_backends/avro_schema_registry_spec.rb +32 -0
  114. data/spec/schema_backends/avro_validation_spec.rb +24 -0
  115. data/spec/schema_backends/base_spec.rb +33 -0
  116. data/spec/schemas/com/my-namespace/Generated.avsc +71 -0
  117. data/spec/schemas/com/my-namespace/MyNestedSchema.avsc +62 -0
  118. data/spec/schemas/com/my-namespace/MySchema-key.avsc +13 -0
  119. data/spec/schemas/com/my-namespace/MySchema.avsc +18 -0
  120. data/spec/schemas/com/my-namespace/MySchemaCompound-key.avsc +18 -0
  121. data/spec/schemas/com/my-namespace/MySchemaWithBooleans.avsc +18 -0
  122. data/spec/schemas/com/my-namespace/MySchemaWithDateTimes.avsc +33 -0
  123. data/spec/schemas/com/my-namespace/MySchemaWithId.avsc +28 -0
  124. data/spec/schemas/com/my-namespace/MySchemaWithUniqueId.avsc +32 -0
  125. data/spec/schemas/com/my-namespace/Wibble.avsc +43 -0
  126. data/spec/schemas/com/my-namespace/Widget.avsc +27 -0
  127. data/spec/schemas/com/my-namespace/WidgetTheSecond.avsc +27 -0
  128. data/spec/schemas/com/my-namespace/request/CreateTopic.avsc +11 -0
  129. data/spec/schemas/com/my-namespace/request/Index.avsc +11 -0
  130. data/spec/schemas/com/my-namespace/request/UpdateRequest.avsc +11 -0
  131. data/spec/schemas/com/my-namespace/response/CreateTopic.avsc +11 -0
  132. data/spec/schemas/com/my-namespace/response/Index.avsc +11 -0
  133. data/spec/schemas/com/my-namespace/response/UpdateResponse.avsc +11 -0
  134. data/spec/spec_helper.rb +267 -0
  135. data/spec/utils/db_poller_spec.rb +320 -0
  136. data/spec/utils/db_producer_spec.rb +514 -0
  137. data/spec/utils/deadlock_retry_spec.rb +74 -0
  138. data/spec/utils/inline_consumer_spec.rb +31 -0
  139. data/spec/utils/lag_reporter_spec.rb +76 -0
  140. data/spec/utils/platform_schema_validation_spec.rb +0 -0
  141. data/spec/utils/schema_controller_mixin_spec.rb +84 -0
  142. data/support/deimos-solo.png +0 -0
  143. data/support/deimos-with-name-next.png +0 -0
  144. data/support/deimos-with-name.png +0 -0
  145. data/support/flipp-logo.png +0 -0
  146. metadata +551 -0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add rake task to Rails.
4
+ class Deimos::Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load 'tasks/deimos.rake'
7
+ end
8
+ end
@@ -0,0 +1,140 @@
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
+ # :nodoc:
37
+ def sql_type(field)
38
+ type = field.type.type
39
+ return type if %w(array map record).include?(type)
40
+
41
+ if type == :union
42
+ non_null = field.type.schemas.reject { |f| f.type == :null }
43
+ if non_null.size > 1
44
+ warn("WARNING: #{field.name} has more than one non-null type. Picking the first for the SQL type.")
45
+ end
46
+ return non_null.first.type
47
+ end
48
+ return type.to_sym if %w(float boolean).include?(type)
49
+ return :integer if type == 'int'
50
+ return :bigint if type == 'long'
51
+
52
+ if type == 'double'
53
+ warn('Avro `double` type turns into SQL `float` type. Please ensure you have the correct `limit` set.')
54
+ return :float
55
+ end
56
+
57
+ :string
58
+ end
59
+
60
+ # @override
61
+ def coerce_field(field, value)
62
+ AvroSchemaCoercer.new(avro_schema).coerce_type(field.type, value)
63
+ end
64
+
65
+ # @override
66
+ def schema_fields
67
+ avro_schema.fields.map do |field|
68
+ enum_values = field.type.type == 'enum' ? field.type.symbols : []
69
+ SchemaField.new(field.name, field.type, enum_values)
70
+ end
71
+ end
72
+
73
+ # @override
74
+ def validate(payload, schema:)
75
+ Avro::SchemaValidator.validate!(avro_schema(schema), payload,
76
+ recursive: true,
77
+ fail_on_extra_fields: true)
78
+ end
79
+
80
+ # @override
81
+ def self.mock_backend
82
+ :avro_validation
83
+ end
84
+
85
+ # @override
86
+ def self.content_type
87
+ 'avro/binary'
88
+ end
89
+
90
+ private
91
+
92
+ # @param schema [String]
93
+ # @return [Avro::Schema]
94
+ def avro_schema(schema=nil)
95
+ schema ||= @schema
96
+ @schema_store.find(schema, @namespace)
97
+ end
98
+
99
+ # Generate a key schema from the given value schema and key ID. This
100
+ # is used when encoding or decoding keys from an existing value schema.
101
+ # @param key_id [Symbol]
102
+ # @return [Hash]
103
+ def _generate_key_schema(key_id)
104
+ key_field = avro_schema.fields.find { |f| f.name == key_id.to_s }
105
+ name = _key_schema_name(@schema)
106
+ key_schema = {
107
+ 'type' => 'record',
108
+ 'name' => name,
109
+ 'namespace' => @namespace,
110
+ 'doc' => "Key for #{@namespace}.#{@schema} - autogenerated by Deimos",
111
+ 'fields' => [
112
+ {
113
+ 'name' => key_id,
114
+ 'type' => key_field.type.type_sym.to_s
115
+ }
116
+ ]
117
+ }
118
+ @schema_store.add_schema(key_schema)
119
+ key_schema
120
+ end
121
+
122
+ # @param value_schema [Hash]
123
+ # @return [String]
124
+ def _field_name_from_schema(value_schema)
125
+ raise "Schema #{@schema} not found!" if value_schema.nil?
126
+ if value_schema['fields'].nil? || value_schema['fields'].empty?
127
+ raise "Schema #{@schema} has no fields!"
128
+ end
129
+
130
+ value_schema['fields'][0]['name']
131
+ end
132
+
133
+ # @param schema [String]
134
+ # @return [String]
135
+ def _key_schema_name(schema)
136
+ "#{schema}_key"
137
+ end
138
+ end
139
+ end
140
+ 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
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/time'
4
+
5
+ module Deimos
6
+ # Class to coerce values in a payload to match a schema.
7
+ class AvroSchemaCoercer
8
+ # @param schema [Avro::Schema]
9
+ def initialize(schema)
10
+ @schema = schema
11
+ end
12
+
13
+ # Coerce sub-records in a payload to match the schema.
14
+ # @param type [Avro::Schema::UnionSchema]
15
+ # @param val [Object]
16
+ # @return [Object]
17
+ def coerce_union(type, val)
18
+ union_types = type.schemas.map { |s| s.type.to_sym }
19
+ return nil if val.nil? && union_types.include?(:null)
20
+
21
+ schema_type = type.schemas.find { |s| s.type.to_sym != :null }
22
+ coerce_type(schema_type, val)
23
+ end
24
+
25
+ # Coerce sub-records in a payload to match the schema.
26
+ # @param type [Avro::Schema::RecordSchema]
27
+ # @param val [Object]
28
+ # @return [Object]
29
+ def coerce_record(type, val)
30
+ record = val.map do |name, value|
31
+ field = type.fields.find { |f| f.name == name }
32
+ coerce_type(field.type, value)
33
+ end
34
+ val.keys.zip(record).to_h
35
+ end
36
+
37
+ # Coerce values in a payload to match the schema.
38
+ # @param type [Avro::Schema]
39
+ # @param val [Object]
40
+ # @return [Object]
41
+ def coerce_type(type, val)
42
+ int_classes = [Time, ActiveSupport::TimeWithZone]
43
+ field_type = type.type.to_sym
44
+
45
+ case field_type
46
+ when :int, :long
47
+ if %w(timestamp-millis timestamp-micros).include?(type.logical_type)
48
+ val
49
+ elsif val.is_a?(Integer) ||
50
+ _is_integer_string?(val) ||
51
+ int_classes.any? { |klass| val.is_a?(klass) }
52
+ val.to_i
53
+ else
54
+ val # this will fail
55
+ end
56
+ when :float, :double
57
+ if val.is_a?(Numeric) || _is_float_string?(val)
58
+ val.to_f
59
+ else
60
+ val # this will fail
61
+ end
62
+ when :string
63
+ if val.respond_to?(:to_str)
64
+ val.to_s
65
+ elsif _is_to_s_defined?(val)
66
+ val.to_s
67
+ else
68
+ val # this will fail
69
+ end
70
+ when :boolean
71
+ if val.nil? || val == false
72
+ false
73
+ else
74
+ true
75
+ end
76
+ when :union
77
+ coerce_union(type, val)
78
+ when :record
79
+ coerce_record(type, val)
80
+ else
81
+ val
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ # @param val [String]
88
+ # @return [Boolean]
89
+ def _is_integer_string?(val)
90
+ return false unless val.is_a?(String)
91
+
92
+ begin
93
+ true if Integer(val)
94
+ rescue StandardError
95
+ false
96
+ end
97
+ end
98
+
99
+ # @param val [String]
100
+ # @return [Boolean]
101
+ def _is_float_string?(val)
102
+ return false unless val.is_a?(String)
103
+
104
+ begin
105
+ true if Float(val)
106
+ rescue StandardError
107
+ false
108
+ end
109
+ end
110
+
111
+ # @param val [Object]
112
+ # @return [Boolean]
113
+ def _is_to_s_defined?(val)
114
+ return false if val.nil?
115
+
116
+ Object.instance_method(:to_s).bind(val).call != val.to_s
117
+ end
118
+ end
119
+ 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 || schema)
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,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deimos
4
+ # Represents a field in the schema.
5
+ class SchemaField
6
+ attr_accessor :name, :type, :enum_values
7
+
8
+ # @param name [String]
9
+ # @param type [Object]
10
+ # @param enum_values [Array<String>]
11
+ def initialize(name, type, enum_values=[])
12
+ @name = name
13
+ @type = type
14
+ @enum_values = enum_values
15
+ end
16
+ end
17
+
18
+ module SchemaBackends
19
+ # Base class for encoding / decoding.
20
+ class Base
21
+ attr_accessor :schema, :namespace, :key_schema
22
+
23
+ # @param schema [String|Symbol]
24
+ # @param namespace [String]
25
+ def initialize(schema:, namespace: nil)
26
+ @schema = schema
27
+ @namespace = namespace
28
+ end
29
+
30
+ # Encode a payload with a schema. Public method.
31
+ # @param payload [Hash]
32
+ # @param schema [Symbol|String]
33
+ # @param topic [String]
34
+ # @return [String]
35
+ def encode(payload, schema: nil, topic: nil)
36
+ validate(payload, schema: schema || @schema)
37
+ encode_payload(payload, schema: schema || @schema, topic: topic)
38
+ end
39
+
40
+ # Decode a payload with a schema. Public method.
41
+ # @param payload [String]
42
+ # @param schema [Symbol|String]
43
+ # @return [Hash,nil]
44
+ def decode(payload, schema: nil)
45
+ return nil if payload.nil?
46
+ decode_payload(payload, schema: schema || @schema)
47
+ end
48
+
49
+ # Given a hash, coerce its types to our schema. To be defined by subclass.
50
+ # @param payload [Hash]
51
+ # @return [Hash]
52
+ def coerce(payload)
53
+ result = {}
54
+ self.schema_fields.each do |field|
55
+ name = field.name
56
+ next unless payload.key?(name)
57
+
58
+ val = payload[name]
59
+ result[name] = coerce_field(field, val)
60
+ end
61
+ result.with_indifferent_access
62
+ end
63
+
64
+ # Indicate a class which should act as a mocked version of this backend.
65
+ # This class should perform all validations but not actually do any
66
+ # encoding.
67
+ # Note that the "mock" version (e.g. avro_validation) should return
68
+ # its own symbol when this is called, since it may be called multiple
69
+ # times depending on the order of RSpec helpers.
70
+ # @return [Symbol]
71
+ def self.mock_backend
72
+ :mock
73
+ end
74
+
75
+ # The content type to use when encoding / decoding requests over HTTP via ActionController.
76
+ # @return [String]
77
+ def self.content_type
78
+ raise NotImplementedError
79
+ end
80
+
81
+ # Encode a payload. To be defined by subclass.
82
+ # @param payload [Hash]
83
+ # @param schema [Symbol|String]
84
+ # @param topic [String]
85
+ # @return [String]
86
+ def encode_payload(_payload, schema:, topic: nil)
87
+ raise NotImplementedError
88
+ end
89
+
90
+ # Decode a payload. To be defined by subclass.
91
+ # @param payload [String]
92
+ # @param schema [String|Symbol]
93
+ # @return [Hash]
94
+ def decode_payload(_payload, schema:)
95
+ raise NotImplementedError
96
+ end
97
+
98
+ # Validate that a payload matches the schema. To be defined by subclass.
99
+ # @param payload [Hash]
100
+ # @param schema [String|Symbol]
101
+ def validate(_payload, schema:)
102
+ raise NotImplementedError
103
+ end
104
+
105
+ # List of field names belonging to the schema. To be defined by subclass.
106
+ # @return [Array<SchemaField>]
107
+ def schema_fields
108
+ raise NotImplementedError
109
+ end
110
+
111
+ # Given a value and a field definition (as defined by whatever the
112
+ # underlying schema library is), coerce the given value to
113
+ # the given field type.
114
+ # @param field [SchemaField]
115
+ # @param value [Object]
116
+ # @return [Object]
117
+ def coerce_field(_field, _value)
118
+ raise NotImplementedError
119
+ end
120
+
121
+ # Given a field definition, return the SQL type that might be used in
122
+ # ActiveRecord table creation - e.g. for Avro, a `long` type would
123
+ # return `:bigint`. There are also special values that need to be returned:
124
+ # `:array`, `:map` and `:record`, for types representing those structures.
125
+ # `:enum` is also recognized.
126
+ # @param field [SchemaField]
127
+ # @return [Symbol]
128
+ def sql_type(field)
129
+ raise NotImplementedError
130
+ end
131
+
132
+ # Encode a message key. To be defined by subclass.
133
+ # @param key [String|Hash] the value to use as the key.
134
+ # @param key_id [Symbol|String] the field name of the key.
135
+ # @param topic [String]
136
+ # @return [String]
137
+ def encode_key(_key, _key_id, topic: nil)
138
+ raise NotImplementedError
139
+ end
140
+
141
+ # Decode a message key. To be defined by subclass.
142
+ # @param payload [Hash] the message itself.
143
+ # @param key_id [Symbol|String] the field in the message to decode.
144
+ # @return [String]
145
+ def decode_key(_payload, _key_id)
146
+ raise NotImplementedError
147
+ end
148
+ end
149
+ end
150
+ end