deimos-ruby 1.8.1.pre.beta1 → 1.8.1.pre.beta6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 335328a0ca5fb9147405485a66944ebe0d05af552ec09ec9605d76a7e7da7e9a
4
- data.tar.gz: 438a9a91bda52be144d63867a4ae42dd1901cc7472f61232044aa934efebb464
3
+ metadata.gz: d38b0803c074ff89dd8ff166ed8de2dcacb2ecaae46cb321abc0de630498e849
4
+ data.tar.gz: efecfba1d4d76a83270534ab7687c344253b2e4a7765dde9d42b6959083171db
5
5
  SHA512:
6
- metadata.gz: 3e01c5c9db93cb6e0f56dafa2f1b301c30b9cc11c397ca35f77b80cfe62bd9600eacb8774516ee1c81337193687944a8c7bec3cba400df6ea8b1c33af7e82c27
7
- data.tar.gz: 9de8cea35db671c43123d096653a6423ccfcaad8a2b8e9478f787e879bd7fd50a626604b8626f79b5f8654c84130fded58385d6f75fe44e59e1bfb5a284e1096
6
+ metadata.gz: d435a7c9f130650f7f9849554f2d0a71b87a9f8bddca2f542b94848ca13e8fe3084aa095f19eadd581a7af42f43511509ae9dc06bbb69982da34463304945a69
7
+ data.tar.gz: 9cfd22453a8a59d6c74a46bf41c6feb874bf125fb335c4b084df04f681bb9f3d2b03a8295a18c86166c194e592855d8b811bad39c29565002ec6dd6100edd589
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## UNRELEASED
9
9
 
