deimos-ruby 2.1.13 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f53dee42dc005e4b2d17f8e88f0bcd358d3985bddd595af90c05727b98ad47b6
4
- data.tar.gz: 56e3494c8c255d223b79214a78766a4140cda5fc0355fe3cbc38cf414fd99027
3
+ metadata.gz: 5e3d172790c33077ab5f0dc188a8a28b9bb7050cb612bbac833111e7e1caafbe
4
+ data.tar.gz: 89040d16c763b278cb0dee57c609f3d9386e00d0d8d8a9f331b2b56b22600752
5
5
  SHA512:
6
- metadata.gz: d0672ef183346354f9df0a4ea3104ea330c52bb25ebc5b3daa373fc97a64be50a242f0e686e1dca7417a083401c1fc0dac9270e651e127a447a46660b48c58e3
7
- data.tar.gz: efbf308dd6ec940af43091747c5420bfa95873d20b6f7c1b107af13f225448a191d7016850b4f0baac3b20552d6a8c54c73a22b49bb8b4f48e0a753c2dd4b025
6
+ metadata.gz: 20e247295e0724264d1db8dbd32a9267663acbbbf6478c32a78333a742b2538195a353bf0892a3ac5f0ea1e27f6dead3836af551e40e05c1f54071b76ba3cf3e
7
+ data.tar.gz: cdec52113639f6cf7c5c79671386ffd103bbfb16ebf2500913e2015789af8416117ec8b69923fcdc7804cd29a3efe887415e19a4d452443afa2d81649458b028
@@ -36,7 +36,7 @@ jobs:
36
36
  strategy:
37
37
  fail-fast: false
38
38
  matrix:
39
- ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4' ]
39
+ ruby: [ '3.2', '3.3', '3.4' ]
40
40
 
41
41
  steps:
42
42
  - uses: actions/checkout@v3
data/CHANGELOG.md CHANGED
@@ -7,6 +7,9 @@ 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
+
10
13
  # 2.1.13 - 2025-10-03
