avromatic 3.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +11 -12
  3. data/.github/CODEOWNERS +1 -0
  4. data/.rubocop.yml +2 -2
  5. data/.ruby-version +1 -1
  6. data/Appraisals +15 -15
  7. data/CHANGELOG.md +18 -0
  8. data/README.md +24 -3
  9. data/avromatic.gemspec +15 -6
  10. data/gemfiles/{avro1_9_rails5_2.gemfile → avro1_10_rails7_0.gemfile} +3 -3
  11. data/gemfiles/{avro1_9_rails6_1.gemfile → avro1_11_rails6_1.gemfile} +1 -1
  12. data/gemfiles/{avro1_9_rails6_0.gemfile → avro1_11_rails7_0.gemfile} +3 -3
  13. data/lib/avromatic/io/datum_writer.rb +1 -3
  14. data/lib/avromatic/messaging.rb +2 -4
  15. data/lib/avromatic/model/attributes.rb +30 -13
  16. data/lib/avromatic/model/configurable.rb +3 -1
  17. data/lib/avromatic/model/configuration.rb +9 -1
  18. data/lib/avromatic/model/message_decoder.rb +3 -1
  19. data/lib/avromatic/model/messaging_serialization.rb +9 -6
  20. data/lib/avromatic/model/nested_models.rb +4 -2
  21. data/lib/avromatic/model/raw_serialization.rb +6 -5
  22. data/lib/avromatic/model/types/array_type.rb +1 -0
  23. data/lib/avromatic/model/types/custom_type.rb +6 -1
  24. data/lib/avromatic/model/types/enum_type.rb +1 -0
  25. data/lib/avromatic/model/types/fixed_type.rb +1 -0
  26. data/lib/avromatic/model/types/map_type.rb +1 -0
  27. data/lib/avromatic/model/types/record_type.rb +1 -0
  28. data/lib/avromatic/model/types/type_factory.rb +7 -3
  29. data/lib/avromatic/model/types/union_type.rb +2 -3
  30. data/lib/avromatic/model/validation.rb +1 -3
  31. data/lib/avromatic/model_registry.rb +13 -2
  32. data/lib/avromatic/rspec.rb +1 -0
  33. data/lib/avromatic/version.rb +1 -1
  34. data/lib/avromatic.rb +6 -0
  35. metadata +21 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d90a142c407a453ef9007373f215d8acd137937e54b46aa2c29e8edcf8440aef
4
- data.tar.gz: c8eecbb9860859346d9a67e3e2a78a2e38447bdff4d4fb9208f57bcf89940215
3
+ metadata.gz: edd31aa475804bf20a1f5b4ddbe90e8c98460617daeaf2e1641316c08cce505b
4
+ data.tar.gz: b20ae31b2aeff034f9fa99e2a90d4bb78dfe894967ab8e0267b1df6ee954a15d
5
5
  SHA512:
6
- metadata.gz: 77b078294c887e298e4ede5f1f47cfccaccffd0b065ad1d4bad9820d2af8c77e522462282d1d44db35acefaf6fc301ccb7a59dfda844c5ebee297d51792ee912
7
- data.tar.gz: 8f57c2afee1266f790c031920efb7e1690420e4c6de36d22af3e11361bc5bf7eaabb3eba4e2974a994a7f7509e3db01335c954895c981db4069a8c7828b483de
6
+ metadata.gz: 6e4d441e7645b52840725041d361f699266a9e5dd974e8cd588c8f4750ba21eb4efd016b9c6c20e034a98ed79b673f9aa12a5f0b3a56312a57b344659b93606d
7
+ data.tar.gz: 5b8ba9cc0049f80a31f88399ca504796997038b969d728be5867d6fd5bc6868786e4ae9dffc0de8a7c1db99e129f9398e4f867794dbd449d54f74c69650735bb
data/.circleci/config.yml CHANGED
@@ -2,14 +2,14 @@ version: 2.1
2
2
  jobs:
3
3
  lint:
4
4
  docker:
5
- - image: salsify/ruby_ci:2.5.8
5
+ - image: salsify/ruby_ci:2.7.4
6
6
  working_directory: ~/avromatic
7
7
  steps:
8
8
  - checkout
9
9
  - restore_cache:
10
10
  keys:
11
- - v2-gems-ruby-2.5.8-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
12
- - v2-gems-ruby-2.5.8-
11
+ - v2-gems-ruby-2.7.4-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
12
+ - v2-gems-ruby-2.7.4-
13
13
  - run:
14
14
  name: Install Gems
15
15
  command: |
@@ -18,7 +18,7 @@ jobs:
18
18
  bundle clean
19
19
  fi
20
20
  - save_cache:
21
- key: v2-gems-ruby-2.5.8-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
21
+ key: v2-gems-ruby-2.7.4-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
22
22
  paths:
23
23
  - "vendor/bundle"
24
24
  - "gemfiles/vendor/bundle"
@@ -69,21 +69,20 @@ workflows:
69
69
  matrix:
70
70
  parameters:
71
71
  gemfile:
72
- - "gemfiles/avro1_9_rails5_2.gemfile"
73
72
  - "gemfiles/avro1_10_rails5_2.gemfile"
74
- - "gemfiles/avro1_9_rails6_0.gemfile"
75
73
  - "gemfiles/avro1_10_rails6_0.gemfile"