10
+ ## 1.8.1-beta6 - 2020-08-13
11
+ - Fix for consuming nil payloads with Ruby 2.3.
12
+
13
+ ## 1.8.1-beta5 - 2020-08-13
14
+
15
+ ### Fixes :wrench:
16
+ - Fix regression bug which introduces backwards incompatibility
17
+ with ActiveRecordProducer's `record_attributes` method.
18
+
19
+ ## 1.8.1-beta4 - 2020-08-12
20
+
21
+ ### Fixes :wrench:
22
+ - Fix regression bug where arrays were not being encoded
23
+
24
+ ## 1.8.1-beta3 - 2020-08-05
25
+
26
+ ### Fixes :wrench:
27
+ - Simplify decoding messages and handle producer not found
28
+ - Consolidate types in sub-records recursively
29
+ (fixes [#72](https://github.com/flipp-oss/deimos/issues/72))
30
+
31
+ ## 1.8.1-beta2 - 2020-07-28
32
+
33
+ ### Features :star:
34
+ - Add `SchemaControllerMixin` to encode and decode schema-encoded
35
+ payloads in Rails controllers.
36
+
10
37
  ## 1.8.1-beta1 - 2020-07-22
11
38
 
12
39
  ### Fixes :wrench:
@@ -35,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
35
62
  - Added `ActiveRecordConsumer` batch mode
36
63
 
37
64
  ### Fixes :wrench:
65
+ - Fixes `send_produce_error` to decode `failed_messages` with built-in decoder.
38
66
  - Lag calculation can be incorrect if no messages are being consumed.
39
67
  - Fixed bug where printing messages on a MessageSizeTooLarge
40
68
  error didn't work.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- deimos-ruby (1.8.1.pre.beta1)
4
+ deimos-ruby (1.8.1.pre.beta6)
5
5
  avro_turf (~> 0.11)
6
6
  phobos (~> 1.9)
7
7
  ruby-kafka (~> 0.7)
@@ -87,7 +87,7 @@ GEM
87
87
  rake (~> 13.0)
88
88
  dogstatsd-ruby (4.8.1)
89
89
  erubi (1.9.0)
90
- excon (0.75.0)
90
+ excon (0.76.0)
91
91
  exponential-backoff (0.0.4)
92
92
  ffi (1.13.1)
93
93
  formatador (0.2.5)
@@ -205,6 +205,14 @@ GEM
205
205
  rspec-mocks (3.9.1)
206
206
  diff-lcs (>= 1.2.0, < 2.0)
207
207
  rspec-support (~> 3.9.0)
208
+ rspec-rails (4.0.1)
209
+ actionpack (>= 4.2)
210
+ activesupport (>= 4.2)
211
+ railties (>= 4.2)
212
+ rspec-core (~> 3.9)
213
+ rspec-expectations (~> 3.9)
214
+ rspec-mocks (~> 3.9)
215
+ rspec-support (~> 3.9)
208
216
  rspec-support (3.9.3)
209
217
  rspec_junit_formatter (0.4.1)
210
218
  rspec-core (>= 2, < 4, != 2.12.0)
@@ -250,7 +258,6 @@ PLATFORMS
250
258
  ruby
251
259
 
252
260
  DEPENDENCIES
253
- activerecord (~> 6)
254
261
  activerecord-import
255
262
  avro (~> 1.9)
256
263
  database_cleaner (~> 1.7)
@@ -265,6 +272,7 @@ DEPENDENCIES
265
272
  rails (~> 6)
266
273
  rake (~> 13)
267
274
  rspec (~> 3)
275
+ rspec-rails (~> 4)
268
276
  rspec_junit_formatter (~> 0.3)
269
277
  rubocop (~> 0.72)
270
278
  rubocop-rspec (~> 1.27)
data/README.md CHANGED
@@ -22,6 +22,7 @@ Built on Phobos and hence Ruby-Kafka.
22
22
  * [Kafka Message Keys](#kafka-message-keys)
23
23
  * [Consumers](#consumers)
24
24
  * [Rails Integration](#rails-integration)
25
+ * [Controller Mixin](#controller-mixin)
25
26
  * [Database Backend](#database-backend)
26
27
  * [Database Poller](#database-poller)
27
28
  * [Running Consumers](#running-consumers)
@@ -447,6 +448,58 @@ class Widget < ActiveRecord::Base
447
448
  end
448
449
  ```
449
450
 
451
+ ### Controller Mixin
452
+
453
+ Deimos comes with a mixin for `ActionController` which automatically encodes and decodes schema
454
+ payloads. There are some advantages to encoding your data in e.g. Avro rather than straight JSON,
455
+ particularly if your service is talking to another backend service rather than the front-end
456
+ browser:
457
+
458
+ * It enforces a contract between services. Solutions like [OpenAPI](https://swagger.io/specification/)
459
+ do this as well, but in order for the client to know the contract, usually some kind of code
460
+ generation has to happen. Using schemas ensures both sides know the contract without having to change code.
461
+ In addition, OpenAPI is now a huge and confusing format, and using simpler schema formats
462
+ can be beneficial.
463
+ * Using Avro or Protobuf ensures both forwards and backwards compatibility,
464
+ which reduces the need for versioning since both sides can simply ignore fields they aren't aware
465
+ of.
466
+ * Encoding and decoding using Avro or Protobuf is generally faster than straight JSON, and
467
+ results in smaller payloads and therefore less network traffic.
468
+
469
+ To use the mixin, add the following to your `WhateverController`:
470
+
471
+ ```ruby
472
+ class WhateverController < ApplicationController
473
+ include Deimos::Utils::SchemaControllerMixin
474
+
475
+ request_namespace 'my.namespace.requests'
476
+ response_namespace 'my.namespace.responses'
477
+
478
+ # Add a "schemas" line for all routes that should encode/decode schemas.
479
+ # Default is to match the schema name to the route name.
480
+ schemas :index
481
+ # will look for: my.namespace.requests.Index.avsc
482
+ # my.namespace.responses.Index.avsc
483
+
484
+ # If all routes use the default, you can add them all at once
485
+ schemas :index, :show, :update
486
+
487
+ # Different schemas can be specified as well
488
+ schemas :index, :show, request: 'IndexRequest', response: 'IndexResponse'
489
+
490
+ # To access the encoded data, use the `payload` helper method, and to render it back,
491
+ # use the `render_schema` method.
492
+
493
+ def index
494
+ response = { 'response_id' => payload['request_id'] + 'hi mom' }
495
+ render_schema(response)
496
+ end
497
+ end
498
+ ```
499
+
500
+ To make use of this feature, your requests and responses need to have the correct content type.
501
+ For Avro content, this is the `avro/binary` content type.
502
+
450
503
  # Database Backend
451
504
 
452
505
  Deimos provides a way to allow Kafka messages to be created inside a
@@ -546,7 +599,7 @@ class MyConsumer < Deimos::ActiveRecordConsumer
546
599
 
547
600
  # Optional override to change the attributes of the record before they
548
601
  # are saved.
549
- def record_attributes(payload)
602
+ def record_attributes(payload, key)
550
603
  super.merge(:some_field => 'some_value')
551
604
  end
552
605
 
@@ -627,7 +680,7 @@ class MyConsumer < Deimos::ActiveRecordConsumer
627
680
 
628
681
  # Optional override to change the attributes of the record before they
629
682
  # are saved.
630
- def record_attributes(payload)
683
+ def record_attributes(payload, key)
631
684
  super.merge(:some_field => 'some_value')
632
685
  end
633
686
  end
@@ -23,7 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_runtime_dependency('ruby-kafka', '~> 0.7')
24
24
  spec.add_runtime_dependency('sigurd', '0.0.1')
25
25
 
26
- spec.add_development_dependency('activerecord', '~> 6')
27
26
  spec.add_development_dependency('activerecord-import')
28
27
  spec.add_development_dependency('avro', '~> 1.9')
29
28
  spec.add_development_dependency('database_cleaner', '~> 1.7')
@@ -38,6 +37,7 @@ Gem::Specification.new do |spec|
38
37
  spec.add_development_dependency('rake', '~> 13')
39
38
  spec.add_development_dependency('rspec', '~> 3')
40
39
  spec.add_development_dependency('rspec_junit_formatter', '~>0.3')
40
+ spec.add_development_dependency('rspec-rails', '~> 4')
41
41
  spec.add_development_dependency('rubocop', '~> 0.72')
42
42
  spec.add_development_dependency('rubocop-rspec', '~> 1.27')
43
43
  spec.add_development_dependency('sqlite3', '~> 1.3')
@@ -23,6 +23,7 @@ require 'deimos/monkey_patches/phobos_producer'
23
23
  require 'deimos/monkey_patches/phobos_cli'
24
24
 
25
25
  require 'deimos/railtie' if defined?(Rails)
26
+ require 'deimos/utils/schema_controller_mixin' if defined?(ActionController)
26
27
 
27
28
  if defined?(ActiveRecord)
28
29
  require 'deimos/kafka_source'
@@ -88,8 +88,13 @@ module Deimos
88
88
 
89
89
  # Create payloads with payload + key attributes
90
90
  upserts = messages.map do |m|
91
- record_attributes(m.payload, m.key)&.
92
- merge(record_key(m.key))
91
+ attrs = if self.method(:record_attributes).parameters.size == 2
92
+ record_attributes(m.payload, m.key)
93
+ else
94
+ record_attributes(m.payload)
95
+ end
96
+
97
+ attrs&.merge(record_key(m.key))
93
98
  end
94
99
 
95
100
  # If overridden record_attributes indicated no record, skip
@@ -37,7 +37,14 @@ module Deimos
37
37
  record = klass.new
38
38
  assign_key(record, payload, key)
39
39
  end
40
- attrs = record_attributes(payload.with_indifferent_access, key)
40
+
41
+ # for backwards compatibility
42
+ # TODO next major release we should deprecate this
43
+ attrs = if self.method(:record_attributes).parameters.size == 2
44
+ record_attributes(payload.with_indifferent_access, key)
45
+ else
46
+ record_attributes(payload.with_indifferent_access)
47
+ end
41
48
  # don't use attributes= - bypass Rails < 5 attr_protected
42
49
  attrs.each do |k, v|
43
50
  record.send("#{k}=", v)
@@ -10,7 +10,7 @@ module Deimos
10
10
 
11
11
  # :nodoc:
12
12
  def around_consume(payload, metadata)
13
- decoded_payload = payload.dup
13
+ decoded_payload = payload.nil? ? nil : payload.dup
14
14
  new_metadata = metadata.dup
15
15
  benchmark = Benchmark.measure do
16
16
  _with_span do
@@ -46,13 +46,18 @@ module Deimos
46
46
 
47
47
  messages = exception.failed_messages
48
48
  messages.group_by(&:topic).each do |topic, batch|
49
- next if batch.empty?
49
+ producer = Deimos::Producer.descendants.find { |c| c.topic == topic }
50
+ next if batch.empty? || !producer
50
51
 
51
- producer = batch.first.metadata[:producer_name]
52
- payloads = batch.map { |m| m.metadata[:decoded_payload] }
52
+ decoder = Deimos.schema_backend(schema: producer.config[:schema],
53
+ namespace: producer.config[:namespace])
54
+ payloads = batch.map { |m| decoder.decode(m.value) }
53
55
 
54
- Deimos.config.metrics&.count('publish_error', payloads.size,
55
- tags: %W(topic:#{topic}))
56
+ Deimos.config.metrics&.increment(
57
+ 'publish_error',
58
+ tags: %W(topic:#{topic}),
59
+ by: payloads.size
60
+ )
56
61
  Deimos.instrument(
57
62
  'produce_error',
58
63
  producer: producer,
@@ -82,6 +82,11 @@ module Deimos
82
82
  :avro_validation
83
83
  end
84
84
 
85
+ # @override
86
+ def self.content_type
87
+ 'avro/binary'
88
+ end
89
+
85
90
  private
86
91
 
87
92
  # @param schema [String]
@@ -10,18 +10,37 @@ module Deimos
10
10
  @schema = schema
11
11
  end
12
12
 
13
- # @param type [Symbol]
13
+ # Coerce sub-records in a payload to match the schema.
14
+ # @param type [Avro::Schema::UnionSchema]
15
+ # @param val [Object]
16
+ # @return [Object]
17
+ def coerce_union(type, val)
18
+ union_types = type.schemas.map { |s| s.type.to_sym }
19
+ return nil if val.nil? && union_types.include?(:null)
20
+
21
+ schema_type = type.schemas.find { |s| s.type.to_sym != :null }
22
+ coerce_type(schema_type, val)
23
+ end
24
+
25
+ # Coerce sub-records in a payload to match the schema.
26
+ # @param type [Avro::Schema::RecordSchema]
27
+ # @param val [Object]
28
+ # @return [Object]
29
+ def coerce_record(type, val)
30
+ record = val.map do |name, value|
31
+ field = type.fields.find { |f| f.name == name }
32
+ coerce_type(field.type, value)
33
+ end
34
+ val.keys.zip(record).to_h
35
+ end
36
+
37
+ # Coerce values in a payload to match the schema.
38
+ # @param type [Avro::Schema]
14
39
  # @param val [Object]
15
40
  # @return [Object]
16
41
  def coerce_type(type, val)
17
42
  int_classes = [Time, ActiveSupport::TimeWithZone]
18
43
  field_type = type.type.to_sym
19
- if field_type == :union
20
- union_types = type.schemas.map { |s| s.type.to_sym }
21
- return nil if val.nil? && union_types.include?(:null)
22
-
23
- field_type = union_types.find { |t| t != :null }
24
- end
25
44
 
26
45
  case field_type
27
46
  when :int, :long
@@ -32,14 +51,12 @@ module Deimos
32
51
  else
33
52
  val # this will fail
34
53
  end
35
-
36
54
  when :float, :double
37
55
  if val.is_a?(Numeric) || _is_float_string?(val)
38
56
  val.to_f
39
57
  else
40
58
  val # this will fail
41
59
  end
42
-
43
60
  when :string
44
61
  if val.respond_to?(:to_str)
45
62
  val.to_s
@@ -54,6 +71,10 @@ module Deimos
54
71
  else
55
72
  true
56
73
  end
74
+ when :union
75
+ coerce_union(type, val)
76
+ when :record
77
+ coerce_record(type, val)
57
78
  else
58
79
  val
59
80
  end
@@ -71,6 +71,12 @@ module Deimos
71
71
  :mock
72
72
  end
73
73
 
74
+ # The content type to use when encoding / decoding requests over HTTP via ActionController.
75
+ # @return [String]
76
+ def self.content_type
77
+ raise NotImplementedError
78
+ end
79
+
74
80
  # Encode a payload. To be defined by subclass.
75
81
  # @param payload [Hash]
76
82
  # @param schema [Symbol|String]
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deimos
4
+ module Utils
5
+ # Mixin to automatically decode schema-encoded payloads when given the correct content type,
6
+ # and provide the `render_schema` method to encode the payload for responses.
7
+ module SchemaControllerMixin
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ Mime::Type.register('avro/binary', :avro)
12
+
13
+ attr_accessor :payload
14
+
15
+ if respond_to?(:before_filter)
16
+ before_filter(:decode_schema, if: :schema_format?)
17
+ else
18
+ before_action(:decode_schema, if: :schema_format?)
19
+ end
20
+ end
21
+
22
+ # :nodoc:
23
+ module ClassMethods
24
+ # @return [Hash<String, Hash<Symbol, String>>]
25
+ def schema_mapping
26
+ @schema_mapping ||= {}
27
+ end
28
+
29
+ # Indicate which schemas should be assigned to actions.
30
+ # @param actions [Symbol]
31
+ # @param request [String]
32
+ # @param response [String]
33
+ def schemas(*actions, request: nil, response: nil)
34
+ actions.each do |action|
35
+ request ||= action.to_s.titleize
36
+ response ||= action.to_s.titleize
37
+ schema_mapping[action.to_s] = { request: request, response: response }
38
+ end
39
+ end
40
+
41
+ # @return [Hash<Symbol, String>]
42
+ def namespaces
43
+ @namespaces ||= {}
44
+ end
45
+
46
+ # Set the namespace for both requests and responses.
47
+ # @param name [String]
48
+ def namespace(name)
49
+ request_namespace(name)
50
+ response_namespace(name)
51
+ end
52
+
53
+ # Set the namespace for requests.
54
+ # @param name [String]
55
+ def request_namespace(name)
56
+ namespaces[:request] = name
57
+ end
58
+
59
+ # Set the namespace for repsonses.
60
+ # @param name [String]
61
+ def response_namespace(name)
62
+ namespaces[:response] = name
63
+ end
64
+ end
65
+
66
+ # @return [Boolean]
67
+ def schema_format?
68
+ request.content_type == Deimos.schema_backend_class.content_type
69
+ end
70
+
71
+ # Get the namespace from either an existing instance variable, or tease it out of the schema.
72
+ # @param type [Symbol] :request or :response
73
+ # @return [Array<String, String>] the namespace and schema.
74
+ def parse_namespace(type)
75
+ namespace = self.class.namespaces[type]
76
+ schema = self.class.schema_mapping[params['action']][type]
77
+ if schema.nil?
78
+ raise "No #{type} schema defined for #{params[:controller]}##{params[:action]}!"
79
+ end
80
+
81
+ if namespace.nil?
82
+ last_period = schema.rindex('.')
83
+ namespace, schema = schema.split(last_period)
84
+ end
85
+ if namespace.nil? || schema.nil?
86
+ raise "No request namespace defined for #{params[:controller]}##{params[:action]}!"
87
+ end
88
+
89
+ [namespace, schema]
90
+ end
91
+
92
+ # Decode the payload with the parameters.
93
+ def decode_schema
94
+ namespace, schema = parse_namespace(:request)
95
+ decoder = Deimos.schema_backend(schema: schema, namespace: namespace)
96
+ @payload = decoder.decode(request.body.read).with_indifferent_access
97
+ request.body.rewind if request.body.respond_to?(:rewind)
98
+ end
99
+
100
+ # Render a hash into a payload as specified by the configured schema and namespace.
101
+ # @param payload [Hash]
102
+ def render_schema(payload, schema: nil, namespace: nil)
103
+ namespace, schema = parse_namespace(:response) if !schema && !namespace
104
+ encoder = Deimos.schema_backend(schema: schema, namespace: namespace)
105
+ encoded = encoder.encode(payload)
106
+ response.headers['Content-Type'] = encoder.class.content_type
107
+ send_data(encoded)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Deimos
4
- VERSION = '1.8.1-beta1'
4
+ VERSION = '1.8.1-beta6'
5
5
  end
@@ -32,6 +32,12 @@ module ConsumerTest
32
32
  end
33
33
  end
34
34
 
35
+ it 'should consume a nil message' do
36
+ test_consume_message(MyConsumer, nil) do |payload, _metadata|
37
+ expect(payload).to be_nil
38
+ end
39
+ end
40
+
35
41
  it 'should consume a message idempotently' do
36
42
  # testing for a crash and re-consuming the same message/metadata
37
43
  key = { 'test_id' => 'foo' }
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Deimos::KafkaListener do
4
+ include_context 'with widgets'
5
+
6
+ prepend_before(:each) do
7
+ producer_class = Class.new(Deimos::Producer) do
8
+ schema 'MySchema'
9
+ namespace 'com.my-namespace'
10
+ topic 'my-topic'
11
+ key_config none: true
12
+ end
13
+ stub_const('MyProducer', producer_class)
14
+ end
15
+
16
+ before(:each) do
17
+ Deimos.configure do |c|
18
+ c.producers.backend = :kafka
19
+ c.schema.backend = :avro_local
20
+ end
21
+ allow_any_instance_of(Kafka::Cluster).to receive(:add_target_topics)
22
+ allow_any_instance_of(Kafka::Cluster).to receive(:partitions_for).
23
+ and_raise(Kafka::Error)
24
+ end
25
+
26
+ describe '.send_produce_error' do
27
+ let(:payloads) do
28
+ [{ 'test_id' => 'foo', 'some_int' => 123 },
29
+ { 'test_id' => 'bar', 'some_int' => 124 }]
30
+ end
31
+
32
+ it 'should listen to publishing errors and republish as Deimos events' do
33
+ allow(Deimos::Producer).to receive(:descendants).and_return([MyProducer])
34
+ Deimos.subscribe('produce_error') do |event|
35
+ expect(event.payload).to include(
36
+ producer: MyProducer,
37
+ topic: 'my-topic',
38
+ payloads: payloads
39
+ )
40
+ end
41
+ expect(Deimos.config.metrics).to receive(:increment).
42
+ with('publish_error', tags: %w(topic:my-topic), by: 2)
43
+ expect { MyProducer.publish_list(payloads) }.to raise_error(Kafka::DeliveryFailed)
44
+ end
45
+
46
+ it 'should not send any notifications when producer is not found' do
47
+ Deimos.subscribe('produce_error') do |_|
48
+ raise 'OH NOES'
49
+ end
50
+ allow(Deimos::Producer).to receive(:descendants).and_return([])
51
+ expect(Deimos.config.metrics).not_to receive(:increment).with('publish_error', anything)
52
+ expect { MyProducer.publish_list(payloads) }.to raise_error(Kafka::DeliveryFailed)
53
+ end
54
+ end
55
+ end
@@ -41,6 +41,14 @@ module ProducerTest
41
41
  end
42
42
  stub_const('MyNoKeyProducer', producer_class)
43
43
 
44
+ producer_class = Class.new(Deimos::Producer) do
45
+ schema 'MyNestedSchema'
46
+ namespace 'com.my-namespace'
47
+ topic 'my-topic'
48
+ key_config field: 'test_id'
49
+ end
50
+ stub_const('MyNestedSchemaProducer', producer_class)
51
+
44
52
  producer_class = Class.new(Deimos::Producer) do
45
53
  schema 'MySchema'
46
54
  namespace 'com.my-namespace'
@@ -233,6 +241,34 @@ module ProducerTest
233
241
  )
234
242
  end
235
243
 
244
+ it 'should properly encode and coerce values with a nested record' do
245
+ expect(MyNestedSchemaProducer.encoder).to receive(:encode_key).with('test_id', 'foo', topic: 'my-topic-key')
246
+ MyNestedSchemaProducer.publish(
247
+ 'test_id' => 'foo',
248
+ 'test_float' => BigDecimal('123.456'),
249
+ 'test_array' => ['1'],
250
+ 'some_nested_record' => {
251
+ 'some_int' => 123,
252
+ 'some_float' => BigDecimal('456.789'),
253
+ 'some_string' => '123',
254
+ 'some_optional_int' => nil
255
+ },
256
+ 'some_optional_record' => nil
257
+ )
258
+ expect(MyNestedSchemaProducer.topic).to have_sent(
259
+ 'test_id' => 'foo',
260
+ 'test_float' => 123.456,
261
+ 'test_array' => ['1'],
262
+ 'some_nested_record' => {
263
+ 'some_int' => 123,
264
+ 'some_float' => 456.789,
265
+ 'some_string' => '123',
266
+ 'some_optional_int' => nil
267
+ },
268
+ 'some_optional_record' => nil
269
+ )
270
+ end
271
+
236
272
  it 'should error with nothing set' do
237
273
  expect {
238
274
  MyErrorProducer.publish_list(
@@ -0,0 +1,62 @@
1
+ {
2
+ "namespace": "com.my-namespace",
3
+ "name": "MyNestedSchema",
4
+ "type": "record",
5
+ "doc": "Test schema",
6
+ "fields": [
7
+ {
8
+ "name": "test_id",
9
+ "type": "string",
10
+ "doc": "test string"
11
+ },
12
+ {
13
+ "name": "test_float",
14
+ "type": "float",
15
+ "doc": "test float"
16
+ },
17
+ {
18
+ "name": "test_array",
19
+ "type": {
20
+ "type": "array",
21
+ "items": "string"
22
+ }
23
+ },
24
+ {
25
+ "name": "some_nested_record",
26
+ "doc": "some nested record",
27
+ "type": {
28
+ "name": "MyNestedRecord",
29
+ "type": "record",
30
+ "fields": [
31
+ {
32
+ "name": "some_int",
33
+ "type": "int",
34
+ "doc": "some int"
35
+ },
36
+ {
37
+ "name": "some_float",
38
+ "type": "float",
39
+ "doc": "some float"
40
+ },
41
+ {
42
+ "name": "some_string",
43
+ "type": "string",
44
+ "doc": "some string"
45
+ },
46
+ {
47
+ "name": "some_optional_int",
48
+ "type": [ "null", "int" ],
49
+ "doc": "some optional int",
50
+ "default": null
51
+ }
52
+ ]
53
+ }
54
+ },
55
+ {
56
+ "name": "some_optional_record",
57
+ "doc": "some optional record",
58
+ "type": [ "null", "MyNestedRecord" ],
59
+ "default": null
60
+ }
61
+ ]
62
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.request",
3
+ "name": "Index",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "request_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.request",
3
+ "name": "UpdateRequest",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "update_request_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.response",
3
+ "name": "Index",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "response_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "namespace": "com.my-namespace.response",
3
+ "name": "UpdateResponse",
4
+ "type": "record",
5
+ "fields": [
6
+ {
7
+ "name": "update_response_id",
8
+ "type": "string"
9
+ }
10
+ ]
11
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
4
4
  require 'active_record'
5
+ require 'action_controller/railtie'
5
6
  require 'database_cleaner'
6
7
  require 'deimos'
7
8
  require 'deimos/metrics/mock'
@@ -11,6 +12,11 @@ require 'active_support/testing/time_helpers'
11
12
  require 'activerecord-import'
12
13
  require 'handlers/my_batch_consumer'
13
14
  require 'handlers/my_consumer'
15
+ require 'rspec/rails'
16
+
17
+ class DeimosApp < Rails::Application
18
+ end
19
+ DeimosApp.initialize!
14
20
 
15
21
  # Helpers for Executor/DbProducer
16
22
  module TestRunners
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deimos/utils/schema_controller_mixin'
4
+ require 'deimos/schema_backends/avro_local'
5
+
6
+ RSpec.describe Deimos::Utils::SchemaControllerMixin, type: :controller do
7
+
8
+ before(:each) do
9
+ Deimos.configure do
10
+ schema.backend(:avro_local)
11
+ end
12
+ end
13
+
14
+ controller(ActionController::Base) do
15
+ include Deimos::Utils::SchemaControllerMixin # rubocop:disable RSpec/DescribedClass
16
+
17
+ request_namespace 'com.my-namespace.request'
18
+ response_namespace 'com.my-namespace.response'
19
+ schemas :index, :show
20
+ schemas :update, request: 'UpdateRequest', response: 'UpdateResponse'
21
+
22
+ # :nodoc:
23
+ def index
24
+ render_schema({ 'response_id' => payload[:request_id] + ' mom' })
25
+ end
26
+
27
+ # :nodoc:
28
+ def show
29
+ render_schema({ 'response_id' => payload[:request_id] + ' dad' })
30
+ end
31
+
32
+ # :nodoc:
33
+ def update
34
+ render_schema({ 'update_response_id' => payload[:update_request_id] + ' sis' })
35
+ end
36
+ end
37
+
38
+ it 'should render the correct response for index' do
39
+ request_backend = Deimos.schema_backend(schema: 'Index',
40
+ namespace: 'com.my-namespace.request')
41
+ response_backend = Deimos.schema_backend(schema: 'Index',
42
+ namespace: 'com.my-namespace.response')
43
+ request.content_type = 'avro/binary'
44
+ get :index, body: request_backend.encode({ 'request_id' => 'hi' })
45
+ expect(response_backend.decode(response.body)).to eq({ 'response_id' => 'hi mom' })
46
+ end
47
+
48
+ it 'should render the correct response for show' do
49
+ request_backend = Deimos.schema_backend(schema: 'Index',
50
+ namespace: 'com.my-namespace.request')
51
+ response_backend = Deimos.schema_backend(schema: 'Index',
52
+ namespace: 'com.my-namespace.response')
53
+ request.content_type = 'avro/binary'
54
+ get :show, params: { id: 1 }, body: request_backend.encode({ 'request_id' => 'hi' })
55
+ expect(response_backend.decode(response.body)).to eq({ 'response_id' => 'hi dad' })
56
+ end
57
+
58
+ it 'should render the correct response for update' do
59
+ request_backend = Deimos.schema_backend(schema: 'UpdateRequest',
60
+ namespace: 'com.my-namespace.request')
61
+ response_backend = Deimos.schema_backend(schema: 'UpdateResponse',
62
+ namespace: 'com.my-namespace.response')
63
+ request.content_type = 'avro/binary'
64
+ post :update, params: { id: 1 }, body: request_backend.encode({ 'update_request_id' => 'hi' })
65
+ expect(response_backend.decode(response.body)).to eq({ 'update_response_id' => 'hi sis' })
66
+ end
67
+
68
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deimos-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.1.pre.beta1
4
+ version: 1.8.1.pre.beta6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-22 00:00:00.000000000 Z
11
+ date: 2020-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: avro_turf
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.0.1
69
- - !ruby/object:Gem::Dependency
70
- name: activerecord
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '6'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '6'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: activerecord-import
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -276,6 +262,20 @@ dependencies:
276
262
  - - "~>"
277
263
  - !ruby/object:Gem::Version
278
264
  version: '0.3'
265
+ - !ruby/object:Gem::Dependency
266
+ name: rspec-rails
267
+ requirement: !ruby/object:Gem::Requirement
268
+ requirements:
269
+ - - "~>"
270
+ - !ruby/object:Gem::Version
271
+ version: '4'
272
+ type: :development
273
+ prerelease: false
274
+ version_requirements: !ruby/object:Gem::Requirement
275
+ requirements:
276
+ - - "~>"
277
+ - !ruby/object:Gem::Version
278
+ version: '4'
279
279
  - !ruby/object:Gem::Dependency
280
280
  name: rubocop
281
281
  requirement: !ruby/object:Gem::Requirement
@@ -399,6 +399,7 @@ files:
399
399
  - lib/deimos/utils/deadlock_retry.rb
400
400
  - lib/deimos/utils/inline_consumer.rb
401
401
  - lib/deimos/utils/lag_reporter.rb
402
+ - lib/deimos/utils/schema_controller_mixin.rb
402
403
  - lib/deimos/version.rb
403
404
  - lib/generators/deimos/active_record/templates/migration.rb.tt
404
405
  - lib/generators/deimos/active_record/templates/model.rb.tt
@@ -427,6 +428,7 @@ files:
427
428
  - spec/generators/active_record_generator_spec.rb
428
429
  - spec/handlers/my_batch_consumer.rb
429
430
  - spec/handlers/my_consumer.rb
431
+ - spec/kafka_listener_spec.rb
430
432
  - spec/kafka_source_spec.rb
431
433
  - spec/kafka_topic_info_spec.rb
432
434
  - spec/message_spec.rb
@@ -440,6 +442,7 @@ files:
440
442
  - spec/schema_backends/avro_validation_spec.rb
441
443
  - spec/schema_backends/base_spec.rb
442
444
  - spec/schemas/com/my-namespace/Generated.avsc
445
+ - spec/schemas/com/my-namespace/MyNestedSchema.avsc
443
446
  - spec/schemas/com/my-namespace/MySchema-key.avsc
444
447
  - spec/schemas/com/my-namespace/MySchema.avsc
445
448
  - spec/schemas/com/my-namespace/MySchemaCompound-key.avsc
@@ -450,12 +453,17 @@ files:
450
453
  - spec/schemas/com/my-namespace/Wibble.avsc
451
454
  - spec/schemas/com/my-namespace/Widget.avsc
452
455
  - spec/schemas/com/my-namespace/WidgetTheSecond.avsc
456
+ - spec/schemas/com/my-namespace/request/Index.avsc
457
+ - spec/schemas/com/my-namespace/request/UpdateRequest.avsc
458
+ - spec/schemas/com/my-namespace/response/Index.avsc
459
+ - spec/schemas/com/my-namespace/response/UpdateResponse.avsc
453
460
  - spec/spec_helper.rb
454
461
  - spec/utils/db_poller_spec.rb
455
462
  - spec/utils/db_producer_spec.rb
456
463
  - spec/utils/deadlock_retry_spec.rb
457
464
  - spec/utils/lag_reporter_spec.rb
458
465
  - spec/utils/platform_schema_validation_spec.rb
466
+ - spec/utils/schema_controller_mixin_spec.rb
459
467
  - support/deimos-solo.png
460
468
  - support/deimos-with-name-next.png
461
469
  - support/deimos-with-name.png
@@ -501,6 +509,7 @@ test_files:
501
509
  - spec/generators/active_record_generator_spec.rb
502
510
  - spec/handlers/my_batch_consumer.rb
503
511
  - spec/handlers/my_consumer.rb
512
+ - spec/kafka_listener_spec.rb
504
513
  - spec/kafka_source_spec.rb
505
514
  - spec/kafka_topic_info_spec.rb
506
515
  - spec/message_spec.rb
@@ -514,6 +523,7 @@ test_files:
514
523
  - spec/schema_backends/avro_validation_spec.rb
515
524
  - spec/schema_backends/base_spec.rb
516
525
  - spec/schemas/com/my-namespace/Generated.avsc
526
+ - spec/schemas/com/my-namespace/MyNestedSchema.avsc
517
527
  - spec/schemas/com/my-namespace/MySchema-key.avsc
518
528
  - spec/schemas/com/my-namespace/MySchema.avsc
519
529
  - spec/schemas/com/my-namespace/MySchemaCompound-key.avsc
@@ -524,9 +534,14 @@ test_files:
524
534
  - spec/schemas/com/my-namespace/Wibble.avsc
525
535
  - spec/schemas/com/my-namespace/Widget.avsc
526
536
  - spec/schemas/com/my-namespace/WidgetTheSecond.avsc
537
+ - spec/schemas/com/my-namespace/request/Index.avsc
538
+ - spec/schemas/com/my-namespace/request/UpdateRequest.avsc
539
+ - spec/schemas/com/my-namespace/response/Index.avsc
540
+ - spec/schemas/com/my-namespace/response/UpdateResponse.avsc
527
541
  - spec/spec_helper.rb
528
542
  - spec/utils/db_poller_spec.rb
529
543
  - spec/utils/db_producer_spec.rb
530
544
  - spec/utils/deadlock_retry_spec.rb
531
545
  - spec/utils/lag_reporter_spec.rb
532
546
  - spec/utils/platform_schema_validation_spec.rb
547
+ - spec/utils/schema_controller_mixin_spec.rb