deimos-ruby 1.11.1 → 1.12.2
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/CHANGELOG.md +20 -2
- data/Gemfile.lock +8 -8
- data/README.md +103 -0
- data/deimos-ruby.gemspec +1 -1
- data/docs/CONFIGURATION.md +4 -0
- data/lib/deimos/active_record_consume/batch_consumption.rb +7 -5
- data/lib/deimos/active_record_consume/message_consumption.rb +4 -3
- data/lib/deimos/active_record_consumer.rb +2 -2
- data/lib/deimos/active_record_producer.rb +3 -0
- data/lib/deimos/config/configuration.rb +29 -0
- data/lib/deimos/consume/batch_consumption.rb +2 -2
- data/lib/deimos/consume/message_consumption.rb +2 -2
- data/lib/deimos/consumer.rb +10 -0
- data/lib/deimos/producer.rb +4 -3
- data/lib/deimos/schema_backends/avro_base.rb +64 -1
- data/lib/deimos/schema_backends/avro_schema_registry.rb +1 -1
- data/lib/deimos/schema_backends/base.rb +18 -2
- data/lib/deimos/schema_class/base.rb +62 -0
- data/lib/deimos/schema_class/enum.rb +24 -0
- data/lib/deimos/schema_class/record.rb +66 -0
- data/lib/deimos/shared_config.rb +5 -0
- data/lib/deimos/test_helpers.rb +43 -7
- data/lib/deimos/utils/schema_class.rb +29 -0
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +23 -0
- data/lib/generators/deimos/schema_class/templates/schema_class.rb.tt +15 -0
- data/lib/generators/deimos/schema_class/templates/schema_enum.rb.tt +21 -0
- data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +65 -0
- data/lib/generators/deimos/schema_class_generator.rb +247 -0
- data/lib/tasks/deimos.rake +8 -0
- data/spec/active_record_batch_consumer_spec.rb +120 -110
- data/spec/active_record_consumer_spec.rb +97 -88
- data/spec/active_record_producer_spec.rb +38 -27
- data/spec/batch_consumer_spec.rb +37 -28
- data/spec/config/configuration_spec.rb +10 -3
- data/spec/consumer_spec.rb +95 -84
- data/spec/generators/active_record_generator_spec.rb +1 -0
- data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +206 -0
- data/spec/generators/schema_class_generator_spec.rb +186 -0
- data/spec/producer_spec.rb +110 -0
- data/spec/schema_classes/generated.rb +156 -0
- data/spec/schema_classes/my_nested_schema.rb +114 -0
- data/spec/schema_classes/my_schema.rb +53 -0
- data/spec/schema_classes/my_schema_key.rb +35 -0
- data/spec/schema_classes/my_schema_with_complex_types.rb +172 -0
- data/spec/schemas/com/my-namespace/Generated.avsc +6 -0
- data/spec/schemas/com/my-namespace/MySchemaWithComplexTypes.avsc +95 -0
- data/spec/spec_helper.rb +6 -1
- metadata +28 -4
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Deimos
|
6
|
+
module SchemaClass
|
7
|
+
# Base Class for Schema Classes generated from Avro.
|
8
|
+
class Base
|
9
|
+
|
10
|
+
# :nodoc:
|
11
|
+
def initialize(*_args)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Converts the object to a string that represents a JSON object
|
15
|
+
# @return [String] a JSON string
|
16
|
+
def to_json(*_args)
|
17
|
+
to_h.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
# Converts the object to a hash which can be used for debugging or comparing objects.
|
21
|
+
# @return [Hash] a hash representation of the payload
|
22
|
+
def as_json(_opts={})
|
23
|
+
JSON.parse(to_json)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Converts the object attributes to a hash which can be used for Kafka
|
27
|
+
# @return [Hash] the payload as a hash.
|
28
|
+
def to_h
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
# :nodoc:
|
33
|
+
def ==(other)
|
34
|
+
comparison = other
|
35
|
+
if other.class == self.class
|
36
|
+
comparison = other.as_json
|
37
|
+
end
|
38
|
+
|
39
|
+
comparison == self.as_json
|
40
|
+
end
|
41
|
+
|
42
|
+
# :nodoc:
|
43
|
+
def to_s
|
44
|
+
klass = self.class
|
45
|
+
"#{klass}(#{self.as_json.symbolize_keys.to_s[1..-2]})"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Initializes this class from a given value
|
49
|
+
# @param value [Object]
|
50
|
+
def self.initialize_from_value(value)
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# :nodoc:
|
57
|
+
def hash
|
58
|
+
as_json.hash
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Deimos
|
7
|
+
module SchemaClass
|
8
|
+
# Base Class for Enum Classes generated from Avro.
|
9
|
+
class Enum < Base
|
10
|
+
# Returns all the valid symbols for this enum.
|
11
|
+
# @return [Array<String>]
|
12
|
+
def symbols
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
# :nodoc:
|
17
|
+
def self.initialize_from_value(value)
|
18
|
+
return nil if value.nil?
|
19
|
+
|
20
|
+
value.is_a?(self) ? value : self.new(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Deimos
|
7
|
+
module SchemaClass
|
8
|
+
# Base Class of Record Classes generated from Avro.
|
9
|
+
class Record < Base
|
10
|
+
|
11
|
+
# Converts the object to a hash which can be used for debugging or comparing objects.
|
12
|
+
# @return [Hash] a hash representation of the payload
|
13
|
+
def as_json(_opts={})
|
14
|
+
super.except('payload_key')
|
15
|
+
end
|
16
|
+
|
17
|
+
# Element access method as if this Object were a hash
|
18
|
+
# @param key[String||Symbol]
|
19
|
+
# @return [Object] The value of the attribute if exists, nil otherwise
|
20
|
+
def [](key)
|
21
|
+
self.try(key.to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
# :nodoc
|
25
|
+
def with_indifferent_access
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the schema name of the inheriting class.
|
30
|
+
# @return [String]
|
31
|
+
def schema
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the namespace for the schema of the inheriting class.
|
36
|
+
# @return [String]
|
37
|
+
def namespace
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the full schema name of the inheriting class.
|
42
|
+
# @return [String]
|
43
|
+
def full_schema
|
44
|
+
"#{namespace}.#{schema}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the schema validator from the schema backend
|
48
|
+
# @return [Deimos::SchemaBackends::Base]
|
49
|
+
def validator
|
50
|
+
Deimos.schema_backend(schema: schema, namespace: namespace)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Array<String>] an array of fields names in the schema.
|
54
|
+
def schema_fields
|
55
|
+
validator.schema_fields.map(&:name)
|
56
|
+
end
|
57
|
+
|
58
|
+
# :nodoc:
|
59
|
+
def self.initialize_from_value(value)
|
60
|
+
return nil if value.nil?
|
61
|
+
|
62
|
+
value.is_a?(self) ? value : self.new(**value.symbolize_keys)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/deimos/shared_config.rb
CHANGED
data/lib/deimos/test_helpers.rb
CHANGED
@@ -50,7 +50,6 @@ module Deimos
|
|
50
50
|
included do
|
51
51
|
|
52
52
|
RSpec.configure do |config|
|
53
|
-
|
54
53
|
config.prepend_before(:each) do
|
55
54
|
client = double('client').as_null_object
|
56
55
|
allow(client).to receive(:time) do |*_args, &block|
|
@@ -210,9 +209,11 @@ module Deimos
|
|
210
209
|
'value' => payload)
|
211
210
|
|
212
211
|
unless skip_expectation
|
213
|
-
|
214
|
-
|
215
|
-
|
212
|
+
_handler_expectation(:consume,
|
213
|
+
payload,
|
214
|
+
handler,
|
215
|
+
call_original,
|
216
|
+
&block)
|
216
217
|
end
|
217
218
|
Phobos::Actions::ProcessMessage.new(
|
218
219
|
listener: listener,
|
@@ -277,9 +278,11 @@ module Deimos
|
|
277
278
|
'partition' => 1,
|
278
279
|
'offset_lag' => 0)
|
279
280
|
unless skip_expectation
|
280
|
-
|
281
|
-
|
282
|
-
|
281
|
+
_handler_expectation(:consume_batch,
|
282
|
+
payloads,
|
283
|
+
handler,
|
284
|
+
call_original,
|
285
|
+
&block)
|
283
286
|
end
|
284
287
|
action = Phobos::Actions::ProcessBatchInline.new(
|
285
288
|
listener: listener,
|
@@ -356,5 +359,38 @@ module Deimos
|
|
356
359
|
|
357
360
|
handler.handler.constantize
|
358
361
|
end
|
362
|
+
|
363
|
+
# Test that a given handler will execute a `method` on an `input` correctly,
|
364
|
+
# If a block is given, that block will be executed when `method` is called.
|
365
|
+
# Otherwise it will just confirm that `method` is called at all.
|
366
|
+
# @param method [Symbol]
|
367
|
+
# @param input [Object]
|
368
|
+
# @param handler [Deimos::Consumer]
|
369
|
+
# @param call_original [Boolean]
|
370
|
+
def _handler_expectation(method,
|
371
|
+
input,
|
372
|
+
handler,
|
373
|
+
call_original,
|
374
|
+
&block)
|
375
|
+
schema_class = handler.class.config[:schema]
|
376
|
+
expected = input.dup
|
377
|
+
|
378
|
+
config = handler.class.config
|
379
|
+
use_schema_classes = config[:use_schema_classes]
|
380
|
+
use_schema_classes = use_schema_classes.present? ? use_schema_classes : Deimos.config.schema.use_schema_classes
|
381
|
+
|
382
|
+
if use_schema_classes && schema_class.present?
|
383
|
+
expected = if input.is_a?(Array)
|
384
|
+
input.map do |payload|
|
385
|
+
Utils::SchemaClass.instance(payload, schema_class)
|
386
|
+
end
|
387
|
+
else
|
388
|
+
Utils::SchemaClass.instance(input, schema_class)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
expectation = expect(handler).to receive(method).with(expected, anything, &block)
|
393
|
+
expectation.and_call_original if call_original
|
394
|
+
end
|
359
395
|
end
|
360
396
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Deimos
|
4
|
+
module Utils
|
5
|
+
# Class used by SchemaClassGenerator and Consumer/Producer interfaces
|
6
|
+
module SchemaClass
|
7
|
+
class << self
|
8
|
+
# Converts a raw payload into an instance of the Schema Class
|
9
|
+
# @param payload [Hash]
|
10
|
+
# @param schema [String]
|
11
|
+
# @return [Deimos::SchemaClass::Record]
|
12
|
+
def instance(payload, schema)
|
13
|
+
klass = "Schemas::#{schema.underscore.camelize}".safe_constantize
|
14
|
+
return payload if klass.nil? || payload.nil?
|
15
|
+
|
16
|
+
klass.new(**payload.symbolize_keys)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param config [Hash] Producer or Consumer config
|
20
|
+
# @return [Boolean]
|
21
|
+
def use?(config)
|
22
|
+
use_schema_classes = config[:use_schema_classes]
|
23
|
+
use_schema_classes.present? ? use_schema_classes : Deimos.config.schema.use_schema_classes
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/deimos/version.rb
CHANGED
data/lib/deimos.rb
CHANGED
@@ -19,6 +19,9 @@ require 'deimos/backends/kafka_async'
|
|
19
19
|
require 'deimos/backends/test'
|
20
20
|
|
21
21
|
require 'deimos/schema_backends/base'
|
22
|
+
require 'deimos/utils/schema_class'
|
23
|
+
require 'deimos/schema_class/enum'
|
24
|
+
require 'deimos/schema_class/record'
|
22
25
|
|
23
26
|
require 'deimos/monkey_patches/phobos_producer'
|
24
27
|
require 'deimos/monkey_patches/phobos_cli'
|
@@ -59,6 +62,26 @@ module Deimos
|
|
59
62
|
schema_backend_class.new(schema: schema, namespace: namespace)
|
60
63
|
end
|
61
64
|
|
65
|
+
# @param schema [String]
|
66
|
+
# @param namespace [String]
|
67
|
+
# @param payload [Hash]
|
68
|
+
# @param subject [String]
|
69
|
+
# @return [String]
|
70
|
+
def encode(schema:, namespace:, payload:, subject: nil)
|
71
|
+
self.schema_backend(schema: schema, namespace: namespace).
|
72
|
+
encode(payload, topic: subject || "#{namespace}.#{schema}" )
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param schema [String]
|
76
|
+
# @param namespace [String]
|
77
|
+
# @param payload [String]
|
78
|
+
# @param subject [String]
|
79
|
+
# @return [Hash,nil]
|
80
|
+
def decode(schema:, namespace:, payload:, subject: nil)
|
81
|
+
self.schema_backend(schema: schema, namespace: namespace).
|
82
|
+
decode(payload, topic: subject || "#{namespace}.#{schema}" )
|
83
|
+
end
|
84
|
+
|
62
85
|
# Start the DB producers to send Kafka messages.
|
63
86
|
# @param thread_count [Integer] the number of threads to start.
|
64
87
|
def start_db_backend!(thread_count: 1)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is autogenerated by Deimos, Do NOT modify
|
4
|
+
module Schemas
|
5
|
+
<% if @sub_schema_templates.present? && @sub_schema_templates.any? -%>
|
6
|
+
### Secondary Schema Classes ###
|
7
|
+
<% end -%>
|
8
|
+
<%- @sub_schema_templates.each do |schema_template| -%>
|
9
|
+
<%=- schema_template %>
|
10
|
+
|
11
|
+
<%- end -%>
|
12
|
+
### Primary Schema Class ###
|
13
|
+
<%=- @main_class_definition -%>
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Autogenerated Schema for Enum at <%= @current_schema.namespace %>.<%= @current_schema.name %>
|
2
|
+
class <%= Deimos::SchemaBackends::AvroBase.schema_classname(@current_schema) %> < Deimos::SchemaClass::Enum
|
3
|
+
# @return ['<%= @current_schema.symbols.join("', '") %>']
|
4
|
+
attr_accessor :<%= @current_schema.name.underscore %>
|
5
|
+
|
6
|
+
# :nodoc:
|
7
|
+
def initialize(<%= @current_schema.name.underscore %>)
|
8
|
+
super
|
9
|
+
self.<%= @current_schema.name.underscore %> = <%= @current_schema.name.underscore %>
|
10
|
+
end
|
11
|
+
|
12
|
+
# @override
|
13
|
+
def symbols
|
14
|
+
%w(<%= @current_schema.symbols.join(' ') %>)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @override
|
18
|
+
def to_h
|
19
|
+
@<%= @current_schema.name.underscore %>
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Autogenerated Schema for Record at <%= @current_schema.namespace %>.<%= @current_schema.name %>
|
2
|
+
class <%= Deimos::SchemaBackends::AvroBase.schema_classname(@current_schema) %> < Deimos::SchemaClass::Record
|
3
|
+
<%- if @field_assignments.select{ |h| h[:is_schema_class] }.any? -%>
|
4
|
+
### Attribute Readers ###
|
5
|
+
<%- @field_assignments.select{ |h| h[:is_schema_class] }.each do |method_definition| -%>
|
6
|
+
# @return [<%= method_definition[:deimos_type] %>]
|
7
|
+
attr_reader :<%= method_definition[:field].name %>
|
8
|
+
<%- end -%>
|
9
|
+
|
10
|
+
<% end -%>
|
11
|
+
<%- if @field_assignments.select{ |h| !h[:is_schema_class] }.any? -%>
|
12
|
+
### Attribute Accessors ###
|
13
|
+
<%- @field_assignments.select{ |h| !h[:is_schema_class] }.each do |method_definition| -%>
|
14
|
+
# @param <%= method_definition[:method_argument] %> [<%= method_definition[:deimos_type] %>]
|
15
|
+
attr_accessor :<%= method_definition[:field].name %>
|
16
|
+
<%- end -%>
|
17
|
+
|
18
|
+
<% end -%>
|
19
|
+
<%- if @field_assignments.select{ |h| h[:is_schema_class] }.any? -%>
|
20
|
+
### Attribute Writers ###
|
21
|
+
<%- @field_assignments.select{ |h| h[:is_schema_class] }.each do |method_definition| -%>
|
22
|
+
# @param <%= method_definition[:method_argument] %> [<%= method_definition[:deimos_type] %>]
|
23
|
+
def <%= method_definition[:field].name %>=(<%= method_definition[:method_argument] %>)
|
24
|
+
<%- if method_definition[:field_type] == :array -%>
|
25
|
+
@<%= method_definition[:field].name %> = values.map do |value|
|
26
|
+
<%= method_definition[:field_initialization] %>
|
27
|
+
end
|
28
|
+
<%- elsif method_definition[:field_type] == :map -%>
|
29
|
+
@<%= method_definition[:field].name %> = values.transform_values do |value|
|
30
|
+
<%= method_definition[:field_initialization] %>
|
31
|
+
end
|
32
|
+
<%- else -%>
|
33
|
+
@<%= method_definition[:field].name %> = <%= method_definition[:field_initialization] %>
|
34
|
+
<%- end -%>
|
35
|
+
end
|
36
|
+
|
37
|
+
<%- end -%>
|
38
|
+
<% end -%>
|
39
|
+
# @override
|
40
|
+
<%= @initialization_definition %>
|
41
|
+
super
|
42
|
+
<%- @fields.each do |field| -%>
|
43
|
+
self.<%= field.name %> = <%= field.name %>
|
44
|
+
<% end -%>
|
45
|
+
end
|
46
|
+
|
47
|
+
# @override
|
48
|
+
def schema
|
49
|
+
'<%= @current_schema.name %>'
|
50
|
+
end
|
51
|
+
|
52
|
+
# @override
|
53
|
+
def namespace
|
54
|
+
'<%= @current_schema.namespace %>'
|
55
|
+
end
|
56
|
+
|
57
|
+
# @override
|
58
|
+
def to_h
|
59
|
+
{
|
60
|
+
<%- @fields.each do |field| -%>
|
61
|
+
<%= field_to_h(field) %>
|
62
|
+
<% end -%>
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
require 'deimos'
|
5
|
+
require 'deimos/schema_backends/avro_base'
|
6
|
+
require 'deimos/config/configuration'
|
7
|
+
|
8
|
+
# Generates new schema classes.
|
9
|
+
module Deimos
|
10
|
+
module Generators
|
11
|
+
# Generator for Schema Classes used for the IDE and consumer/producer interfaces
|
12
|
+
class SchemaClassGenerator < Rails::Generators::Base
|
13
|
+
|
14
|
+
SPECIAL_TYPES = %i(record enum).freeze
|
15
|
+
INITIALIZE_WHITESPACE = "\n#{' ' * 19}"
|
16
|
+
IGNORE_DEFAULTS = %w(message_id timestamp).freeze
|
17
|
+
SCHEMA_CLASS_FILE = 'schema_class.rb'
|
18
|
+
SCHEMA_RECORD_PATH = File.expand_path('schema_class/templates/schema_record.rb.tt', __dir__).freeze
|
19
|
+
SCHEMA_ENUM_PATH = File.expand_path('schema_class/templates/schema_enum.rb.tt', __dir__).freeze
|
20
|
+
|
21
|
+
source_root File.expand_path('schema_class/templates', __dir__)
|
22
|
+
|
23
|
+
no_commands do
|
24
|
+
# Retrieve the fields from this Avro Schema
|
25
|
+
# @param schema [Avro::Schema::NamedSchema]
|
26
|
+
# @return [Array<SchemaField>]
|
27
|
+
def fields(schema)
|
28
|
+
schema.fields.map do |field|
|
29
|
+
Deimos::SchemaField.new(field.name, field.type, [], field.default)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Converts Deimos::SchemaField's to String form for generated YARD docs
|
34
|
+
# @param schema_field [Deimos::SchemaField]
|
35
|
+
# @return [String] A string representation of the Type of this SchemaField
|
36
|
+
def deimos_field_type(schema_field)
|
37
|
+
_field_type(schema_field.type)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Generate a Schema Model Class and all of its Nested Records from a
|
41
|
+
# Deimos Consumer or Producer Configuration object
|
42
|
+
# @param schema_name [String]
|
43
|
+
# @param namespace [String]
|
44
|
+
# @param key_schema_name [String]
|
45
|
+
def generate_classes(schema_name, namespace, key_schema_name)
|
46
|
+
schema_base = Deimos.schema_backend(schema: schema_name, namespace: namespace)
|
47
|
+
schema_base.load_schema
|
48
|
+
if key_schema_name.present?
|
49
|
+
key_schema_base = Deimos.schema_backend(schema: key_schema_name, namespace: namespace)
|
50
|
+
key_schema_base.load_schema
|
51
|
+
generate_class_from_schema_base(key_schema_base)
|
52
|
+
end
|
53
|
+
generate_class_from_schema_base(schema_base, key_schema_base: key_schema_base)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param schema_base [Deimos::SchemaBackends::Base]
|
57
|
+
# @param key_schema_base[Avro::Schema::NamedSchema]
|
58
|
+
def generate_class_from_schema_base(schema_base, key_schema_base: nil)
|
59
|
+
schemas = schema_base.schema_store.schemas.values
|
60
|
+
sub_schemas = schemas.reject { |s| s.name == schema_base.schema }
|
61
|
+
@sub_schema_templates = sub_schemas.map do |schema|
|
62
|
+
_generate_class_template_from_schema(schema)
|
63
|
+
end
|
64
|
+
|
65
|
+
main_schema = schemas.find { |s| s.name == schema_base.schema }
|
66
|
+
class_template = _generate_class_template_from_schema(main_schema, key_schema_base)
|
67
|
+
@main_class_definition = class_template
|
68
|
+
|
69
|
+
file_prefix = main_schema.name.underscore
|
70
|
+
namespace_path = main_schema.namespace.tr('.', '/')
|
71
|
+
filename = "#{Deimos.config.schema.generated_class_path}/#{namespace_path}/#{file_prefix}.rb"
|
72
|
+
template(SCHEMA_CLASS_FILE, filename, force: true)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Format a given field into its appropriate to_h representation.
|
76
|
+
# @param field[Deimos::SchemaField]
|
77
|
+
# @return [String]
|
78
|
+
def field_to_h(field)
|
79
|
+
res = "'#{field.name}' => @#{field.name}"
|
80
|
+
field_base_type = _schema_base_class(field.type).type_sym
|
81
|
+
|
82
|
+
if %i(record enum).include?(field_base_type)
|
83
|
+
res += case field.type.type_sym
|
84
|
+
when :array
|
85
|
+
'.map { |v| v&.to_h }'
|
86
|
+
when :map
|
87
|
+
'.transform_values { |v| v&.to_h }'
|
88
|
+
else
|
89
|
+
'&.to_h'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
res + (field.name == @fields.last.name ? '' : ',')
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
desc 'Generate a class based on configured consumer and producers.'
|
99
|
+
# :nodoc:
|
100
|
+
def generate
|
101
|
+
_validate
|
102
|
+
Rails.logger.info("Generating schemas from Deimos.config to #{Deimos.config.schema.generated_class_path}")
|
103
|
+
Deimos.config.producer_objects.each do |config|
|
104
|
+
schema_name = config.schema
|
105
|
+
namespace = config.namespace || Deimos.config.producers.schema_namespace
|
106
|
+
key_schema_name = config.key_config[:schema]
|
107
|
+
generate_classes(schema_name, namespace, key_schema_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
Deimos.config.consumer_objects.each do |config|
|
111
|
+
schema_name = config.schema
|
112
|
+
namespace = config.namespace
|
113
|
+
key_schema_name = config.key_config[:schema]
|
114
|
+
generate_classes(schema_name, namespace, key_schema_name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Determines if Schema Class Generation can be run.
|
121
|
+
# @raise if Schema Backend is not of a Avro-based class
|
122
|
+
def _validate
|
123
|
+
backend = Deimos.config.schema.backend.to_s
|
124
|
+
raise 'Schema Class Generation requires an Avro-based Schema Backend' if backend !~ /^avro/
|
125
|
+
end
|
126
|
+
|
127
|
+
# @param schema[Avro::Schema::NamedSchema]
|
128
|
+
# @param key_schema_base[Avro::Schema::NamedSchema]
|
129
|
+
# @return [String]
|
130
|
+
def _generate_class_template_from_schema(schema, key_schema_base=nil)
|
131
|
+
_set_instance_variables(schema, key_schema_base)
|
132
|
+
|
133
|
+
temp = schema.is_a?(Avro::Schema::RecordSchema) ? _record_class_template : _enum_class_template
|
134
|
+
res = ERB.new(temp, nil, '-')
|
135
|
+
res.result(binding)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param schema[Avro::Schema::NamedSchema]
|
139
|
+
# @param key_schema_base[Avro::Schema::NamedSchema]
|
140
|
+
def _set_instance_variables(schema, key_schema_base=nil)
|
141
|
+
schema_is_record = schema.is_a?(Avro::Schema::RecordSchema)
|
142
|
+
@current_schema = schema
|
143
|
+
return unless schema_is_record
|
144
|
+
|
145
|
+
@fields = fields(schema)
|
146
|
+
if key_schema_base.present?
|
147
|
+
key_schema_base.load_schema
|
148
|
+
key_schema = key_schema_base.schema_store.schemas.values.first
|
149
|
+
@fields << Deimos::SchemaField.new('payload_key', key_schema, [], nil)
|
150
|
+
end
|
151
|
+
@initialization_definition = _initialization_definition
|
152
|
+
@field_assignments = _field_assignments
|
153
|
+
end
|
154
|
+
|
155
|
+
# Defines the initialization method for Schema Records with one keyword argument per line
|
156
|
+
# @return [String] A string which defines the method signature for the initialize method
|
157
|
+
def _initialization_definition
|
158
|
+
arguments = @fields.map do |schema_field|
|
159
|
+
arg = "#{schema_field.name}:"
|
160
|
+
arg += _field_default(schema_field)
|
161
|
+
arg.strip
|
162
|
+
end
|
163
|
+
|
164
|
+
result = "def initialize(#{arguments.first}"
|
165
|
+
arguments[1..-1].each_with_index do |arg, _i|
|
166
|
+
result += ",#{INITIALIZE_WHITESPACE}#{arg}"
|
167
|
+
end
|
168
|
+
"#{result})"
|
169
|
+
end
|
170
|
+
|
171
|
+
# @param [SchemaField]
|
172
|
+
# @return [String]
|
173
|
+
def _field_default(field)
|
174
|
+
default = field.default
|
175
|
+
return ' nil' if default == :no_default || default.nil? || IGNORE_DEFAULTS.include?(field.name)
|
176
|
+
|
177
|
+
case field.type.type_sym
|
178
|
+
when :string
|
179
|
+
" '#{default}'"
|
180
|
+
when :record
|
181
|
+
schema_name = Deimos::SchemaBackends::AvroBase.schema_classname(field.type)
|
182
|
+
class_instance = Utils::SchemaClass.instance(field.default, schema_name)
|
183
|
+
" #{class_instance.to_h}"
|
184
|
+
else
|
185
|
+
" #{default}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Overrides default attr accessor methods
|
190
|
+
# @return [Array<String>]
|
191
|
+
def _field_assignments
|
192
|
+
result = []
|
193
|
+
@fields.each do |field|
|
194
|
+
field_type = field.type.type_sym # Record, Union, Enum, Array or Map
|
195
|
+
schema_base_type = _schema_base_class(field.type)
|
196
|
+
field_base_type = _field_type(schema_base_type)
|
197
|
+
method_argument = %i(array map).include?(field_type) ? 'values' : 'value'
|
198
|
+
is_schema_class = %i(record enum).include?(schema_base_type.type_sym)
|
199
|
+
|
200
|
+
field_initialization = method_argument
|
201
|
+
|
202
|
+
if is_schema_class
|
203
|
+
field_initialization = "#{field_base_type}.initialize_from_value(value)"
|
204
|
+
end
|
205
|
+
|
206
|
+
result << {
|
207
|
+
field: field,
|
208
|
+
field_type: field_type,
|
209
|
+
is_schema_class: is_schema_class,
|
210
|
+
method_argument: method_argument,
|
211
|
+
deimos_type: deimos_field_type(field),
|
212
|
+
field_initialization: field_initialization
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
result
|
217
|
+
end
|
218
|
+
|
219
|
+
# Converts Avro::Schema::NamedSchema's to String form for generated YARD docs.
|
220
|
+
# Recursively handles the typing for Arrays, Maps and Unions.
|
221
|
+
# @param avro_schema [Avro::Schema::NamedSchema]
|
222
|
+
# @return [String] A string representation of the Type of this SchemaField
|
223
|
+
def _field_type(avro_schema)
|
224
|
+
Deimos::SchemaBackends::AvroBase.field_type(avro_schema)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Returns the base class for this schema. Decodes Arrays, Maps and Unions
|
228
|
+
# @param avro_schema [Avro::Schema::NamedSchema]
|
229
|
+
# @return [Avro::Schema::NamedSchema]
|
230
|
+
def _schema_base_class(avro_schema)
|
231
|
+
Deimos::SchemaBackends::AvroBase.schema_base_class(avro_schema)
|
232
|
+
end
|
233
|
+
|
234
|
+
# An ERB template for schema record classes
|
235
|
+
# @return [String]
|
236
|
+
def _record_class_template
|
237
|
+
File.read(SCHEMA_RECORD_PATH).strip
|
238
|
+
end
|
239
|
+
|
240
|
+
# An ERB template for schema enum classes
|
241
|
+
# @return [String]
|
242
|
+
def _enum_class_template
|
243
|
+
File.read(SCHEMA_ENUM_PATH).strip
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
data/lib/tasks/deimos.rake
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'phobos'
|
4
4
|
require 'phobos/cli'
|
5
|
+
require 'generators/deimos/schema_class_generator'
|
6
|
+
require 'optparse'
|
5
7
|
|
6
8
|
namespace :deimos do
|
7
9
|
desc 'Starts Deimos in the rails environment'
|
@@ -31,4 +33,10 @@ namespace :deimos do
|
|
31
33
|
Deimos::Utils::DbPoller.start!
|
32
34
|
end
|
33
35
|
|
36
|
+
desc 'Run Schema Model Generator'
|
37
|
+
task generate_schema_classes: :environment do
|
38
|
+
Rails.logger.info("Running deimos:generate_schema_classes")
|
39
|
+
Deimos::Generators::SchemaClassGenerator.start
|
40
|
+
end
|
41
|
+
|
34
42
|
end
|