76
74
  - "gemfiles/avro1_10_rails6_1.gemfile"
77
- - "gemfiles/avro1_9_rails6_1.gemfile"
75
+ - "gemfiles/avro1_10_rails7_0.gemfile"
76
+ - "gemfiles/avro1_11_rails7_0.gemfile"
78
77
  ruby-version:
79
- - "2.5.8"
80
- - "2.6.6"
81
- - "2.7.2"
78
+ - "2.7.4"
82
79
  - test:
83
80
  matrix:
84
81
  parameters:
85
82
  gemfile:
86
83
  - "gemfiles/avro1_10_rails6_1.gemfile"
87
- - "gemfiles/avro1_9_rails6_1.gemfile"
84
+ - "gemfiles/avro1_10_rails7_0.gemfile"
85
+ - "gemfiles/avro1_11_rails7_0.gemfile"
88
86
  ruby-version:
89
- - "3.0.0"
87
+ - "3.0.3"
88
+ - "3.1.0"
@@ -0,0 +1 @@
1
+ * @jturkel @kphelps @tjwp
data/.rubocop.yml CHANGED
@@ -2,10 +2,10 @@ inherit_gem:
2
2
  salsify_rubocop: conf/rubocop.yml
3
3
 
4
4
  AllCops:
5
- TargetRubyVersion: 2.5
5
+ TargetRubyVersion: 2.7
6
6
  Exclude:
7
7
  - 'vendor/**/*'
8
- - 'gemfiles/vendor/**/*'
8
+ - 'gemfiles/**/*'
9
9
 
10
10
  Style/MultilineBlockChain:
11
11
  Enabled: false
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.5.8
1
+ 2.7.4
data/Appraisals CHANGED
@@ -1,37 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise 'avro1_9-rails5_2' do
4
- gem 'avro', '1.9.2'
5
- gem 'activesupport', '~> 5.2.0'
6
- gem 'activemodel', '~> 5.2.0'
7
- end
8
-
9
3
  appraise 'avro1_10-rails5_2' do
10
4
  gem 'avro', '~> 1.10.0'
11
5
  gem 'activesupport', '~> 5.2.0'
12
6
  gem 'activemodel', '~> 5.2.0'
13
7
  end
14
8
 
15
- appraise 'avro1_9-rails6_0' do
16
- gem 'avro', '1.9.2'
17
- gem 'activesupport', '~> 6.0.0'
18
- gem 'activemodel', '~> 6.0.0'
19
- end
20
-
21
9
  appraise 'avro1_10-rails6_0' do
22
10
  gem 'avro', '~> 1.10.0'
23
11
  gem 'activesupport', '~> 6.0.0'
24
12
  gem 'activemodel', '~> 6.0.0'
25
13
  end
26
14
 
27
- appraise 'avro1_9-rails6_1' do
28
- gem 'avro', '1.9.2'
15
+ appraise 'avro1_10-rails6_1' do
16
+ gem 'avro', '~> 1.10.0'
29
17
  gem 'activesupport', '~> 6.1.0'
30
18
  gem 'activemodel', '~> 6.1.0'
31
19
  end
32
20
 
33
- appraise 'avro1_10-rails6_1' do
21
+ appraise 'avro1_10-rails7_0' do
34
22
  gem 'avro', '~> 1.10.0'
23
+ gem 'activesupport', '~> 7.0.0'
24
+ gem 'activemodel', '~> 7.0.0'
25
+ end
26
+
27
+ appraise 'avro1_11-rails6_1' do
28
+ gem 'avro', '~> 1.11.0'
35
29
  gem 'activesupport', '~> 6.1.0'
36
30
  gem 'activemodel', '~> 6.1.0'
37
31
  end
32
+
33
+ appraise 'avro1_11-rails7_0' do
34
+ gem 'avro', '~> 1.11.0'
35
+ gem 'activesupport', '~> 7.0.0'
36
+ gem 'activemodel', '~> 7.0.0'
37
+ end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # avromatic changelog
2
2
 
3
+ ## 4.1.0
4
+ - Add support for specifying a subject for the avro schema when building an Avromatic model
5
+
6
+ ## 4.0.0
7
+ - Drop support for Ruby 2.6.
8
+ - Drop support for Avro 1.9.
9
+ - Add support for Avro 1.11.
10
+ - Add support for Rails 7.0.
11
+
12
+ ## 3.0.2
13
+ - Reset the schema registry client between RSpec tests to ensure any cached values are
14
+ consistent with the fake schema registry.
15
+
16
+ ## 3.0.1
17
+ - Raise an error when registering a nested model that has already been auto-generated.
18
+ This avoids hard to troubleshoot coercion errors when instantiating models and fixes
19
+ a regression introduced in Avromatic 2.2.2.
20
+
3
21
  ## 3.0.0
4
22
  - Drop support for Ruby 2.4.
5
23
  - Add support for Ruby 3.0.
