deimos-ruby 1.16.1 → 1.16.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +6 -0
- data/README.md +22 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +7 -2
- data/lib/deimos/active_record_consume/batch_slicer.rb +2 -0
- data/lib/deimos/active_record_consume/message_consumption.rb +8 -4
- data/lib/deimos/active_record_consumer.rb +7 -4
- data/lib/deimos/active_record_producer.rb +10 -0
- data/lib/deimos/backends/base.rb +4 -2
- data/lib/deimos/backends/kafka.rb +1 -0
- data/lib/deimos/backends/kafka_async.rb +1 -0
- data/lib/deimos/config/configuration.rb +4 -0
- data/lib/deimos/config/phobos_config.rb +2 -1
- data/lib/deimos/consume/batch_consumption.rb +8 -1
- data/lib/deimos/consume/message_consumption.rb +4 -1
- data/lib/deimos/instrumentation.rb +11 -4
- data/lib/deimos/kafka_message.rb +1 -0
- data/lib/deimos/kafka_source.rb +5 -0
- data/lib/deimos/kafka_topic_info.rb +4 -0
- data/lib/deimos/message.rb +19 -2
- data/lib/deimos/metrics/datadog.rb +2 -1
- data/lib/deimos/metrics/mock.rb +2 -2
- data/lib/deimos/metrics/provider.rb +6 -0
- data/lib/deimos/monkey_patches/phobos_cli.rb +1 -1
- data/lib/deimos/monkey_patches/phobos_producer.rb +1 -0
- data/lib/deimos/producer.rb +12 -6
- data/lib/deimos/schema_backends/base.rb +31 -17
- data/lib/deimos/schema_backends/mock.rb +2 -2
- data/lib/deimos/schema_class/base.rb +9 -5
- data/lib/deimos/schema_class/enum.rb +4 -2
- data/lib/deimos/schema_class/record.rb +12 -6
- data/lib/deimos/shared_config.rb +6 -2
- data/lib/deimos/test_helpers.rb +21 -4
- data/lib/deimos/tracing/datadog.rb +1 -1
- data/lib/deimos/tracing/mock.rb +4 -3
- data/lib/deimos/tracing/provider.rb +5 -0
- data/lib/deimos/utils/db_poller.rb +9 -1
- data/lib/deimos/utils/db_producer.rb +14 -2
- data/lib/deimos/utils/deadlock_retry.rb +3 -0
- data/lib/deimos/utils/inline_consumer.rb +14 -6
- data/lib/deimos/utils/lag_reporter.rb +11 -0
- data/lib/deimos/utils/schema_controller_mixin.rb +8 -0
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +3 -2
- data/lib/generators/deimos/active_record_generator.rb +1 -1
- data/lib/generators/deimos/db_backend_generator.rb +1 -0
- data/lib/generators/deimos/db_poller_generator.rb +1 -0
- data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +13 -5
- data/lib/generators/deimos/schema_class_generator.rb +51 -24
- data/rbs_collection.lock.yaml +176 -0
- data/rbs_collection.yaml +15 -0
- data/regenerate_test_schema_classes.rb +68 -0
- data/sig/avro.rbs +14 -0
- data/sig/defs.rbs +1859 -0
- data/sig/fig_tree.rbs +2 -0
- data/spec/active_record_producer_spec.rb +24 -0
- data/spec/generators/schema_class/my_schema_spec.rb +16 -0
- data/spec/generators/schema_class/my_schema_with_circular_reference_spec.rb +1 -1
- data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +32 -24
- data/spec/producer_spec.rb +11 -11
- data/spec/schemas/{generated.rb → my_namespace/generated.rb} +28 -32
- data/spec/schemas/{my_nested_schema.rb → my_namespace/my_nested_schema.rb} +23 -16
- data/spec/schemas/{my_schema.rb → my_namespace/my_schema.rb} +12 -5
- data/spec/schemas/my_namespace/my_schema_compound_key.rb +41 -0
- data/spec/schemas/my_namespace/my_schema_id_key.rb +36 -0
- data/spec/schemas/{my_schema_key.rb → my_namespace/my_schema_key.rb} +3 -3
- data/spec/schemas/my_namespace/my_schema_with_boolean.rb +41 -0
- data/spec/schemas/{my_schema_with_circular_reference.rb → my_namespace/my_schema_with_circular_reference.rb} +15 -10
- data/spec/schemas/{my_schema_with_complex_type.rb → my_namespace/my_schema_with_complex_type.rb} +34 -49
- data/spec/schemas/my_namespace/my_schema_with_date_time.rb +56 -0
- data/spec/schemas/my_namespace/my_schema_with_id.rb +51 -0
- data/spec/schemas/my_namespace/my_schema_with_unique_id.rb +56 -0
- data/spec/schemas/my_namespace/wibble.rb +76 -0
- data/spec/schemas/my_namespace/widget.rb +56 -0
- data/spec/schemas/my_namespace/widget_the_second.rb +56 -0
- data/spec/schemas/request/create_topic.rb +36 -0
- data/spec/schemas/request/index.rb +36 -0
- data/spec/schemas/request/update_request.rb +36 -0
- data/spec/schemas/response/create_topic.rb +36 -0
- data/spec/schemas/response/index.rb +36 -0
- data/spec/schemas/response/update_response.rb +36 -0
- data/spec/snapshots/consumers-no-nest.snap +93 -86
- data/spec/snapshots/consumers.snap +94 -87
- data/spec/snapshots/consumers_and_producers-no-nest.snap +108 -87
- data/spec/snapshots/consumers_and_producers.snap +109 -88
- data/spec/snapshots/consumers_circular-no-nest.snap +93 -86
- data/spec/snapshots/consumers_circular.snap +94 -87
- data/spec/snapshots/consumers_complex_types-no-nest.snap +93 -86
- data/spec/snapshots/consumers_complex_types.snap +94 -87
- data/spec/snapshots/consumers_nested-no-nest.snap +93 -86
- data/spec/snapshots/consumers_nested.snap +94 -87
- data/spec/snapshots/namespace_folders.snap +111 -90
- data/spec/snapshots/producers_with_key-no-nest.snap +94 -87
- data/spec/snapshots/producers_with_key.snap +95 -88
- data/spec/spec_helper.rb +2 -1
- metadata +53 -15
@@ -8,8 +8,11 @@ module Deimos
|
|
8
8
|
include Phobos::Producer
|
9
9
|
attr_accessor :id, :current_topic
|
10
10
|
|
11
|
+
# @return [Integer]
|
11
12
|
BATCH_SIZE = 1000
|
13
|
+
# @return [Integer]
|
12
14
|
DELETE_BATCH_SIZE = 10
|
15
|
+
# @return [Integer]
|
13
16
|
MAX_DELETE_ATTEMPTS = 3
|
14
17
|
|
15
18
|
# @param logger [Logger]
|
@@ -19,12 +22,13 @@ module Deimos
|
|
19
22
|
@logger.push_tags("DbProducer #{@id}") if @logger.respond_to?(:push_tags)
|
20
23
|
end
|
21
24
|
|
22
|
-
# @return [
|
25
|
+
# @return [FigTree]
|
23
26
|
def config
|
24
27
|
Deimos.config.db_producer
|
25
28
|
end
|
26
29
|
|
27
30
|
# Start the poll.
|
31
|
+
# @return [void]
|
28
32
|
def start
|
29
33
|
@logger.info('Starting...')
|
30
34
|
@signal_to_stop = false
|
@@ -40,12 +44,14 @@ module Deimos
|
|
40
44
|
end
|
41
45
|
|
42
46
|
# Stop the poll.
|
47
|
+
# @return [void]
|
43
48
|
def stop
|
44
49
|
@logger.info('Received signal to stop')
|
45
50
|
@signal_to_stop = true
|
46
51
|
end
|
47
52
|
|
48
53
|
# Complete one loop of processing all messages in the DB.
|
54
|
+
# @return [void]
|
49
55
|
def process_next_messages
|
50
56
|
topics = retrieve_topics
|
51
57
|
@logger.info("Found topics: #{topics}")
|
@@ -80,6 +86,7 @@ module Deimos
|
|
80
86
|
end
|
81
87
|
|
82
88
|
# Process a single batch in a topic.
|
89
|
+
# @return [void]
|
83
90
|
def process_topic_batch
|
84
91
|
messages = retrieve_messages
|
85
92
|
return false if messages.empty?
|
@@ -111,6 +118,7 @@ module Deimos
|
|
111
118
|
end
|
112
119
|
|
113
120
|
# @param messages [Array<Deimos::KafkaMessage>]
|
121
|
+
# @return [void]
|
114
122
|
def delete_messages(messages)
|
115
123
|
attempts = 1
|
116
124
|
begin
|
@@ -137,6 +145,7 @@ module Deimos
|
|
137
145
|
end
|
138
146
|
|
139
147
|
# @param messages [Array<Deimos::KafkaMessage>]
|
148
|
+
# @return [void]
|
140
149
|
def log_messages(messages)
|
141
150
|
return if config.log_topics != :all && !config.log_topics.include?(@current_topic)
|
142
151
|
|
@@ -146,7 +155,8 @@ module Deimos
|
|
146
155
|
end
|
147
156
|
end
|
148
157
|
|
149
|
-
# Send metrics to
|
158
|
+
# Send metrics related to pending messages.
|
159
|
+
# @return [void]
|
150
160
|
def send_pending_metrics
|
151
161
|
metrics = Deimos.config.metrics
|
152
162
|
return unless metrics
|
@@ -185,6 +195,7 @@ module Deimos
|
|
185
195
|
# Shut down the sync producer if we have to. Phobos will automatically
|
186
196
|
# create a new one. We should call this if the producer can be in a bad
|
187
197
|
# state and e.g. we need to clear the buffer.
|
198
|
+
# @return [void]
|
188
199
|
def shutdown_producer
|
189
200
|
if self.class.producer.respond_to?(:sync_producer_shutdown) # Phobos 1.8.3
|
190
201
|
self.class.producer.sync_producer_shutdown
|
@@ -194,6 +205,7 @@ module Deimos
|
|
194
205
|
# Produce messages in batches, reducing the size 1/10 if the batch is too
|
195
206
|
# large. Does not retry batches of messages that have already been sent.
|
196
207
|
# @param batch [Array<Hash>]
|
208
|
+
# @return [void]
|
197
209
|
def produce_messages(batch)
|
198
210
|
batch_size = batch.size
|
199
211
|
current_index = 0
|
@@ -7,9 +7,11 @@ module Deimos
|
|
7
7
|
class DeadlockRetry
|
8
8
|
class << self
|
9
9
|
# Maximum number of times to retry the block after encountering a deadlock
|
10
|
+
# @return [Integer]
|
10
11
|
RETRY_COUNT = 2
|
11
12
|
|
12
13
|
# Need to match on error messages to support older Rails versions
|
14
|
+
# @return [Array<String>]
|
13
15
|
DEADLOCK_MESSAGES = [
|
14
16
|
# MySQL
|
15
17
|
'Deadlock found when trying to get lock',
|
@@ -28,6 +30,7 @@ module Deimos
|
|
28
30
|
# from retrying at the same time.
|
29
31
|
# @param tags [Array] Tags to attach when logging and reporting metrics.
|
30
32
|
# @yield Yields to the block that may deadlock.
|
33
|
+
# @return [void]
|
31
34
|
def wrap(tags=[])
|
32
35
|
count = RETRY_COUNT
|
33
36
|
|
@@ -6,10 +6,12 @@ module Deimos
|
|
6
6
|
module Utils
|
7
7
|
# Listener that can seek to get the last X messages in a topic.
|
8
8
|
class SeekListener < Phobos::Listener
|
9
|
+
# @return [Integer]
|
9
10
|
MAX_SEEK_RETRIES = 3
|
11
|
+
# @return [Integer]
|
10
12
|
attr_accessor :num_messages
|
11
13
|
|
12
|
-
#
|
14
|
+
# @return [void]
|
13
15
|
def start_listener
|
14
16
|
@num_messages ||= 10
|
15
17
|
@consumer = create_kafka_consumer
|
@@ -45,17 +47,20 @@ module Deimos
|
|
45
47
|
|
46
48
|
cattr_accessor :total_messages
|
47
49
|
|
48
|
-
# @param klass [Class
|
50
|
+
# @param klass [Class<Deimos::Consumer>]
|
51
|
+
# @return [void]
|
49
52
|
def self.config_class=(klass)
|
50
53
|
self.config.merge!(klass.config)
|
51
54
|
end
|
52
55
|
|
53
|
-
#
|
56
|
+
# @param _kafka_client [Kafka::Client]
|
57
|
+
# @return [void]
|
54
58
|
def self.start(_kafka_client)
|
55
59
|
self.total_messages = []
|
56
60
|
end
|
57
61
|
|
58
|
-
#
|
62
|
+
# @param payload [Hash]
|
63
|
+
# @param metadata [Hash]
|
59
64
|
def consume(payload, metadata)
|
60
65
|
self.class.total_messages << {
|
61
66
|
key: metadata[:key],
|
@@ -66,18 +71,20 @@ module Deimos
|
|
66
71
|
|
67
72
|
# Class which can process/consume messages inline.
|
68
73
|
class InlineConsumer
|
74
|
+
# @return [Integer]
|
69
75
|
MAX_MESSAGE_WAIT_TIME = 1.second
|
76
|
+
# @return [Integer]
|
70
77
|
MAX_TOPIC_WAIT_TIME = 10.seconds
|
71
78
|
|
72
79
|
# Get the last X messages from a topic. You can specify a subclass of
|
73
80
|
# Deimos::Consumer or Deimos::Producer, or provide the
|
74
81
|
# schema, namespace and key_config directly.
|
75
82
|
# @param topic [String]
|
76
|
-
# @param config_class [Class
|
83
|
+
# @param config_class [Class<Deimos::Consumer>,Class<Deimos::Producer>]
|
77
84
|
# @param schema [String]
|
78
85
|
# @param namespace [String]
|
79
86
|
# @param key_config [Hash]
|
80
|
-
# @param num_messages [
|
87
|
+
# @param num_messages [Integer]
|
81
88
|
# @return [Array<Hash>]
|
82
89
|
def self.get_messages_for(topic:, schema: nil, namespace: nil, key_config: nil,
|
83
90
|
config_class: nil, num_messages: 10)
|
@@ -106,6 +113,7 @@ module Deimos
|
|
106
113
|
# @param frk_consumer [Class]
|
107
114
|
# @param num_messages [Integer] If this number is >= the number
|
108
115
|
# of messages in the topic, all messages will be consumed.
|
116
|
+
# @return [void]
|
109
117
|
def self.consume(topic:, frk_consumer:, num_messages: 10)
|
110
118
|
listener = SeekListener.new(
|
111
119
|
handler: frk_consumer,
|
@@ -24,6 +24,7 @@ module Deimos
|
|
24
24
|
|
25
25
|
# @param topic [String]
|
26
26
|
# @param partition [Integer]
|
27
|
+
# @return [void]
|
27
28
|
def report_lag(topic, partition)
|
28
29
|
self.topics[topic.to_s] ||= Topic.new(topic, self)
|
29
30
|
self.topics[topic.to_s].report_lag(partition)
|
@@ -32,6 +33,7 @@ module Deimos
|
|
32
33
|
# @param topic [String]
|
33
34
|
# @param partition [Integer]
|
34
35
|
# @param offset [Integer]
|
36
|
+
# @return [void]
|
35
37
|
def assign_current_offset(topic, partition, offset)
|
36
38
|
self.topics[topic.to_s] ||= Topic.new(topic, self)
|
37
39
|
self.topics[topic.to_s].assign_current_offset(partition, offset)
|
@@ -56,11 +58,15 @@ module Deimos
|
|
56
58
|
end
|
57
59
|
|
58
60
|
# @param partition [Integer]
|
61
|
+
# @param offset [Integer]
|
62
|
+
# @return [void]
|
59
63
|
def assign_current_offset(partition, offset)
|
60
64
|
self.partition_current_offsets[partition.to_i] = offset
|
61
65
|
end
|
62
66
|
|
63
67
|
# @param partition [Integer]
|
68
|
+
# @param offset [Integer]
|
69
|
+
# @return [Integer]
|
64
70
|
def compute_lag(partition, offset)
|
65
71
|
begin
|
66
72
|
client = Phobos.create_kafka_client
|
@@ -74,6 +80,7 @@ module Deimos
|
|
74
80
|
end
|
75
81
|
|
76
82
|
# @param partition [Integer]
|
83
|
+
# @return [void]
|
77
84
|
def report_lag(partition)
|
78
85
|
current_offset = self.partition_current_offsets[partition.to_i]
|
79
86
|
return unless current_offset
|
@@ -94,6 +101,7 @@ module Deimos
|
|
94
101
|
|
95
102
|
class << self
|
96
103
|
# Reset all group information.
|
104
|
+
# @return [void]
|
97
105
|
def reset
|
98
106
|
@groups = {}
|
99
107
|
end
|
@@ -103,6 +111,7 @@ module Deimos
|
|
103
111
|
# topic = event.payload.fetch(:topic)
|
104
112
|
# partition = event.payload.fetch(:partition)
|
105
113
|
# @param payload [Hash]
|
114
|
+
# @return [void]
|
106
115
|
def message_processed(payload)
|
107
116
|
offset = payload[:offset] || payload[:last_offset]
|
108
117
|
topic = payload[:topic]
|
@@ -116,6 +125,7 @@ module Deimos
|
|
116
125
|
end
|
117
126
|
|
118
127
|
# @param payload [Hash]
|
128
|
+
# @return [void]
|
119
129
|
def offset_seek(payload)
|
120
130
|
offset = payload[:offset]
|
121
131
|
topic = payload[:topic]
|
@@ -129,6 +139,7 @@ module Deimos
|
|
129
139
|
end
|
130
140
|
|
131
141
|
# @param payload [Hash]
|
142
|
+
# @return [void]
|
132
143
|
def heartbeat(payload)
|
133
144
|
group = payload[:group_id]
|
134
145
|
synchronize do
|
@@ -31,6 +31,7 @@ module Deimos
|
|
31
31
|
# @param kwactions [String]
|
32
32
|
# @param request [String]
|
33
33
|
# @param response [String]
|
34
|
+
# @return [void]
|
34
35
|
def schemas(*actions, request: nil, response: nil, **kwactions)
|
35
36
|
actions.each do |action|
|
36
37
|
request ||= action.to_s.titleize
|
@@ -49,6 +50,7 @@ module Deimos
|
|
49
50
|
|
50
51
|
# Set the namespace for both requests and responses.
|
51
52
|
# @param name [String]
|
53
|
+
# @return [void]
|
52
54
|
def namespace(name)
|
53
55
|
request_namespace(name)
|
54
56
|
response_namespace(name)
|
@@ -56,12 +58,14 @@ module Deimos
|
|
56
58
|
|
57
59
|
# Set the namespace for requests.
|
58
60
|
# @param name [String]
|
61
|
+
# @return [void]
|
59
62
|
def request_namespace(name)
|
60
63
|
namespaces[:request] = name
|
61
64
|
end
|
62
65
|
|
63
66
|
# Set the namespace for repsonses.
|
64
67
|
# @param name [String]
|
68
|
+
# @return [void]
|
65
69
|
def response_namespace(name)
|
66
70
|
namespaces[:response] = name
|
67
71
|
end
|
@@ -94,6 +98,7 @@ module Deimos
|
|
94
98
|
end
|
95
99
|
|
96
100
|
# Decode the payload with the parameters.
|
101
|
+
# @return [void]
|
97
102
|
def decode_schema
|
98
103
|
namespace, schema = parse_namespace(:request)
|
99
104
|
decoder = Deimos.schema_backend(schema: schema, namespace: namespace)
|
@@ -109,6 +114,9 @@ module Deimos
|
|
109
114
|
|
110
115
|
# Render a hash into a payload as specified by the configured schema and namespace.
|
111
116
|
# @param payload [Hash]
|
117
|
+
# @param schema [String]
|
118
|
+
# @param namespace [String]
|
119
|
+
# @return [void]
|
112
120
|
def render_schema(payload, schema: nil, namespace: nil)
|
113
121
|
namespace, schema = parse_namespace(:response) if !schema && !namespace
|
114
122
|
encoder = Deimos.schema_backend(schema: schema, namespace: namespace)
|
data/lib/deimos/version.rb
CHANGED
data/lib/deimos.rb
CHANGED
@@ -45,7 +45,7 @@ require 'erb'
|
|
45
45
|
# Parent module.
|
46
46
|
module Deimos
|
47
47
|
class << self
|
48
|
-
# @return [Class
|
48
|
+
# @return [Class<Deimos::SchemaBackends::Base>]
|
49
49
|
def schema_backend_class
|
50
50
|
backend = Deimos.config.schema.backend.to_s
|
51
51
|
|
@@ -54,7 +54,7 @@ module Deimos
|
|
54
54
|
"Deimos::SchemaBackends::#{backend.classify}".constantize
|
55
55
|
end
|
56
56
|
|
57
|
-
# @param schema [String
|
57
|
+
# @param schema [String, Symbol]
|
58
58
|
# @param namespace [String]
|
59
59
|
# @return [Deimos::SchemaBackends::Base]
|
60
60
|
def schema_backend(schema:, namespace:)
|
@@ -81,6 +81,7 @@ module Deimos
|
|
81
81
|
|
82
82
|
# Start the DB producers to send Kafka messages.
|
83
83
|
# @param thread_count [Integer] the number of threads to start.
|
84
|
+
# @return [void]
|
84
85
|
def start_db_backend!(thread_count: 1)
|
85
86
|
Sigurd.exit_on_signal = true
|
86
87
|
if self.config.producers.backend != :db
|
@@ -69,7 +69,7 @@ module Deimos
|
|
69
69
|
end
|
70
70
|
|
71
71
|
desc 'Generate migration for a table based on an existing schema.'
|
72
|
-
#
|
72
|
+
# @return [void]
|
73
73
|
def generate
|
74
74
|
migration_template('migration.rb', "db/migrate/create_#{table_name.underscore}.rb")
|
75
75
|
template('model.rb', "app/models/#{table_name.underscore.singularize}.rb")
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<%- if @field_assignments.select{ |h| !h[:is_schema_class] }.any? -%>
|
21
21
|
### Attribute Accessors ###
|
22
22
|
<%- @field_assignments.select{ |h| !h[:is_schema_class] }.each do |method_definition| -%>
|
23
|
-
# @
|
23
|
+
# @return [<%= method_definition[:deimos_type] %>]
|
24
24
|
attr_accessor :<%= method_definition[:field].name %>
|
25
25
|
<%- end -%>
|
26
26
|
|
@@ -28,14 +28,14 @@
|
|
28
28
|
<%- if @field_assignments.select{ |h| h[:is_schema_class] }.any? -%>
|
29
29
|
### Attribute Writers ###
|
30
30
|
<%- @field_assignments.select{ |h| h[:is_schema_class] }.each do |method_definition| -%>
|
31
|
-
# @
|
31
|
+
# @return [<%= method_definition[:deimos_type] %>]
|
32
32
|
def <%= method_definition[:field].name %>=(<%= method_definition[:method_argument] %>)
|
33
33
|
<%- if method_definition[:field_type] == :array -%>
|
34
|
-
@<%= method_definition[:field].name %> = values
|
34
|
+
@<%= method_definition[:field].name %> = values&.map do |value|
|
35
35
|
<%= method_definition[:field_initialization] %>
|
36
36
|
end
|
37
37
|
<%- elsif method_definition[:field_type] == :map -%>
|
38
|
-
@<%= method_definition[:field].name %> = values
|
38
|
+
@<%= method_definition[:field].name %> = values&.transform_values do |value|
|
39
39
|
<%= method_definition[:field_initialization] %>
|
40
40
|
end
|
41
41
|
<%- else -%>
|
@@ -62,7 +62,15 @@
|
|
62
62
|
def namespace
|
63
63
|
'<%= @current_schema.namespace %>'
|
64
64
|
end
|
65
|
-
|
65
|
+
<%- if @tombstone_assignment %>
|
66
|
+
def self.tombstone(key)
|
67
|
+
record = self.allocate
|
68
|
+
<%- if @tombstone_assignment.present? -%>
|
69
|
+
<%= @tombstone_assignment %>
|
70
|
+
<%- end -%>
|
71
|
+
record
|
72
|
+
end
|
73
|
+
<%- end %>
|
66
74
|
# @override
|
67
75
|
def as_json(_opts={})
|
68
76
|
{
|
@@ -11,11 +11,17 @@ module Deimos
|
|
11
11
|
# Generator for Schema Classes used for the IDE and consumer/producer interfaces
|
12
12
|
class SchemaClassGenerator < Rails::Generators::Base
|
13
13
|
|
14
|
+
# @return [Array<Symbol>]
|
14
15
|
SPECIAL_TYPES = %i(record enum).freeze
|
16
|
+
# @return [String]
|
15
17
|
INITIALIZE_WHITESPACE = "\n#{' ' * 19}"
|
18
|
+
# @return [Array<String>]
|
16
19
|
IGNORE_DEFAULTS = %w(message_id timestamp).freeze
|
20
|
+
# @return [String]
|
17
21
|
SCHEMA_CLASS_FILE = 'schema_class.rb'
|
22
|
+
# @return [String]
|
18
23
|
SCHEMA_RECORD_PATH = File.expand_path('schema_class/templates/schema_record.rb.tt', __dir__).freeze
|
24
|
+
# @return [String]
|
19
25
|
SCHEMA_ENUM_PATH = File.expand_path('schema_class/templates/schema_enum.rb.tt', __dir__).freeze
|
20
26
|
|
21
27
|
source_root File.expand_path('schema_class/templates', __dir__)
|
@@ -41,16 +47,17 @@ module Deimos
|
|
41
47
|
# Deimos Consumer or Producer Configuration object
|
42
48
|
# @param schema_name [String]
|
43
49
|
# @param namespace [String]
|
44
|
-
# @param
|
45
|
-
|
50
|
+
# @param key_config [Hash,nil]
|
51
|
+
# @return [void]
|
52
|
+
def generate_classes(schema_name, namespace, key_config)
|
46
53
|
schema_base = Deimos.schema_backend(schema: schema_name, namespace: namespace)
|
47
54
|
schema_base.load_schema
|
48
|
-
if
|
49
|
-
key_schema_base = Deimos.schema_backend(schema:
|
55
|
+
if key_config&.dig(:schema)
|
56
|
+
key_schema_base = Deimos.schema_backend(schema: key_config[:schema], namespace: namespace)
|
50
57
|
key_schema_base.load_schema
|
51
|
-
generate_class_from_schema_base(key_schema_base)
|
58
|
+
generate_class_from_schema_base(key_schema_base, key_config: nil)
|
52
59
|
end
|
53
|
-
generate_class_from_schema_base(schema_base,
|
60
|
+
generate_class_from_schema_base(schema_base, key_config: key_config)
|
54
61
|
end
|
55
62
|
|
56
63
|
# @param schema [Avro::Schema::NamedSchema]
|
@@ -83,8 +90,9 @@ module Deimos
|
|
83
90
|
end
|
84
91
|
|
85
92
|
# @param schema_base [Deimos::SchemaBackends::Base]
|
86
|
-
# @param
|
87
|
-
|
93
|
+
# @param key_config [Hash,nil]
|
94
|
+
# @return [void]
|
95
|
+
def generate_class_from_schema_base(schema_base, key_config: nil)
|
88
96
|
@discovered_schemas = Set.new
|
89
97
|
@sub_schema_templates = []
|
90
98
|
schemas = collect_all_schemas(schema_base.schema_store.schemas.values)
|
@@ -93,11 +101,11 @@ module Deimos
|
|
93
101
|
sub_schemas = schemas.reject { |s| s.name == schema_base.schema }.sort_by(&:name)
|
94
102
|
if Deimos.config.schema.nest_child_schemas
|
95
103
|
@sub_schema_templates = sub_schemas.map do |schema|
|
96
|
-
_generate_class_template_from_schema(schema)
|
104
|
+
_generate_class_template_from_schema(schema, nil)
|
97
105
|
end
|
98
|
-
write_file(main_schema,
|
106
|
+
write_file(main_schema, key_config)
|
99
107
|
else
|
100
|
-
write_file(main_schema,
|
108
|
+
write_file(main_schema, key_config)
|
101
109
|
sub_schemas.each do |schema|
|
102
110
|
write_file(schema, nil)
|
103
111
|
end
|
@@ -105,9 +113,10 @@ module Deimos
|
|
105
113
|
end
|
106
114
|
|
107
115
|
# @param schema [Avro::Schema::NamedSchema]
|
108
|
-
# @param
|
109
|
-
|
110
|
-
|
116
|
+
# @param key_config [Hash,nil]
|
117
|
+
# @return [void]
|
118
|
+
def write_file(schema, key_config)
|
119
|
+
class_template = _generate_class_template_from_schema(schema, key_config)
|
111
120
|
@modules = Utils::SchemaClass.modules_for(schema.namespace)
|
112
121
|
@main_class_definition = class_template
|
113
122
|
|
@@ -143,7 +152,7 @@ module Deimos
|
|
143
152
|
end
|
144
153
|
|
145
154
|
desc 'Generate a class based on configured consumer and producers.'
|
146
|
-
#
|
155
|
+
# @return [void]
|
147
156
|
def generate
|
148
157
|
_validate
|
149
158
|
Rails.logger.info("Generating schemas from Deimos.config to #{Deimos.config.schema.generated_class_path}")
|
@@ -154,7 +163,7 @@ module Deimos
|
|
154
163
|
key_schema_name = config.key_config[:schema]
|
155
164
|
found_schemas.add("#{namespace}.#{schema_name}")
|
156
165
|
found_schemas.add("#{namespace}.#{key_schema_name}") if key_schema_name
|
157
|
-
generate_classes(schema_name, namespace,
|
166
|
+
generate_classes(schema_name, namespace, config.key_config)
|
158
167
|
end
|
159
168
|
|
160
169
|
Deimos.config.consumer_objects.each do |config|
|
@@ -163,7 +172,7 @@ module Deimos
|
|
163
172
|
key_schema_name = config.key_config[:schema]
|
164
173
|
found_schemas.add("#{namespace}.#{schema_name}")
|
165
174
|
found_schemas.add("#{namespace}.#{key_schema_name}") if key_schema_name
|
166
|
-
generate_classes(schema_name, namespace,
|
175
|
+
generate_classes(schema_name, namespace, config.key_config)
|
167
176
|
end
|
168
177
|
|
169
178
|
generate_from_schema_files(found_schemas)
|
@@ -191,10 +200,10 @@ module Deimos
|
|
191
200
|
end
|
192
201
|
|
193
202
|
# @param schema[Avro::Schema::NamedSchema]
|
194
|
-
# @param
|
203
|
+
# @param key_config[Hash,nil]
|
195
204
|
# @return [String]
|
196
|
-
def _generate_class_template_from_schema(schema,
|
197
|
-
_set_instance_variables(schema,
|
205
|
+
def _generate_class_template_from_schema(schema, key_config)
|
206
|
+
_set_instance_variables(schema, key_config)
|
198
207
|
|
199
208
|
temp = schema.is_a?(Avro::Schema::RecordSchema) ? _record_class_template : _enum_class_template
|
200
209
|
res = ERB.new(temp, nil, '-')
|
@@ -202,20 +211,38 @@ module Deimos
|
|
202
211
|
end
|
203
212
|
|
204
213
|
# @param schema[Avro::Schema::NamedSchema]
|
205
|
-
# @param
|
206
|
-
def _set_instance_variables(schema,
|
214
|
+
# @param key_config [Hash,nil]
|
215
|
+
def _set_instance_variables(schema, key_config)
|
207
216
|
schema_is_record = schema.is_a?(Avro::Schema::RecordSchema)
|
208
217
|
@current_schema = schema
|
209
218
|
return unless schema_is_record
|
210
219
|
|
211
220
|
@fields = fields(schema)
|
212
|
-
|
221
|
+
key_schema = nil
|
222
|
+
if key_config&.dig(:schema)
|
223
|
+
key_schema_base = Deimos.schema_backend(schema: key_config[:schema], namespace: schema.namespace)
|
213
224
|
key_schema_base.load_schema
|
214
225
|
key_schema = key_schema_base.schema_store.schemas.values.first
|
215
226
|
@fields << Deimos::SchemaField.new('payload_key', key_schema, [], nil)
|
216
227
|
end
|
217
228
|
@initialization_definition = _initialization_definition
|
218
229
|
@field_assignments = _field_assignments
|
230
|
+
@tombstone_assignment = _tombstone_assignment(key_config, key_schema)
|
231
|
+
end
|
232
|
+
|
233
|
+
def _tombstone_assignment(key_config, key_schema)
|
234
|
+
return nil unless key_config
|
235
|
+
|
236
|
+
if key_config[:plain]
|
237
|
+
"record.tombstone_key = key"
|
238
|
+
elsif key_config[:field]
|
239
|
+
"record.tombstone_key = key\n record.#{key_config[:field]} = key"
|
240
|
+
elsif key_schema
|
241
|
+
field_base_type = _field_type(key_schema)
|
242
|
+
"record.tombstone_key = #{field_base_type}.initialize_from_value(key)\n record.payload_key = key"
|
243
|
+
else
|
244
|
+
''
|
245
|
+
end
|
219
246
|
end
|
220
247
|
|
221
248
|
# Defines the initialization method for Schema Records with one keyword argument per line
|
@@ -234,7 +261,7 @@ module Deimos
|
|
234
261
|
"#{result})"
|
235
262
|
end
|
236
263
|
|
237
|
-
# @param [SchemaField]
|
264
|
+
# @param field [SchemaField]
|
238
265
|
# @return [String]
|
239
266
|
def _field_default(field)
|
240
267
|
default = field.default
|