deimos-ruby 2.1.12 → 2.2.0.pre.beta1
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/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/README.md +13 -6
- data/deimos-ruby.gemspec +1 -0
- data/docs/CONFIGURATION.md +14 -14
- data/lib/deimos/active_record_consume/message_consumption.rb +5 -1
- data/lib/deimos/config/configuration.rb +3 -1
- data/lib/deimos/consume/message_consumption.rb +21 -9
- data/lib/deimos/ext/producer_middleware.rb +25 -4
- data/lib/deimos/ext/schema_route.rb +5 -0
- data/lib/deimos/message.rb +3 -2
- data/lib/deimos/producer.rb +8 -3
- data/lib/deimos/schema_backends/avro_base.rb +9 -0
- data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -1
- data/lib/deimos/schema_backends/base.rb +10 -0
- data/lib/deimos/schema_backends/proto_base.rb +92 -0
- data/lib/deimos/schema_backends/proto_schema_registry.rb +33 -0
- data/lib/deimos/transcoder.rb +6 -2
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +9 -7
- data/lib/generators/deimos/schema_class_generator.rb +17 -6
- data/spec/gen/sample/v1/sample_pb.rb +20 -0
- data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +6 -5
- data/spec/protos/sample/v1/sample.proto +24 -0
- data/spec/schema_backends/proto_schema_registry_spec.rb +50 -0
- data/spec/schemas/com/my-namespace/MySchemaWithComplexTypes.avsc +5 -0
- data/spec/schemas/my_namespace/generated.rb +10 -3
- data/spec/schemas/my_namespace/my_schema_with_complex_type.rb +35 -9
- data/spec/snapshots/consumers-no-nest.snap +5 -0
- data/spec/snapshots/consumers.snap +5 -0
- data/spec/snapshots/consumers_and_producers-no-nest.snap +5 -0
- data/spec/snapshots/consumers_and_producers.snap +5 -0
- data/spec/snapshots/consumers_circular-no-nest.snap +5 -0
- data/spec/snapshots/consumers_circular.snap +5 -0
- data/spec/snapshots/consumers_complex_types-no-nest.snap +5 -0
- data/spec/snapshots/consumers_complex_types.snap +5 -0
- data/spec/snapshots/consumers_nested-no-nest.snap +5 -0
- data/spec/snapshots/consumers_nested.snap +5 -0
- data/spec/snapshots/namespace_folders.snap +5 -0
- data/spec/snapshots/namespace_map.snap +5 -0
- data/spec/snapshots/producers_with_key-no-nest.snap +5 -0
- data/spec/snapshots/producers_with_key.snap +5 -0
- metadata +23 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e3d172790c33077ab5f0dc188a8a28b9bb7050cb612bbac833111e7e1caafbe
|
4
|
+
data.tar.gz: 89040d16c763b278cb0dee57c609f3d9386e00d0d8d8a9f331b2b56b22600752
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20e247295e0724264d1db8dbd32a9267663acbbbf6478c32a78333a742b2538195a353bf0892a3ac5f0ea1e27f6dead3836af551e40e05c1f54071b76ba3cf3e
|
7
|
+
data.tar.gz: cdec52113639f6cf7c5c79671386ffd103bbfb16ebf2500913e2015789af8416117ec8b69923fcdc7804cd29a3efe887415e19a4d452443afa2d81649458b028
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## UNRELEASED
|
9
9
|
|
10
|
+
- Feature: Support Protobuf as a schema backend.
|
11
|
+
- Feature: Add the ability to specify the schema backend per topic.
|
12
|
+
|
13
|
+
# 2.1.13 - 2025-10-03
|
14
|
+
- Fix: Union types where the first type is string and has a default value of empty string was incorrectly turning the field into a required argument, which crashed when trying to instantiate it.
|
15
|
+
|
10
16
|
# 2.1.12 - 2025-10-03
|
11
17
|
|
12
18
|
- Fix: Fixes a crash when schema classes are in use and key config is set to `:plain`.
|
data/README.md
CHANGED
@@ -84,21 +84,19 @@ For a full configuration reference, please see [the configuration docs ](docs/CO
|
|
84
84
|
|
85
85
|
# Schemas
|
86
86
|
|
87
|
-
Deimos
|
88
|
-
This has since been expanded to a plugin architecture allowing messages to be
|
89
|
-
encoded and decoded via any schema specification you wish.
|
87
|
+
Deimos has a plugin architecture allowing messages to be encoded and decoded via any schema specification you wish.
|
90
88
|
|
91
89
|
Currently we have the following possible schema backends:
|
92
90
|
* Avro Local (use pure Avro)
|
93
91
|
* Avro Schema Registry (use the Confluent Schema Registry)
|
94
92
|
* Avro Validation (validate using an Avro schema but leave decoded - this is useful
|
95
93
|
for unit testing and development)
|
94
|
+
* Protobuf Schema Registry (use Protobuf with the Confluent Schema Registry)
|
96
95
|
* Mock (no actual encoding/decoding).
|
97
96
|
|
98
|
-
Note that to use
|
99
|
-
Gemfile.
|
97
|
+
Note that to use Protobuf, you must include the [proto_turf](https://github.com/flipp-oss/proto_turf) gem in your Gemfile.
|
100
98
|
|
101
|
-
Other possible schemas could
|
99
|
+
Other possible schemas could [JSONSchema](https://json-schema.org/), etc. Feel free to
|
102
100
|
contribute!
|
103
101
|
|
104
102
|
To create a new schema backend, please see the existing examples [here](lib/deimos/schema_backends).
|
@@ -141,6 +139,8 @@ class MyProducer < Deimos::Producer
|
|
141
139
|
end
|
142
140
|
```
|
143
141
|
|
142
|
+
Note that if you are using Protobuf, you need to pass a Protobuf message object as the payload - you can't use a bare hash.
|
143
|
+
|
144
144
|
## Auto-added Fields
|
145
145
|
|
146
146
|
If your schema has a field called `message_id`, and the payload you give
|
@@ -276,6 +276,9 @@ MyProducer.publish({
|
|
276
276
|
})
|
277
277
|
```
|
278
278
|
|
279
|
+
> [!IMPORTANT]
|
280
|
+
> Protobuf should *not* be used as a key schema, since the binary encoding is [unstable](https://protobuf.dev/programming-guides/encoding/#implications) and may break partitioning. Deimos will automatically convert key fields to plain values and key hashes to JSON.
|
281
|
+
|
279
282
|
## Instrumentation
|
280
283
|
|
281
284
|
Deimos will send events through the [Karafka instrumentation monitor](https://karafka.io/docs/Monitoring-and-Logging/#subscribing-to-the-instrumentation-events).
|
@@ -1091,6 +1094,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/flipp-
|
|
1091
1094
|
|
1092
1095
|
If making changes to the generator, you should regenerate the test schema classes by running `bundle exec ./regenerate_test_schema_classes.rb` .
|
1093
1096
|
|
1097
|
+
You can regenerate test Protobuf classes by running:
|
1098
|
+
|
1099
|
+
protoc -I spec/protos --ruby_out=spec/gen --ruby_opt=paths=source_relative spec/protos/**/*.proto
|
1100
|
+
|
1094
1101
|
You can/should re-generate RBS types when methods or classes change by running the following:
|
1095
1102
|
|
1096
1103
|
rbs collection install # if you haven't done it
|
data/deimos-ruby.gemspec
CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency('database_cleaner', '~> 2.1')
|
30
30
|
spec.add_development_dependency('ddtrace', '>= 0.11')
|
31
31
|
spec.add_development_dependency('dogstatsd-ruby', '>= 4.2')
|
32
|
+
spec.add_development_dependency('proto_turf')
|
32
33
|
spec.add_development_dependency('guard', '~> 2')
|
33
34
|
spec.add_development_dependency('guard-rspec', '~> 4')
|
34
35
|
spec.add_development_dependency('guard-rubocop', '~> 1')
|
data/docs/CONFIGURATION.md
CHANGED
@@ -52,18 +52,18 @@ things you need to reference into local variables before calling `configure`.
|
|
52
52
|
|
53
53
|
### Schema Configuration
|
54
54
|
|
55
|
-
| Config name | Default | Description
|
56
|
-
|
57
|
-
| schema.backend | `:mock` | Backend representing the schema encoder/decoder. You can see a full list [here](../lib/deimos/schema_backends).
|
58
|
-
| schema.registry_url | `http://localhost:8081` | URL of the Confluent schema registry.
|
59
|
-
| schema.user | nil | Basic auth user.
|
60
|
-
| schema.password | nil | Basic auth password.
|
61
|
-
| schema.path | nil | Local path to find your schemas.
|
62
|
-
| schema.use_schema_classes | false | Set this to true to use generated schema classes in your application.
|
63
|
-
| schema.generated_class_path | `app/lib/schema_classes` | Local path to generated schema classes.
|
64
|
-
| schema.nest_child_schemas | false | Set to true to nest subschemas within the generated class for the parent schema.
|
65
|
-
| schema.use_full_namespace | false | Set to true to generate folders for schemas matching the full namespace.
|
66
|
-
| schema.schema_namespace_map | {} | A map of namespace prefixes to base module name(s). Example: { 'com.mycompany.suborg' => ['SchemaClasses'] }. Requires `use_full_namespace` to be true.
|
55
|
+
| Config name | Default | Description |
|
56
|
+
|-----------------------------|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
57
|
+
| schema.backend | `:mock` | Backend representing the schema encoder/decoder. You can see a full list [here](../lib/deimos/schema_backends). |
|
58
|
+
| schema.registry_url | `http://localhost:8081` | URL of the Confluent schema registry. |
|
59
|
+
| schema.user | nil | Basic auth user. |
|
60
|
+
| schema.password | nil | Basic auth password. |
|
61
|
+
| schema.path | nil | Local path to find your schemas. |
|
62
|
+
| schema.use_schema_classes | false | Set this to true to use generated schema classes in your application. |
|
63
|
+
| schema.generated_class_path | `app/lib/schema_classes` | Local path to generated schema classes. |
|
64
|
+
| schema.nest_child_schemas | false | Set to true to nest subschemas within the generated class for the parent schema. |
|
65
|
+
| schema.use_full_namespace | false | Set to true to generate folders for schemas matching the full namespace. |
|
66
|
+
| schema.schema_namespace_map | {} | A map of namespace prefixes to base module name(s). Example: { 'com.mycompany.suborg' => ['SchemaClasses'] }. Requires `use_full_namespace` to be true. |
|
67
67
|
|
68
68
|
### Outbox Configuration
|
69
69
|
|
@@ -113,8 +113,8 @@ The following are additional settings that can be added to the `topic` block in
|
|
113
113
|
| Config name | Default | Description |
|
114
114
|
|--------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
115
115
|
| payload_log | :full | Determines how much data is logged per payload.</br>`:full` - all keys and payloads are logged.</br>`:keys` - only keys are logged.</br>`:count` - only the total count of messages are logged. |
|
116
|
-
| schema | nil | Name of the schema to use to encode data before producing.
|
117
|
-
| namespace | nil | Namespace of the schema to use when finding it locally.
|
116
|
+
| schema | nil | Name of the schema to use to encode data before producing. For Avro, namespace and schema are separated, but protobuf uses only the fully resolved name including package. |
|
117
|
+
| namespace | nil | Namespace of the schema to use when finding it locally. Leave blank for protobuf. |
|
118
118
|
| key_config | nil | Configuration hash for message keys. See [Kafka Message Keys](../README.md#kafka-message-keys). |
|
119
119
|
| use_schema_classes | nil | Set to true or false to enable or disable using the producers schema classes. See [Generated Schema Classes](../README.md#generated-schema-classes). |
|
120
120
|
|
@@ -41,7 +41,11 @@ module Deimos
|
|
41
41
|
end
|
42
42
|
|
43
43
|
klass = self.class.config[:record_class]
|
44
|
-
|
44
|
+
payload = message.payload
|
45
|
+
if payload.is_a?(Hash) || payload.nil? || payload.is_a?(SchemaClass::Record)
|
46
|
+
payload = payload.to_h.with_indifferent_access
|
47
|
+
end
|
48
|
+
record = fetch_record(klass, payload, message.key)
|
45
49
|
if delete_record?(message)
|
46
50
|
destroy_record(record)
|
47
51
|
return
|
@@ -27,7 +27,9 @@ module Deimos # rubocop:disable Metrics/ModuleLength
|
|
27
27
|
if transcoder.respond_to?(:key_field) && transcoder.key_field
|
28
28
|
transcoder.backend = Deimos.schema_backend(schema: config.schema,
|
29
29
|
namespace: config.namespace)
|
30
|
-
transcoder.backend.
|
30
|
+
if transcoder.backend.supports_key_schemas?
|
31
|
+
transcoder.backend.generate_key_schema(transcoder.key_field)
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -43,15 +43,27 @@ module Deimos
|
|
43
43
|
# @param exception [Throwable]
|
44
44
|
# @param message [Karafka::Messages::Message]
|
45
45
|
def _handle_message_error(exception, message)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
begin
|
47
|
+
Deimos::Logging.log_warn(
|
48
|
+
message: 'Error consuming message',
|
49
|
+
handler: self.class.name,
|
50
|
+
metadata: Deimos::Logging.metadata_log_text(message.metadata),
|
51
|
+
key: message.key,
|
52
|
+
data: message.payload,
|
53
|
+
error_message: exception.message,
|
54
|
+
error: exception.backtrace
|
55
|
+
)
|
56
|
+
rescue # serialization issues
|
57
|
+
Deimos::Logging.log_warn(
|
58
|
+
message: 'Error consuming message',
|
59
|
+
handler: self.class.name,
|
60
|
+
metadata: Deimos::Logging.metadata_log_text(message.metadata),
|
61
|
+
key: message.raw_key,
|
62
|
+
data: message.raw_payload,
|
63
|
+
error_message: exception.message,
|
64
|
+
error: exception.backtrace
|
65
|
+
)
|
66
|
+
end
|
55
67
|
|
56
68
|
_error(exception, Karafka::Messages::Messages.new([message], messages.metadata))
|
57
69
|
end
|
@@ -1,8 +1,17 @@
|
|
1
1
|
module Deimos
|
2
2
|
|
3
3
|
module ProducerMiddleware
|
4
|
+
|
4
5
|
class << self
|
5
6
|
|
7
|
+
def allowed_classes
|
8
|
+
arr = [Hash, SchemaClass::Record]
|
9
|
+
if defined?(Google::Protobuf)
|
10
|
+
arr.push(Google::Protobuf.const_get(:AbstractMessage))
|
11
|
+
end
|
12
|
+
@allowed_classes ||= arr.freeze
|
13
|
+
end
|
14
|
+
|
6
15
|
def call(message)
|
7
16
|
Karafka.monitor.instrument(
|
8
17
|
'deimos.encode_message',
|
@@ -13,9 +22,12 @@ module Deimos
|
|
13
22
|
|
14
23
|
config = Deimos.karafka_config_for(topic: message[:topic])
|
15
24
|
return message if config.nil? || config.schema.nil?
|
16
|
-
return if message[:payload] &&
|
25
|
+
return if message[:payload] &&
|
26
|
+
self.allowed_classes.none? { |k| message[:payload].is_a?(k) }
|
17
27
|
|
18
|
-
|
28
|
+
payload = message[:payload]
|
29
|
+
payload = payload.to_h if payload.nil? || payload.is_a?(SchemaClass::Record)
|
30
|
+
m = Deimos::Message.new(payload,
|
19
31
|
headers: message[:headers],
|
20
32
|
partition_key: message[:partition_key])
|
21
33
|
_process_message(m, message, config)
|
@@ -90,10 +102,19 @@ module Deimos
|
|
90
102
|
# @param key_transcoder [Deimos::Transcoder]
|
91
103
|
# @return [String]
|
92
104
|
def _retrieve_key(payload, key_transcoder)
|
93
|
-
key = payload.
|
105
|
+
key = payload.try(:delete, :payload_key)
|
94
106
|
return key if key || !key_transcoder.respond_to?(:key_field)
|
95
107
|
|
96
|
-
|
108
|
+
if key_transcoder.key_field
|
109
|
+
key = key_transcoder.key_field.to_s.split('.')
|
110
|
+
current = payload
|
111
|
+
key.each do |k|
|
112
|
+
current = current[k] if current
|
113
|
+
end
|
114
|
+
current
|
115
|
+
else
|
116
|
+
nil
|
117
|
+
end
|
97
118
|
end
|
98
119
|
end
|
99
120
|
end
|
@@ -10,6 +10,7 @@ module Deimos
|
|
10
10
|
schema: nil,
|
11
11
|
namespace: nil,
|
12
12
|
key_config: {none: true},
|
13
|
+
schema_backend: nil,
|
13
14
|
use_schema_classes: Deimos.config.schema.use_schema_classes
|
14
15
|
}.each do |field, default|
|
15
16
|
define_method(field) do |*args|
|
@@ -26,6 +27,7 @@ module Deimos
|
|
26
27
|
payload = Transcoder.new(
|
27
28
|
schema: schema,
|
28
29
|
namespace: namespace,
|
30
|
+
backend: schema_backend,
|
29
31
|
use_schema_classes: use_schema_classes,
|
30
32
|
topic: name
|
31
33
|
)
|
@@ -35,6 +37,7 @@ module Deimos
|
|
35
37
|
if key_config[:plain]
|
36
38
|
key = Transcoder.new(
|
37
39
|
schema: schema,
|
40
|
+
backend: schema_backend,
|
38
41
|
namespace: namespace,
|
39
42
|
use_schema_classes: use_schema_classes,
|
40
43
|
topic: name
|
@@ -44,6 +47,7 @@ module Deimos
|
|
44
47
|
if key_config[:field]
|
45
48
|
key = Transcoder.new(
|
46
49
|
schema: schema,
|
50
|
+
backend: schema_backend,
|
47
51
|
namespace: namespace,
|
48
52
|
use_schema_classes: use_schema_classes,
|
49
53
|
key_field: key_config[:field].to_s,
|
@@ -52,6 +56,7 @@ module Deimos
|
|
52
56
|
elsif key_config[:schema]
|
53
57
|
key = Transcoder.new(
|
54
58
|
schema: key_config[:schema] || schema,
|
59
|
+
backend: schema_backend,
|
55
60
|
namespace: namespace,
|
56
61
|
use_schema_classes: use_schema_classes,
|
57
62
|
topic: self.name
|
data/lib/deimos/message.rb
CHANGED
@@ -23,7 +23,8 @@ module Deimos
|
|
23
23
|
# @param key [String, Integer, Hash]
|
24
24
|
# @param partition_key [Integer]
|
25
25
|
def initialize(payload, topic: nil, key: nil, headers: nil, partition_key: nil)
|
26
|
-
@payload = payload
|
26
|
+
@payload = payload
|
27
|
+
@payload = @payload.with_indifferent_access if @payload.is_a?(Hash)
|
27
28
|
@topic = topic
|
28
29
|
@key = key
|
29
30
|
@headers = headers&.with_indifferent_access
|
@@ -35,7 +36,7 @@ module Deimos
|
|
35
36
|
# @param fields [Array<String>] existing name fields in the schema.
|
36
37
|
# @return [void]
|
37
38
|
def add_fields(fields)
|
38
|
-
return if @payload.except(:payload_key, :partition_key).blank?
|
39
|
+
return if @payload.to_h.except(:payload_key, :partition_key).blank?
|
39
40
|
|
40
41
|
if fields.include?('message_id')
|
41
42
|
@payload['message_id'] ||= SecureRandom.uuid
|
data/lib/deimos/producer.rb
CHANGED
@@ -111,15 +111,20 @@ module Deimos
|
|
111
111
|
backend = determine_backend_class(sync, force_send)
|
112
112
|
|
113
113
|
messages = Array(payloads).map do |p|
|
114
|
+
payload = p
|
115
|
+
payload = payload.to_h if p.is_a?(SchemaClass::Record)
|
114
116
|
m = {
|
115
|
-
payload:
|
117
|
+
payload: payload,
|
116
118
|
headers: headers,
|
117
119
|
topic: topic,
|
118
120
|
partition_key: self.partition_key(p)
|
119
121
|
}
|
120
122
|
if m.dig(:payload, :key).present? && m.dig(:payload, :message).present?
|
121
|
-
m[:key] = m[:payload][:key]
|
122
|
-
m[:
|
123
|
+
m[:key] = m[:payload][:key]
|
124
|
+
m[:key] = m[:key].to_h if m[:key].nil? || m[:key].is_a?(SchemaClass::Record)
|
125
|
+
m[:payload] = m[:payload][:message]
|
126
|
+
m[:payload] = m[:payload].to_h if m[:payload].nil? ||
|
127
|
+
m[:payload].is_a?(SchemaClass::Record)
|
123
128
|
end
|
124
129
|
m
|
125
130
|
end
|
@@ -18,6 +18,15 @@ module Deimos
|
|
18
18
|
@schema_store = AvroTurf::MutableSchemaStore.new(path: Deimos.config.schema.path)
|
19
19
|
end
|
20
20
|
|
21
|
+
def supports_key_schemas?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean]
|
26
|
+
def supports_class_generation?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
21
30
|
# @override
|
22
31
|
def encode_key(key_id, key, topic: nil)
|
23
32
|
begin
|
@@ -21,7 +21,7 @@ module Deimos
|
|
21
21
|
|
22
22
|
# @return [AvroTurf::Messaging]
|
23
23
|
def avro_turf_messaging
|
24
|
-
|
24
|
+
@avro_turf_messaging ||= AvroTurf::Messaging.new(
|
25
25
|
schema_store: @schema_store,
|
26
26
|
registry_url: Deimos.config.schema.registry_url,
|
27
27
|
schemas_path: Deimos.config.schema.path,
|
@@ -41,6 +41,16 @@ module Deimos
|
|
41
41
|
@namespace = namespace
|
42
42
|
end
|
43
43
|
|
44
|
+
# @return [Boolean]
|
45
|
+
def supports_key_schemas?
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Boolean]
|
50
|
+
def supports_class_generation?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
44
54
|
# Encode a payload with a schema. Public method.
|
45
55
|
# @param payload [Hash]
|
46
56
|
# @param schema [String,Symbol]
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'proto_turf'
|
5
|
+
|
6
|
+
module Deimos
|
7
|
+
module SchemaBackends
|
8
|
+
# Encode / decode using Avro, either locally or via schema registry.
|
9
|
+
class ProtoBase < Base
|
10
|
+
SQL_MAP = {
|
11
|
+
string: :string,
|
12
|
+
int32: :integer,
|
13
|
+
uint32: :integer,
|
14
|
+
sint32: :integer,
|
15
|
+
fixed32: :integer,
|
16
|
+
sfixed32: :integer,
|
17
|
+
int64: :bigint,
|
18
|
+
uint64: :bigint,
|
19
|
+
sint64: :bigint,
|
20
|
+
fixed64: :bigint,
|
21
|
+
sfixed64: :bigint,
|
22
|
+
bool: :boolean,
|
23
|
+
bytes: :string,
|
24
|
+
float: :float,
|
25
|
+
message: :record
|
26
|
+
}
|
27
|
+
def proto_schema(schema=@schema)
|
28
|
+
Google::Protobuf::DescriptorPool.generated_pool.lookup(schema)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @override
|
32
|
+
def encode_key(key_id, key, topic: nil)
|
33
|
+
if key.is_a?(Hash)
|
34
|
+
key_id ? key[key_id].to_s : key.sort.to_h.to_json
|
35
|
+
else
|
36
|
+
key.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @override
|
41
|
+
def decode_key(payload, key_id)
|
42
|
+
val = JSON.parse(payload) rescue payload
|
43
|
+
key_id ? val[key_id.to_s] : val
|
44
|
+
end
|
45
|
+
|
46
|
+
# :nodoc:
|
47
|
+
def sql_type(field)
|
48
|
+
type = field.type
|
49
|
+
return SQL_MAP[type] if SQL_MAP[type]
|
50
|
+
return :array if type.repeated?
|
51
|
+
|
52
|
+
if type == :double
|
53
|
+
warn('Protobuf `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
|
+
def coerce(payload)
|
61
|
+
payload
|
62
|
+
end
|
63
|
+
|
64
|
+
# @override
|
65
|
+
def coerce_field(field, value)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @override
|
69
|
+
def schema_fields
|
70
|
+
proto_schema.to_a.map do |f|
|
71
|
+
SchemaField.new(f.name, f.subtype&.name || 'record', [], nil)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @override
|
76
|
+
def validate(payload, schema:)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @override
|
80
|
+
def self.mock_backend
|
81
|
+
:mock
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate_key_schema(field_name)
|
85
|
+
raise 'Protobuf cannot generate key schemas! Please use field_config :plain'
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'proto_base'
|
4
|
+
require 'proto_turf'
|
5
|
+
|
6
|
+
module Deimos
|
7
|
+
module SchemaBackends
|
8
|
+
# Encode / decode using the Protobuf schema registry.
|
9
|
+
class ProtoSchemaRegistry < ProtoBase
|
10
|
+
|
11
|
+
# @override
|
12
|
+
def decode_payload(payload, schema:)
|
13
|
+
self.class.proto_turf.decode(payload)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @override
|
17
|
+
def encode_payload(payload, schema: nil, topic: nil)
|
18
|
+
msg = payload.is_a?(Hash) ? proto_schema.msgclass.new(**payload) : payload
|
19
|
+
self.class.proto_turf.encode(msg, subject: topic)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# @return [ProtoTurf]
|
25
|
+
def self.proto_turf
|
26
|
+
@proto_turf ||= ProtoTurf.new(
|
27
|
+
registry_url: Deimos.config.schema.registry_url,
|
28
|
+
logger: Karafka.logger
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/deimos/transcoder.rb
CHANGED
@@ -7,18 +7,22 @@ module Deimos
|
|
7
7
|
# @param namespace [String]
|
8
8
|
# @param key_field [Symbol]
|
9
9
|
# @param use_schema_classes [Boolean]
|
10
|
+
# @param backend [Symbol]
|
10
11
|
# @param topic [String]
|
11
|
-
def initialize(schema:, namespace:, key_field: nil, use_schema_classes: nil, topic: nil)
|
12
|
+
def initialize(schema:, namespace:, key_field: nil, use_schema_classes: nil, topic: nil, backend: nil)
|
12
13
|
@schema = schema
|
13
14
|
@namespace = namespace
|
14
15
|
self.key_field = key_field
|
15
16
|
@use_schema_classes = use_schema_classes
|
17
|
+
@backend_type = backend
|
16
18
|
@topic = topic
|
17
19
|
end
|
18
20
|
|
19
21
|
# @return [Class < Deimos::SchemaBackends::Base]
|
20
22
|
def backend
|
21
|
-
@backend ||= Deimos.schema_backend(schema: @schema,
|
23
|
+
@backend ||= Deimos.schema_backend(schema: @schema,
|
24
|
+
namespace: @namespace,
|
25
|
+
backend: @backend_type)
|
22
26
|
end
|
23
27
|
|
24
28
|
# for use in test helpers
|
data/lib/deimos/version.rb
CHANGED
data/lib/deimos.rb
CHANGED
@@ -54,32 +54,34 @@ module Deimos
|
|
54
54
|
|
55
55
|
class << self
|
56
56
|
|
57
|
+
# @param backend [Symbol, nil]
|
57
58
|
# @return [Class<Deimos::SchemaBackends::Base>]
|
58
|
-
def schema_backend_class
|
59
|
-
backend
|
59
|
+
def schema_backend_class(backend: nil)
|
60
|
+
backend ||= Deimos.config.schema.backend
|
60
61
|
|
61
62
|
require "deimos/schema_backends/#{backend}"
|
62
63
|
|
63
|
-
"Deimos::SchemaBackends::#{backend.classify}".constantize
|
64
|
+
"Deimos::SchemaBackends::#{backend.to_s.classify}".constantize
|
64
65
|
end
|
65
66
|
|
66
67
|
# @param schema [String, Symbol]
|
67
68
|
# @param namespace [String]
|
68
69
|
# @return [Deimos::SchemaBackends::Base]
|
69
|
-
def schema_backend(schema:, namespace:)
|
70
|
+
def schema_backend(schema:, namespace:, backend: Deimos.config.schema.backend)
|
70
71
|
if config.schema.use_schema_classes
|
71
72
|
# Initialize an instance of the provided schema
|
72
73
|
# in the event the schema class is an override, the inherited
|
73
74
|
# schema and namespace will be applied
|
74
75
|
schema_class = Utils::SchemaClass.klass(schema, namespace)
|
75
76
|
if schema_class.nil?
|
76
|
-
schema_backend_class.new(schema: schema, namespace: namespace)
|
77
|
+
schema_backend_class(backend: backend).new(schema: schema, namespace: namespace)
|
77
78
|
else
|
78
79
|
schema_instance = schema_class.allocate
|
79
|
-
schema_backend_class
|
80
|
+
schema_backend_class(backend: backend).
|
81
|
+
new(schema: schema_instance.schema, namespace: schema_instance.namespace)
|
80
82
|
end
|
81
83
|
else
|
82
|
-
schema_backend_class.new(schema: schema, namespace: namespace)
|
84
|
+
schema_backend_class(backend: backend).new(schema: schema, namespace: namespace)
|
83
85
|
end
|
84
86
|
end
|
85
87
|
|
@@ -48,11 +48,17 @@ module Deimos
|
|
48
48
|
# @param namespace [String]
|
49
49
|
# @param key_config [Hash,nil]
|
50
50
|
# @return [void]
|
51
|
-
def generate_classes(schema_name, namespace, key_config)
|
52
|
-
schema_base = Deimos.schema_backend(schema: schema_name,
|
51
|
+
def generate_classes(schema_name, namespace, key_config, backend: nil)
|
52
|
+
schema_base = Deimos.schema_backend(schema: schema_name,
|
53
|
+
namespace: namespace,
|
54
|
+
backend: backend)
|
55
|
+
return unless schema_base.supports_class_generation?
|
56
|
+
|
53
57
|
schema_base.load_schema
|
54
58
|
if key_config&.dig(:schema)
|
55
|
-
key_schema_base = Deimos.schema_backend(schema: key_config[:schema],
|
59
|
+
key_schema_base = Deimos.schema_backend(schema: key_config[:schema],
|
60
|
+
namespace: namespace,
|
61
|
+
backend: backend)
|
56
62
|
key_schema_base.load_schema
|
57
63
|
generate_class_from_schema_base(key_schema_base, key_config: nil)
|
58
64
|
end
|
@@ -174,7 +180,7 @@ module Deimos
|
|
174
180
|
|
175
181
|
found_schemas["#{namespace}.#{schema_name}"] = key_schema_name
|
176
182
|
found_schemas["#{namespace}.#{key_schema_name}"] = nil
|
177
|
-
generate_classes(schema_name, namespace, config.key_config)
|
183
|
+
generate_classes(schema_name, namespace, config.key_config, backend: config.schema_backend)
|
178
184
|
end
|
179
185
|
|
180
186
|
generate_from_schema_files(found_schemas.keys)
|
@@ -184,7 +190,8 @@ module Deimos
|
|
184
190
|
private
|
185
191
|
|
186
192
|
def generate_from_schema_files(found_schemas)
|
187
|
-
|
193
|
+
path = Deimos.config.schema.path || Deimos.config.schema.paths[:avro].first
|
194
|
+
schema_store = AvroTurf::MutableSchemaStore.new(path: path)
|
188
195
|
schema_store.load_schemas!
|
189
196
|
schema_store.schemas.values.sort_by { |s| "#{s.namespace}#{s.name}" }.each do |schema|
|
190
197
|
name = "#{schema.namespace}.#{schema.name}"
|
@@ -269,7 +276,11 @@ module Deimos
|
|
269
276
|
default = field.default
|
270
277
|
return ' nil' if default == :no_default || default.nil? || IGNORE_DEFAULTS.include?(field.name)
|
271
278
|
|
272
|
-
|
279
|
+
type_sym = field.type.type_sym
|
280
|
+
if type_sym == :union
|
281
|
+
type_sym = field.type.schemas.find { |s| s.type_sym != :null }&.type_sym
|
282
|
+
end
|
283
|
+
case type_sym
|
273
284
|
when :string, :enum
|
274
285
|
" \"#{default}\""
|
275
286
|
when :record
|