data/README.md CHANGED
@@ -72,7 +72,9 @@ and the [Messaging API](#messaging-api).
72
72
  so that they can be referenced by id. Either `schema_registry` or
73
73
  `registry_url` must be configured.
74
74
  * **registry_url**: URL for the schema registry. Either `schema_registry` or
75
- `registry_url` must be configured.
75
+ `registry_url` must be configured. The `build_schema_registry!` method may
76
+ be used to create a caching schema registry client instance based on other
77
+ configuration values.
76
78
  * **use_schema_fingerprint_lookup**: Avromatic supports a Schema Registry
77
79
  [extension](https://github.com/salsify/avro-schema-registry#extensions) that
78
80
  provides an endpoint to lookup existing schema ids by fingerprint.
@@ -91,6 +93,7 @@ Example using a schema registry:
91
93
  Avromatic.configure do |config|
92
94
  config.schema_store = AvroTurf::SchemaStore.new(path: 'avro/schemas')
93
95
  config.registry_url = Rails.configuration.x.avro_schema_registry_url
96
+ config.build_schema_registry!
94
97
  config.build_messaging!
95
98
  end
96
99
  ```
@@ -121,7 +124,7 @@ The Avro schema can be specified by name and loaded using the schema store:
121
124
 
122
125
  ```ruby
123
126
  class MyModel
124
- include Avromatic::Model.build(schema_name :my_model)
127
+ include Avromatic::Model.build(schema_name: :my_model)
125
128
  end
126
129
 
127
130
  # Construct instances by passing in a hash of attributes
@@ -153,12 +156,20 @@ class MyModel
153
156
  end
154
157
  ```
155
158
 
159
+ A specific subject name can be associated with the schema:
160
+ ```ruby
161
+ class MyModel
162
+ include Avromatic::Model.build(schema_name: 'my_model',
163
+ schema_subject: 'my_model-value')
164
+ end
165
+ ```
166
+
156
167
  Models are generated as immutable value
157
168
  objects by default, but can optionally be defined as mutable:
158
169
 
159
170
  ```ruby
160
171
  class MyModel
161
- include Avromatic::Model.build(schema_name :my_model, mutable: true)
172
+ include Avromatic::Model.build(schema_name: :my_model, mutable: true)
162
173
  end
163
174
  ```
164
175
 
@@ -192,6 +203,16 @@ class MyTopic
192
203
  end
193
204
  ```
194
205
 
206
+ A specific subject name can be associated with both the value and key schemas:
207
+ ```ruby
208
+ class MyTopic
209
+ include Avromatic::Model.build(value_schema_name: :topic_value,
210
+ value_schema_subject: 'topic_value-value',
211
+ key_schema_name: :topic_key,
212
+ key_schema_subject: 'topic_key-value')
213
+ end
214
+ ```
215
+
195
216
  A model can also be generated as an anonymous class that can be assigned to a
196
217
  constant:
197
218
 
data/avromatic.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'avromatic/version'
6
6
 
@@ -15,16 +15,25 @@ Gem::Specification.new do |spec|
15
15
  spec.homepage = 'https://github.com/salsify/avromatic.git'
16
16
  spec.license = 'MIT'
17
17
 
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
+ spec.metadata['rubygems_mfa_required'] = 'true'
21
+ else
22
+ raise 'RubyGems 2.0 or newer is required to set allowed_push_host.'
23
+ end
24
+
18
25
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
26
  spec.bindir = 'exe'
20
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
28
  spec.require_paths = ['lib']
22
29
 
23
- spec.required_ruby_version = '>= 2.5'
30
+ spec.metadata['rubygems_mfa_required'] = 'true'
31
+
32
+ spec.required_ruby_version = '>= 2.7'
24
33
 
25
- spec.add_runtime_dependency 'activemodel', '>= 5.2', '< 6.2'
26
- spec.add_runtime_dependency 'activesupport', '>= 5.2', '< 6.2'
27
- spec.add_runtime_dependency 'avro', '>= 1.9.0', '< 1.11'
34
+ spec.add_runtime_dependency 'activemodel', '>= 5.2', '< 7.1'
35
+ spec.add_runtime_dependency 'activesupport', '>= 5.2', '< 7.1'
36
+ spec.add_runtime_dependency 'avro', '>= 1.10.0', '< 1.12'
28
37
  spec.add_runtime_dependency 'avro_schema_registry-client', '>= 0.4.0'
29
38
  spec.add_runtime_dependency 'avro_turf'
30
39
  spec.add_runtime_dependency 'ice_nine'
@@ -36,7 +45,7 @@ Gem::Specification.new do |spec|
36
45
  spec.add_development_dependency 'rake', '~> 13.0'
37
46
  spec.add_development_dependency 'rspec', '~> 3.8'
38
47
  spec.add_development_dependency 'rspec_junit_formatter'
39
- spec.add_development_dependency 'salsify_rubocop', '~> 0.52.1.1'
48
+ spec.add_development_dependency 'salsify_rubocop', '~> 1.1.0'
40
49
  spec.add_development_dependency 'simplecov'
41
50
  spec.add_development_dependency 'webmock'
42
51
  # For AvroSchemaRegistry::FakeServer
@@ -2,8 +2,8 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "avro", "1.9.2"
6
- gem "activesupport", "~> 5.2.0"
7
- gem "activemodel", "~> 5.2.0"
5
+ gem "avro", "~> 1.10.0"
6
+ gem "activesupport", "~> 7.0.0"
7
+ gem "activemodel", "~> 7.0.0"
8
8
 
9
9
  gemspec path: "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "avro", "1.9.2"
5
+ gem "avro", "~> 1.11.0"
6
6
  gem "activesupport", "~> 6.1.0"
7
7
  gem "activemodel", "~> 6.1.0"
8
8
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "avro", "1.9.2"
6
- gem "activesupport", "~> 6.0.0"
7
- gem "activemodel", "~> 6.0.0"
5
+ gem "avro", "~> 1.11.0"
6
+ gem "activesupport", "~> 7.0.0"
7
+ gem "activemodel", "~> 7.0.0"
8
8
 
9
9
  gemspec path: "../"
@@ -21,9 +21,7 @@ module Avromatic
21
21
  end
22
22
  end
23
23
 
24
- unless index_of_schema
25
- raise Avro::IO::AvroTypeError.new(writers_schema, datum)
26
- end
24
+ raise Avro::IO::AvroTypeError.new(writers_schema, datum) unless index_of_schema
27
25
 
28
26
  encoder.write_long(index_of_schema)
29
27
  write_data(writers_schema.schemas[index_of_schema], datum, encoder)
@@ -16,12 +16,10 @@ module Avromatic
16
16
  # The first byte is MAGIC!!!
17
17
  magic_byte = decoder.read(1)
18
18
 
19
- if magic_byte != MAGIC_BYTE
20
- raise "Expected data to begin with a magic byte, got `#{magic_byte.inspect}`"
21
- end
19
+ raise "Expected data to begin with a magic byte, got `#{magic_byte.inspect}`" if magic_byte != MAGIC_BYTE
22
20
 
23
21
  # The schema id is a 4-byte big-endian integer.
24
- schema_id = decoder.read(4).unpack('N').first
22
+ schema_id = decoder.read(4).unpack1('N')
25
23
 
26
24
  writers_schema = @schemas_by_id.fetch(schema_id) do
27
25
  schema_json = @registry.fetch(schema_id)
@@ -22,6 +22,7 @@ module Avromatic
22
22
 
23
23
  class AttributeDefinition
24
24
  attr_reader :name, :name_string, :setter_name, :type, :field, :default, :owner
25
+
25
26
  delegate :serialize, to: :type
26
27
 
27
28
  def initialize(owner:, field:, type:)
@@ -58,21 +59,31 @@ module Avromatic
58
59
  def coerce(input)
59
60
  type.coerce(input)
60
61
  rescue Avromatic::Model::UnknownAttributeError => e
61
- raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
62
+ raise Avromatic::Model::CoercionError.new(
63
+ "Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
62
64
  "because the following unexpected attributes were provided: #{e.unknown_attributes.join(', ')}. " \
63
65
  "Only the following attributes are allowed: #{e.allowed_attributes.join(', ')}. " \
64
- "Provided argument: #{input.inspect}")
66
+ "Provided argument: #{input.inspect}"
67
+ )
65
68
  rescue StandardError
66
69
  if type.input_classes && type.input_classes.none? { |input_class| input.is_a?(input_class) }
67
- raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
68
- "because a #{input.class.name} was provided but expected a #{type.input_classes.map(&:name).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')}. " \
69
- "Provided argument: #{input.inspect}")
70
+ raise Avromatic::Model::CoercionError.new(
71
+ "Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
72
+ "because a #{input.class.name} was provided but expected a #{type.input_classes.map(&:name).to_sentence(
73
+ two_words_connector: ' or ', last_word_connector: ', or '
74
+ )}. " \
75
+ "Provided argument: #{input.inspect}"
76
+ )
70
77
  elsif input.is_a?(Hash) && type.is_a?(Avromatic::Model::Types::UnionType)
