deimos-temp-fork 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +83 -0
- data/.gitignore +41 -0
- data/.gitmodules +0 -0
- data/.rspec +1 -0
- data/.rubocop.yml +333 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +349 -0
- data/CODE_OF_CONDUCT.md +77 -0
- data/Dockerfile +23 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +286 -0
- data/Guardfile +22 -0
- data/LICENSE.md +195 -0
- data/README.md +1099 -0
- data/Rakefile +13 -0
- data/bin/deimos +4 -0
- data/deimos-ruby.gemspec +44 -0
- data/docker-compose.yml +71 -0
- data/docs/ARCHITECTURE.md +140 -0
- data/docs/CONFIGURATION.md +236 -0
- data/docs/DATABASE_BACKEND.md +147 -0
- data/docs/INTEGRATION_TESTS.md +52 -0
- data/docs/PULL_REQUEST_TEMPLATE.md +35 -0
- data/docs/UPGRADING.md +128 -0
- data/lib/deimos-temp-fork.rb +95 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +164 -0
- data/lib/deimos/active_record_consume/batch_slicer.rb +27 -0
- data/lib/deimos/active_record_consume/message_consumption.rb +79 -0
- data/lib/deimos/active_record_consume/schema_model_converter.rb +52 -0
- data/lib/deimos/active_record_consumer.rb +67 -0
- data/lib/deimos/active_record_producer.rb +87 -0
- data/lib/deimos/backends/base.rb +32 -0
- data/lib/deimos/backends/db.rb +41 -0
- data/lib/deimos/backends/kafka.rb +33 -0
- data/lib/deimos/backends/kafka_async.rb +33 -0
- data/lib/deimos/backends/test.rb +20 -0
- data/lib/deimos/batch_consumer.rb +7 -0
- data/lib/deimos/config/configuration.rb +381 -0
- data/lib/deimos/config/phobos_config.rb +137 -0
- data/lib/deimos/consume/batch_consumption.rb +150 -0
- data/lib/deimos/consume/message_consumption.rb +94 -0
- data/lib/deimos/consumer.rb +104 -0
- data/lib/deimos/instrumentation.rb +76 -0
- data/lib/deimos/kafka_message.rb +60 -0
- data/lib/deimos/kafka_source.rb +128 -0
- data/lib/deimos/kafka_topic_info.rb +102 -0
- data/lib/deimos/message.rb +79 -0
- data/lib/deimos/metrics/datadog.rb +47 -0
- data/lib/deimos/metrics/mock.rb +39 -0
- data/lib/deimos/metrics/provider.rb +36 -0
- data/lib/deimos/monkey_patches/phobos_cli.rb +35 -0
- data/lib/deimos/monkey_patches/phobos_producer.rb +51 -0
- data/lib/deimos/poll_info.rb +9 -0
- data/lib/deimos/producer.rb +224 -0
- data/lib/deimos/railtie.rb +8 -0
- data/lib/deimos/schema_backends/avro_base.rb +140 -0
- data/lib/deimos/schema_backends/avro_local.rb +30 -0
- data/lib/deimos/schema_backends/avro_schema_coercer.rb +119 -0
- data/lib/deimos/schema_backends/avro_schema_registry.rb +34 -0
- data/lib/deimos/schema_backends/avro_validation.rb +21 -0
- data/lib/deimos/schema_backends/base.rb +150 -0
- data/lib/deimos/schema_backends/mock.rb +42 -0
- data/lib/deimos/shared_config.rb +63 -0
- data/lib/deimos/test_helpers.rb +360 -0
- data/lib/deimos/tracing/datadog.rb +35 -0
- data/lib/deimos/tracing/mock.rb +40 -0
- data/lib/deimos/tracing/provider.rb +29 -0
- data/lib/deimos/utils/db_poller.rb +150 -0
- data/lib/deimos/utils/db_producer.rb +243 -0
- data/lib/deimos/utils/deadlock_retry.rb +68 -0
- data/lib/deimos/utils/inline_consumer.rb +150 -0
- data/lib/deimos/utils/lag_reporter.rb +175 -0
- data/lib/deimos/utils/schema_controller_mixin.rb +115 -0
- data/lib/deimos/version.rb +5 -0
- data/lib/generators/deimos/active_record/templates/migration.rb.tt +28 -0
- data/lib/generators/deimos/active_record/templates/model.rb.tt +5 -0
- data/lib/generators/deimos/active_record_generator.rb +79 -0
- data/lib/generators/deimos/db_backend/templates/migration +25 -0
- data/lib/generators/deimos/db_backend/templates/rails3_migration +31 -0
- data/lib/generators/deimos/db_backend_generator.rb +48 -0
- data/lib/generators/deimos/db_poller/templates/migration +11 -0
- data/lib/generators/deimos/db_poller/templates/rails3_migration +16 -0
- data/lib/generators/deimos/db_poller_generator.rb +48 -0
- data/lib/tasks/deimos.rake +34 -0
- data/spec/active_record_batch_consumer_spec.rb +481 -0
- data/spec/active_record_consume/batch_slicer_spec.rb +42 -0
- data/spec/active_record_consume/schema_model_converter_spec.rb +105 -0
- data/spec/active_record_consumer_spec.rb +154 -0
- data/spec/active_record_producer_spec.rb +85 -0
- data/spec/backends/base_spec.rb +10 -0
- data/spec/backends/db_spec.rb +54 -0
- data/spec/backends/kafka_async_spec.rb +11 -0
- data/spec/backends/kafka_spec.rb +11 -0
- data/spec/batch_consumer_spec.rb +256 -0
- data/spec/config/configuration_spec.rb +248 -0
- data/spec/consumer_spec.rb +209 -0
- data/spec/deimos_spec.rb +169 -0
- data/spec/generators/active_record_generator_spec.rb +56 -0
- data/spec/handlers/my_batch_consumer.rb +10 -0
- data/spec/handlers/my_consumer.rb +10 -0
- data/spec/kafka_listener_spec.rb +55 -0
- data/spec/kafka_source_spec.rb +381 -0
- data/spec/kafka_topic_info_spec.rb +111 -0
- data/spec/message_spec.rb +19 -0
- data/spec/phobos.bad_db.yml +73 -0
- data/spec/phobos.yml +77 -0
- data/spec/producer_spec.rb +498 -0
- data/spec/rake_spec.rb +19 -0
- data/spec/schema_backends/avro_base_shared.rb +199 -0
- data/spec/schema_backends/avro_local_spec.rb +32 -0
- data/spec/schema_backends/avro_schema_registry_spec.rb +32 -0
- data/spec/schema_backends/avro_validation_spec.rb +24 -0
- data/spec/schema_backends/base_spec.rb +33 -0
- data/spec/schemas/com/my-namespace/Generated.avsc +71 -0
- data/spec/schemas/com/my-namespace/MyNestedSchema.avsc +62 -0
- data/spec/schemas/com/my-namespace/MySchema-key.avsc +13 -0
- data/spec/schemas/com/my-namespace/MySchema.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaCompound-key.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaWithBooleans.avsc +18 -0
- data/spec/schemas/com/my-namespace/MySchemaWithDateTimes.avsc +33 -0
- data/spec/schemas/com/my-namespace/MySchemaWithId.avsc +28 -0
- data/spec/schemas/com/my-namespace/MySchemaWithUniqueId.avsc +32 -0
- data/spec/schemas/com/my-namespace/Wibble.avsc +43 -0
- data/spec/schemas/com/my-namespace/Widget.avsc +27 -0
- data/spec/schemas/com/my-namespace/WidgetTheSecond.avsc +27 -0
- data/spec/schemas/com/my-namespace/request/CreateTopic.avsc +11 -0
- data/spec/schemas/com/my-namespace/request/Index.avsc +11 -0
- data/spec/schemas/com/my-namespace/request/UpdateRequest.avsc +11 -0
- data/spec/schemas/com/my-namespace/response/CreateTopic.avsc +11 -0
- data/spec/schemas/com/my-namespace/response/Index.avsc +11 -0
- data/spec/schemas/com/my-namespace/response/UpdateResponse.avsc +11 -0
- data/spec/spec_helper.rb +267 -0
- data/spec/utils/db_poller_spec.rb +320 -0
- data/spec/utils/db_producer_spec.rb +514 -0
- data/spec/utils/deadlock_retry_spec.rb +74 -0
- data/spec/utils/inline_consumer_spec.rb +31 -0
- data/spec/utils/lag_reporter_spec.rb +76 -0
- data/spec/utils/platform_schema_validation_spec.rb +0 -0
- data/spec/utils/schema_controller_mixin_spec.rb +84 -0
- data/support/deimos-solo.png +0 -0
- data/support/deimos-with-name-next.png +0 -0
- data/support/deimos-with-name.png +0 -0
- data/support/flipp-logo.png +0 -0
- metadata +551 -0
@@ -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
|