11
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.
12
15
 
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 was originally written only supporting Avro encoding via a schema registry.
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 Avro-encoding, you must include the [avro_turf](https://github.com/dasch/avro_turf) gem in your
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 include [Protobuf](https://developers.google.com/protocol-buffers), [JSONSchema](https://json-schema.org/), etc. Feel free to
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')
@@ -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
- record = fetch_record(klass, message.payload.to_h.with_indifferent_access, message.key)
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.generate_key_schema(transcoder.key_field)
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
- Deimos::Logging.log_warn(
47
- message: 'Error consuming message',
48
- handler: self.class.name,
49
- metadata: Deimos::Logging.metadata_log_text(message.metadata),
50
- key: message.key,
51
- data: message.payload,
52
- error_message: exception.message,
53
- error: exception.backtrace
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] && !message[:payload].is_a?(Hash) && !message[:payload].is_a?(SchemaClass::Record)
25
+ return if message[:payload] &&
26
+ self.allowed_classes.none? { |k| message[:payload].is_a?(k) }
17
27
 
18
- m = Deimos::Message.new(message[:payload].to_h,
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.delete(:payload_key)
105
+ key = payload.try(:delete, :payload_key)
94
106
  return key if key || !key_transcoder.respond_to?(:key_field)
95
107
 
96
- key_transcoder.key_field ? payload[key_transcoder.key_field] : nil
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
@@ -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&.with_indifferent_access
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
@@ -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: p&.to_h,
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].to_h
122
- m[:payload] = m[:payload][:message].to_h
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
- @avro_turf_messaging ||= AvroTurf::Messaging.new(
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
@@ -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, namespace: @namespace)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '2.1.13'
4
+ VERSION = '2.2.0-beta1'
5
5
  end
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 = Deimos.config.schema.backend.to_s
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.new(schema: schema_instance.schema, namespace: schema_instance.namespace)
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, namespace: namespace)
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], namespace: namespace)
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
- schema_store = AvroTurf::MutableSchemaStore.new(path: Deimos.config.schema.path)
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}"
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: sample/v1/sample.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+ require 'google/protobuf/timestamp_pb'
8
+
9
+
10
+ descriptor_data = "\n\x16sample/v1/sample.proto\x12\tsample.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"7\n\rNestedMessage\x12\x12\n\nnested_str\x18\x01 \x01(\t\x12\x12\n\nnested_num\x18\x02 \x01(\x05\"\xdb\x02\n\rSampleMessage\x12\x0b\n\x03str\x18\x01 \x01(\t\x12\x0b\n\x03num\x18\x02 \x01(\x05\x12\x0f\n\x07str_arr\x18\x03 \x03(\t\x12\x0c\n\x04\x66lag\x18\x04 \x01(\x08\x12-\n\ttimestamp\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12*\n\x06nested\x18\x06 \x01(\x0b\x32\x18.sample.v1.NestedMessageH\x00\x12\x13\n\tunion_str\x18\x07 \x01(\tH\x00\x12\x32\n\x10non_union_nested\x18\x08 \x01(\x0b\x32\x18.sample.v1.NestedMessage\x12\x35\n\x07str_map\x18\t \x03(\x0b\x32$.sample.v1.SampleMessage.StrMapEntry\x1a-\n\x0bStrMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x07\n\x05unionb\x06proto3"
11
+
12
+ pool = Google::Protobuf::DescriptorPool.generated_pool
13
+ pool.add_serialized_file(descriptor_data)
14
+
15
+ module Sample
16
+ module V1
17
+ NestedMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("sample.v1.NestedMessage").msgclass
18
+ SampleMessage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("sample.v1.SampleMessage").msgclass
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ syntax = "proto3";
2
+
3
+ package sample.v1;
4
+
5
+ import "google/protobuf/timestamp.proto";
6
+
7
+ message NestedMessage {
8
+ string nested_str = 1;
9
+ int32 nested_num = 2;
10
+ }
11
+
12
+ message SampleMessage {
13
+ string str = 1;
14
+ int32 num = 2;
15
+ repeated string str_arr = 3;
16
+ bool flag = 4;
17
+ google.protobuf.Timestamp timestamp = 5;
18
+ oneof union {
19
+ NestedMessage nested = 6;
20
+ string union_str = 7;
21
+ }
22
+ NestedMessage non_union_nested = 8;
23
+ map<string, string> str_map = 9;
24
+ }
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deimos/schema_backends/proto_schema_registry'
4
+ require_relative "#{__dir__}/../gen/sample/v1/sample_pb"
5
+
6
+ RSpec.describe Deimos::SchemaBackends::ProtoSchemaRegistry do
7
+ let(:payload) do
8
+ Sample::V1::SampleMessage.new(
9
+ str: "string",
10
+ num: 123,
11
+ str_arr: %w[one two],
12
+ flag: true,
13
+ timestamp: Time.utc(2017, 1, 1),
14
+ nested: Sample::V1::NestedMessage.new(nested_str: "string"),
15
+ non_union_nested: Sample::V1::NestedMessage.new(nested_num: 456),
16
+ str_map: {'foo' => 'bar'}
17
+ )
18
+ end
19
+
20
+ let(:backend) { described_class.new(schema: 'sample.v1.SampleMessage') }
21
+
22
+ specify('#encode_key') do
23
+ expect(backend.encode_key(nil, 789)).to eq('789')
24
+ expect(backend.encode_key(nil, 'string')).to eq('string')
25
+ expect(backend.encode_key(nil, {foo: 'bar'})).to eq('{"foo":"bar"}')
26
+ expect(backend.encode_key(:foo, 'bar')).to eq('bar')
27
+ end
28
+
29
+ specify('#decode_key') do
30
+ expect(backend.decode_key('789', nil)).to eq(789)
31
+ expect(backend.decode_key('{"foo":"bar"}', :foo)).to eq('bar')
32
+ expect(backend.decode_key('{"foo":"bar"}', nil)).to eq({"foo" => 'bar'})
33
+ end
34
+
35
+ it 'should encode and decode correctly' do
36
+ proto_turf = instance_double(ProtoTurf)
37
+ expect(proto_turf).to receive(:encode).
38
+ with(payload, subject: 'topic').
39
+ and_return('encoded-payload')
40
+ expect(proto_turf).to receive(:decode).
41
+ with('encoded-payload').
42
+ and_return(payload)
43
+ allow(described_class).to receive(:proto_turf).and_return(proto_turf)
44
+ results = backend.encode(payload, topic: 'topic')
45
+ expect(results).to eq('encoded-payload')
46
+ results = backend.decode(results)
47
+ expect(results).to eq(payload)
48
+ end
49
+
50
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.13
4
+ version: 2.2.0.pre.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
@@ -161,6 +161,20 @@ dependencies:
161
161
  - - ">="
162
162
  - !ruby/object:Gem::Version
163
163
  version: '4.2'
164
+ - !ruby/object:Gem::Dependency
165
+ name: proto_turf
166
+ requirement: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ type: :development
172
+ prerelease: false
173
+ version_requirements: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
164
178
  - !ruby/object:Gem::Dependency
165
179
  name: guard
166
180
  requirement: !ruby/object:Gem::Requirement
@@ -483,6 +497,8 @@ files:
483
497
  - lib/deimos/schema_backends/base.rb
484
498
  - lib/deimos/schema_backends/mock.rb
485
499
  - lib/deimos/schema_backends/plain.rb
500
+ - lib/deimos/schema_backends/proto_base.rb
501
+ - lib/deimos/schema_backends/proto_schema_registry.rb
486
502
  - lib/deimos/schema_class/base.rb
487
503
  - lib/deimos/schema_class/enum.rb
488
504
  - lib/deimos/schema_class/record.rb
@@ -539,6 +555,7 @@ files:
539
555
  - spec/batch_consumer_spec.rb
540
556
  - spec/consumer_spec.rb
541
557
  - spec/deimos_spec.rb
558
+ - spec/gen/sample/v1/sample_pb.rb
542
559
  - spec/generators/active_record_generator_spec.rb
543
560
  - spec/generators/schema_class/my_schema_spec.rb
544
561
  - spec/generators/schema_class/my_schema_with_circular_reference_spec.rb
@@ -553,12 +570,14 @@ files:
553
570
  - spec/logging_spec.rb
554
571
  - spec/message_spec.rb
555
572
  - spec/producer_spec.rb
573
+ - spec/protos/sample/v1/sample.proto
556
574
  - spec/rake_spec.rb
557
575
  - spec/schema_backends/avro_base_shared.rb
558
576
  - spec/schema_backends/avro_local_spec.rb
559
577
  - spec/schema_backends/avro_schema_registry_spec.rb
560
578
  - spec/schema_backends/avro_validation_spec.rb
561
579
  - spec/schema_backends/base_spec.rb
580
+ - spec/schema_backends/proto_schema_registry_spec.rb
562
581
  - spec/schemas/com/my-namespace/Generated.avsc
563
582
  - spec/schemas/com/my-namespace/MyNestedSchema.avsc
564
583
  - spec/schemas/com/my-namespace/MySchema.avsc
@@ -669,6 +688,7 @@ test_files:
669
688
  - spec/batch_consumer_spec.rb
670
689
  - spec/consumer_spec.rb
671
690
  - spec/deimos_spec.rb
691
+ - spec/gen/sample/v1/sample_pb.rb
672
692
  - spec/generators/active_record_generator_spec.rb
673
693
  - spec/generators/schema_class/my_schema_spec.rb
674
694
  - spec/generators/schema_class/my_schema_with_circular_reference_spec.rb
@@ -683,12 +703,14 @@ test_files:
683
703
  - spec/logging_spec.rb
684
704
  - spec/message_spec.rb
685
705
  - spec/producer_spec.rb
706
+ - spec/protos/sample/v1/sample.proto
686
707
  - spec/rake_spec.rb
687
708
  - spec/schema_backends/avro_base_shared.rb
688
709
  - spec/schema_backends/avro_local_spec.rb
689
710
  - spec/schema_backends/avro_schema_registry_spec.rb
690
711
  - spec/schema_backends/avro_validation_spec.rb
691
712
  - spec/schema_backends/base_spec.rb
713
+ - spec/schema_backends/proto_schema_registry_spec.rb
692
714
  - spec/schemas/com/my-namespace/Generated.avsc
693
715
  - spec/schemas/com/my-namespace/MyNestedSchema.avsc
694
716
  - spec/schemas/com/my-namespace/MySchema.avsc