71
- raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
72
- "because no union member type matches the provided attributes: #{input.inspect}")
78
+ raise Avromatic::Model::CoercionError.new(
79
+ "Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
80
+ "because no union member type matches the provided attributes: #{input.inspect}"
81
+ )
73
82
  else
74
- raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name}. " \
75
- "Provided argument: #{input.inspect}")
83
+ raise Avromatic::Model::CoercionError.new(
84
+ "Value for #{owner.name}##{name} could not be coerced to a #{type.name}. " \
85
+ "Provided argument: #{input.inspect}"
86
+ )
76
87
  end
77
88
  end
78
89
  end
@@ -106,7 +117,8 @@ module Avromatic
106
117
  unknown_attributes = (data.keys.map(&:to_s) - _attributes.keys.map(&:to_s)).sort
107
118
  allowed_attributes = attribute_definitions.keys.map(&:to_s).sort
108
119
  message = "Unexpected arguments for #{self.class.name}#initialize: #{unknown_attributes.join(', ')}. " \
109
- "Only the following arguments are allowed: #{allowed_attributes.join(', ')}. Provided arguments: #{data.inspect}"
120
+ "Only the following arguments are allowed: #{allowed_attributes.join(', ')}. " \
121
+ "Provided arguments: #{data.inspect}"
110
122
  raise Avromatic::Model::UnknownAttributeError.new(message, unknown_attributes: unknown_attributes,
111
123
  allowed_attributes: allowed_attributes)
112
124
  end
@@ -136,8 +148,8 @@ module Avromatic
136
148
  begin
137
149
  define_avro_attributes(key_avro_schema, generated_methods_module,
138
150
  allow_optional: config.allow_optional_key_fields)
139
- rescue OptionalFieldError => ex
140
- raise "Optional field '#{ex.field.name}' not allowed in key schema."
151
+ rescue OptionalFieldError => e
152
+ raise "Optional field '#{e.field.name}' not allowed in key schema."
141
153
  end
142
154
  end
143
155
  define_avro_attributes(avro_schema, generated_methods_module)
@@ -155,6 +167,7 @@ module Avromatic
155
167
  conflicts =
156
168
  (key_avro_field_names & value_avro_field_names).each_with_object([]) do |name, msgs|
157
169
  next unless schema_fields_differ?(name)
170
+
158
171
  msgs << "Field '#{name}' has a different type in each schema: "\
159
172
  "value #{value_avro_fields_by_name[name]}, "\
160
173
  "key #{key_avro_fields_by_name[name]}"
@@ -190,7 +203,11 @@ module Avromatic
190
203
 
191
204
  # Add all generated methods to a module so they can be overridden
192
205
  generated_methods_module.send(:define_method, field.name) { _attributes[symbolized_field_name] }
193
- generated_methods_module.send(:define_method, "#{field.name}?") { !!_attributes[symbolized_field_name] } if FieldHelper.boolean?(field)
206
+ if FieldHelper.boolean?(field)
207
+ generated_methods_module.send(:define_method, "#{field.name}?") do
208
+ !!_attributes[symbolized_field_name]
209
+ end
210
+ end
194
211
 
195
212
  generated_methods_module.send(:define_method, "#{field.name}=") do |value|
196
213
  _attributes[symbolized_field_name] = attribute_definition.coerce(value)
@@ -24,7 +24,8 @@ module Avromatic
24
24
  end
25
25
 
26
26
  module ClassMethods
27
- delegate :avro_schema, :value_avro_schema, :key_avro_schema, :mutable?, :immutable?, to: :config
27
+ delegate :avro_schema, :value_avro_schema, :key_avro_schema, :mutable?, :immutable?,
28
+ :avro_schema_subject, :value_avro_schema_subject, :key_avro_schema_subject, to: :config
28
29
 
29
30
  def value_avro_field_names
30
31
  @value_avro_field_names ||= value_avro_schema.fields.map(&:name).map(&:to_sym).freeze
@@ -68,6 +69,7 @@ module Avromatic
68
69
  end
69
70
 
70
71
  delegate :avro_schema, :value_avro_schema, :key_avro_schema,
72
+ :avro_schema_subject, :value_avro_schema_subject, :key_avro_schema_subject,
71
73
  :value_avro_field_names, :key_avro_field_names,
72
74
  :value_avro_field_references, :key_avro_field_references,
73
75
  :mutable?, :immutable?,
@@ -7,7 +7,7 @@ module Avromatic
7
7
  class Configuration
8
8
 
9
9
  attr_reader :avro_schema, :key_avro_schema, :nested_models, :mutable,
10
- :allow_optional_key_fields
10
+ :allow_optional_key_fields, :avro_schema_subject, :key_avro_schema_subject
11
11
  alias_method :mutable?, :mutable
12
12
  delegate :schema_store, to: Avromatic
13
13
 
@@ -17,23 +17,30 @@ module Avromatic
17
17
  # @param options [Hash]
18
18
  # @option options [Avro::Schema] :schema
19
19
  # @option options [String, Symbol] :schema_name
20
+ # @option options [String, Symbol] :schema_subject
20
21
  # @option options [Avro::Schema] :value_schema
21
22
  # @option options [String, Symbol] :value_schema_name
23
+ # @option options [String, Symbol] :value_schema_subject
22
24
  # @option options [Avro::Schema] :key_schema
23
25
  # @option options [String, Symbol] :key_schema_name
26
+ # @option options [String, Symbol] :key_schema_subject
24
27
  # @option options [Avromatic::ModelRegistry] :nested_models
25
28
  # @option options [Boolean] :mutable, default false
26
29
  # @option options [Boolean] :allow_optional_key_fields, default false
27
30
  def initialize(**options)
28
31
  @avro_schema = find_avro_schema(**options)
32
+ @avro_schema_subject = options[:schema_subject] || options[:value_schema_subject]
29
33
  raise ArgumentError.new('value_schema(_name) or schema(_name) must be specified') unless avro_schema
34
+
30
35
  @key_avro_schema = find_schema_by_option(:key_schema, **options)
36
+ @key_avro_schema_subject = options[:key_schema_subject]
31
37
  @nested_models = options[:nested_models]
32
38
  @mutable = options.fetch(:mutable, false)
33
39
  @allow_optional_key_fields = options.fetch(:allow_optional_key_fields, false)
34
40
  end
35
41
 
36
42
  alias_method :value_avro_schema, :avro_schema
43
+ alias_method :value_avro_schema_subject, :avro_schema_subject
37
44
 
38
45
  def immutable?
39
46
  !mutable?
@@ -46,6 +53,7 @@ module Avromatic
46
53
  (options[:schema] || options[:schema_name])
47
54
  raise ArgumentError.new('Only one of value_schema(_name) and schema(_name) can be specified')
48
55
  end
56
+
49
57
  find_schema_by_option(:value_schema, **options) || find_schema_by_option(:schema, **options)
50
58
  end
51
59
 
@@ -89,6 +89,7 @@ module Avromatic
89
89
  message_key, message_value = extract_key_and_value(*args)
90
90
  model_key = model_key_for_message(message_key, message_value)
91
91
  raise UnexpectedKeyError.new(*model_key) unless model_map.key?(model_key)
92
+
92
93
  [model_map[model_key], message_key, message_value]
93
94
  end
94
95
 
@@ -106,7 +107,7 @@ module Avromatic
106
107
  end
107
108
 
108
109
  def extract_schema_id(data)
109
- data[1..4].unpack('N').first
110
+ data[1..4].unpack1('N')
110
111
  end
111
112
 
112
113
  def validate_magic_byte!(data)
@@ -118,6 +119,7 @@ module Avromatic
118
119
  models.each_with_object(Hash.new) do |model, map|
119
120
  key = model_key(model)
120
121
  raise DuplicateKeyError.new(map[key], model) if map.key?(key) && !model.equal?(map[key])
122
+
121
123
  map[key] = model
122
124
  end
123
125
  end
@@ -16,15 +16,18 @@ module Avromatic
16
16
  def avro_message_value
17
17
  avro_messaging.encode(
18
18
  value_attributes_for_avro,
19
- schema_name: value_avro_schema.fullname
19
+ schema_name: value_avro_schema.fullname,
20
+ subject: value_avro_schema_subject
20
21
  )
21
22
  end
22
23
 
23
24
  def avro_message_key
24
25
  raise 'Model has no key schema' unless key_avro_schema
26
+
25
27
  avro_messaging.encode(
26
28
  key_attributes_for_avro,
27
- schema_name: key_avro_schema.fullname
29
+ schema_name: key_avro_schema.fullname,
30
+ subject: key_avro_schema_subject
28
31
  )
29
32
  end
30
33
  end
@@ -55,15 +58,15 @@ module Avromatic
55
58
 
56
59
  module Registration
57
60
  def register_schemas!
58
- register_schema(key_avro_schema) if key_avro_schema
59
- register_schema(value_avro_schema)
61
+ register_schema(key_avro_schema, subject: key_avro_schema_subject) if key_avro_schema
62
+ register_schema(value_avro_schema, subject: value_avro_schema_subject)
60
63
  nil
61
64
  end
62
65
 
63
66
  private
64
67
 
65
- def register_schema(schema)
66
- avro_messaging.registry.register(schema.fullname, schema)
68
+ def register_schema(schema, subject: nil)
69
+ avro_messaging.registry.register(subject || schema.fullname, schema)
67
70
  end
68
71
  end
69
72
 
@@ -14,12 +14,14 @@ module Avromatic
14
14
  def register!
15
15
  return unless key_avro_schema.nil? && value_avro_schema.type_sym == :record
16
16
 
17
+ processed = Set.new
17
18
  roots = [self]
18
19
  until roots.empty?
19
20
  model = roots.shift
20
- next if nested_models.registered?(model)
21
+ # Avoid any nested model dependency cycles by ignoring already processed models
22
+ next unless processed.add?(model)
21
23
 
22
- nested_models.register(model)
24
+ nested_models.ensure_registered_model(model)
23
25
  roots.concat(model.referenced_model_classes)
24
26
  end
25
27
  end
@@ -36,6 +36,7 @@ module Avromatic
36
36
  end
37
37
 
38
38
  raise 'Model has no key schema' unless key_avro_schema
39
+
39
40
  avro_raw_encode(key_attributes_for_avro, :key)
40
41
  end
41
42
 
@@ -118,7 +119,7 @@ module Avromatic
118
119
  # If supplied then the key_schema and value_schema are used as the writer's
119
120
  # schema for the corresponding value. The model's schemas are always used
120
121
  # as the reader's schemas.
121
- def avro_raw_decode(key: nil, value:, key_schema: nil, value_schema: nil)
122
+ def avro_raw_decode(value:, key: nil, key_schema: nil, value_schema: nil)
122
123
  key_attributes = key && decode_avro_datum(key, key_schema, :key)
123
124
  value_attributes = decode_avro_datum(value, value_schema, :value)
124
125
  value_attributes.merge!(key_attributes) if key_attributes
@@ -150,10 +151,10 @@ module Avromatic
150
151
 
151
152
  def datum_writer
152
153
  @datum_writer ||= begin
153
- hash = { value: datum_writer_class.new(value_avro_schema) }
154
- hash[:key] = datum_writer_class.new(key_avro_schema) if key_avro_schema
155
- hash
156
- end
154
+ hash = { value: datum_writer_class.new(value_avro_schema) }
155
+ hash[:key] = datum_writer_class.new(key_avro_schema) if key_avro_schema
156
+ hash
157
+ end
157
158
  end
158
159
 
159
160
  def datum_reader
@@ -11,6 +11,7 @@ module Avromatic
11
11
  attr_reader :value_type
12
12
 
13
13
  def initialize(value_type:)
14
+ super()
14
15
  @value_type = value_type
15
16
  end
16
17
 
@@ -11,6 +11,7 @@ module Avromatic
11
11
  attr_reader :custom_type_configuration, :value_classes, :default_type
12
12
 
13
13
  def initialize(custom_type_configuration:, default_type:)
14
+ super()
14
15
  @custom_type_configuration = custom_type_configuration
15
16
  @default_type = default_type
16
17
  @deserializer = custom_type_configuration.deserializer || IDENTITY_PROC
@@ -27,7 +28,11 @@ module Avromatic
27
28
  end
28
29
 
29
30
  def name
30
- custom_type_configuration.value_class ? custom_type_configuration.value_class.name.to_s.freeze : default_type.name
31
+ if custom_type_configuration.value_class
32
+ custom_type_configuration.value_class.name.to_s.freeze
33
+ else
34
+ default_type.name
35
+ end
31
36
  end
32
37
 
33
38
  def coerce(input)
@@ -12,6 +12,7 @@ module Avromatic
12
12
  attr_reader :allowed_values
13
13
 
14
14
  def initialize(allowed_values)
15
+ super()
15
16
  @allowed_values = allowed_values.to_set
16
17
  end
17
18
 
@@ -11,6 +11,7 @@ module Avromatic
11
11
  attr_reader :size
12
12
 
13
13
  def initialize(size)
14
+ super()
14
15
  @size = size
15
16
  end
16
17
 
@@ -11,6 +11,7 @@ module Avromatic
11
11
  attr_reader :value_type, :key_type
12
12
 
13
13
  def initialize(key_type:, value_type:)
14
+ super()
14
15
  @key_type = key_type
15
16
  @value_type = value_type
16
17
  end
@@ -9,6 +9,7 @@ module Avromatic
9
9
  attr_reader :record_class, :value_classes, :input_classes
10
10
 
11
11
  def initialize(record_class:)
12
+ super()
12
13
  @record_class = record_class
13
14
  @value_classes = [record_class].freeze
14
15
  @input_classes = [record_class, Hash].freeze
@@ -59,10 +59,12 @@ module Avromatic
59
59
  when :enum
60
60
  Avromatic::Model::Types::EnumType.new(schema.symbols)
61
61
  when :array
62
- value_type = create(schema: schema.items, nested_models: nested_models, use_custom_types: use_custom_types)
62
+ value_type = create(schema: schema.items, nested_models: nested_models,
63
+ use_custom_types: use_custom_types)
63
64
  Avromatic::Model::Types::ArrayType.new(value_type: value_type)
64
65
  when :map
65
- value_type = create(schema: schema.values, nested_models: nested_models, use_custom_types: use_custom_types)
66
+ value_type = create(schema: schema.values, nested_models: nested_models,
67
+ use_custom_types: use_custom_types)
66
68
  Avromatic::Model::Types::MapType.new(
67
69
  key_type: Avromatic::Model::Types::StringType.new,
68
70
  value_type: value_type
@@ -97,8 +99,10 @@ module Avromatic
97
99
  if nested_models.registered?(fullname)
98
100
  nested_model = nested_models[fullname]
99
101
  unless schema_fingerprint(schema) == schema_fingerprint(nested_model.avro_schema)
100
- raise "The #{nested_model.name} model is already registered with an incompatible version of the #{schema.fullname} schema"
102
+ raise "The #{nested_model.name} model is already registered with an incompatible version of the " \
103
+ "#{schema.fullname} schema"
101
104
  end
105
+
102
106
  nested_model
103
107
  else
104
108
  Avromatic::Model.model(schema: schema, nested_models: nested_models)
@@ -10,6 +10,7 @@ module Avromatic
10
10
  attr_reader :member_types, :value_classes, :input_classes
11
11
 
12
12
  def initialize(member_types:)
13
+ super()
13
14
  @member_types = member_types
14
15
  @value_classes = member_types.flat_map(&:value_classes)
15
16
  @input_classes = member_types.flat_map(&:input_classes).uniq
@@ -31,9 +32,7 @@ module Avromatic
31
32
  end
32
33
  end
33
34
 
34
- if result.nil?
35
- raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}")
36
- end
35
+ raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}") if result.nil?
37
36
 
38
37
  result
39
38
  end
@@ -64,9 +64,7 @@ module Avromatic
64
64
  end
65
65
  end
66
66
 
67
- if recursively_immutable?
68
- @missing_attributes = missing_attributes.freeze
69
- end
67
+ @missing_attributes = missing_attributes.freeze if recursively_immutable?
70
68
 
71
69
  missing_attributes
72
70
  end
@@ -24,8 +24,10 @@ module Avromatic
24
24
 
25
25
  def register(model)
26
26
  raise 'models with a key schema are not supported' if model.key_avro_schema
27
+
27
28
  name = model_fullname(model)
28
29
  raise "'#{name}' has already been registered" if registered?(name)
30
+
29
31
  @hash[name] = model
30
32
  end
31
33
 
@@ -42,8 +44,11 @@ module Avromatic
42
44
  def ensure_registered_model(model)
43
45
  name = model_fullname(model)
44
46
  if registered?(name)
45
- unless fetch(name).equal?(model)
46
- raise "attempted to replace existing model #{fetch(name)} with new model #{model} as '#{name}'"
47
+ existing_model = fetch(name)
48
+ unless existing_model.equal?(model)
49
+ raise "Attempted to replace existing Avromatic model #{model_debug_name(existing_model)} with new model " \
50
+ "#{model_debug_name(model)} as '#{name}'. Perhaps '#{model_debug_name(model)}' needs to be eager loaded " \
51
+ 'via the Avromatic eager_load_models setting?'
47
52
  end
48
53
  else
49
54
  register(model)
@@ -65,5 +70,11 @@ module Avromatic
65
70
 
66
71
  value.start_with?('.') ? value.from(1) : value
67
72
  end
73
+
74
+ private
75
+
76
+ def model_debug_name(model)
77
+ model.name || model.to_s
78
+ end
68
79
  end
69
80
  end
@@ -12,6 +12,7 @@ RSpec.configure do |config|
12
12
 
13
13
  WebMock.stub_request(:any, /^#{registry_uri}/).to_rack(AvroSchemaRegistry::FakeServer)
14
14
  AvroSchemaRegistry::FakeServer.clear
15
+ Avromatic.build_schema_registry!
15
16
  Avromatic.build_messaging!
16
17
  end
17
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Avromatic
4
- VERSION = '3.0.0'
4
+ VERSION = '4.1.0'
5
5
  end
data/lib/avromatic.rb CHANGED
@@ -34,6 +34,7 @@ module Avromatic
34
34
 
35
35
  def self.build_schema_registry
36
36
  raise 'Avromatic must be configured with a registry_url' unless registry_url
37
+
37
38
  if use_schema_fingerprint_lookup
38
39
  AvroSchemaRegistry::CachedClient.new(
39
40
  AvroSchemaRegistry::Client.new(registry_url, logger: logger)
@@ -45,8 +46,13 @@ module Avromatic
45
46
  end
46
47
  end
47
48
 
49
+ def self.build_schema_registry!
50
+ self.schema_registry = build_schema_registry
51
+ end
52
+
48
53
  def self.build_messaging
49
54
  raise 'Avromatic must be configured with a schema_store' unless schema_store
55
+
50
56
  Avromatic::Messaging.new(
51
57
  registry: schema_registry || build_schema_registry,
52
58
  schema_store: schema_store,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avromatic
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salsify Engineering
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-04-22 00:00:00.000000000 Z
11
+ date: 2022-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '5.2'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.2'
22
+ version: '7.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '5.2'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.2'
32
+ version: '7.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activesupport
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,7 @@ dependencies:
39
39
  version: '5.2'
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: '6.2'
42
+ version: '7.1'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,27 +49,27 @@ dependencies:
49
49
  version: '5.2'
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: '6.2'
52
+ version: '7.1'
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: avro
55
55
  requirement: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: 1.9.0
59
+ version: 1.10.0
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
- version: '1.11'
62
+ version: '1.12'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 1.9.0
69
+ version: 1.10.0
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '1.11'
72
+ version: '1.12'
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: avro_schema_registry-client
75
75
  requirement: !ruby/object:Gem::Requirement
@@ -216,14 +216,14 @@ dependencies:
216
216
  requirements:
217
217
  - - "~>"
218
218
  - !ruby/object:Gem::Version
219
- version: 0.52.1.1
219
+ version: 1.1.0
220
220
  type: :development
221
221
  prerelease: false
222
222
  version_requirements: !ruby/object:Gem::Requirement
223
223
  requirements:
224
224
  - - "~>"
225
225
  - !ruby/object:Gem::Version
226
- version: 0.52.1.1
226
+ version: 1.1.0
227
227
  - !ruby/object:Gem::Dependency
228
228
  name: simplecov
229
229
  requirement: !ruby/object:Gem::Requirement
@@ -274,6 +274,7 @@ extensions: []
274
274
  extra_rdoc_files: []
275
275
  files:
276
276
  - ".circleci/config.yml"
277
+ - ".github/CODEOWNERS"
277
278
  - ".gitignore"
278
279
  - ".overcommit.yml"
279
280
  - ".rspec"
@@ -293,9 +294,9 @@ files:
293
294
  - gemfiles/avro1_10_rails5_2.gemfile
294
295
  - gemfiles/avro1_10_rails6_0.gemfile
295
296
  - gemfiles/avro1_10_rails6_1.gemfile
296
- - gemfiles/avro1_9_rails5_2.gemfile
297
- - gemfiles/avro1_9_rails6_0.gemfile
298
- - gemfiles/avro1_9_rails6_1.gemfile
297
+ - gemfiles/avro1_10_rails7_0.gemfile
298
+ - gemfiles/avro1_11_rails6_1.gemfile
299
+ - gemfiles/avro1_11_rails7_0.gemfile
299
300
  - lib/avromatic.rb
300
301
  - lib/avromatic/io.rb
301
302
  - lib/avromatic/io/datum_reader.rb
@@ -345,7 +346,9 @@ files:
345
346
  homepage: https://github.com/salsify/avromatic.git
346
347
  licenses:
347
348
  - MIT
348
- metadata: {}
349
+ metadata:
350
+ allowed_push_host: https://rubygems.org
351
+ rubygems_mfa_required: 'true'
349
352
  post_install_message:
350
353
  rdoc_options: []
351
354
  require_paths:
@@ -354,14 +357,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
354
357
  requirements:
355
358
  - - ">="
356
359
  - !ruby/object:Gem::Version
357
- version: '2.5'
360
+ version: '2.7'
358
361
  required_rubygems_version: !ruby/object:Gem::Requirement
359
362
  requirements:
360
363
  - - ">="
361
364
  - !ruby/object:Gem::Version
362
365
  version: '0'
363
366
  requirements: []
364
- rubygems_version: 3.0.3.1
367
+ rubygems_version: 3.2.22
365
368
  signing_key:
366
369
  specification_version: 4
367
370
  summary: Generate Ruby models from Avro schemas