avromatic 2.4.0 → 4.0.0

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +17 -14
  3. data/.github/CODEOWNERS +1 -0
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +2 -2
  6. data/.ruby-version +1 -1
  7. data/Appraisals +13 -37
  8. data/CHANGELOG.md +22 -0
  9. data/README.md +4 -18
  10. data/avromatic.gemspec +19 -10
  11. data/gemfiles/{avro1_8_rails5_2.gemfile → avro1_10_rails7_0.gemfile} +3 -3
  12. data/gemfiles/{avro1_9_rails6_1.gemfile → avro1_11_rails6_1.gemfile} +1 -1
  13. data/gemfiles/{avro1_9_rails5_2.gemfile → avro1_11_rails7_0.gemfile} +3 -3
  14. data/lib/avromatic/io/datum_writer.rb +1 -3
  15. data/lib/avromatic/messaging.rb +2 -4
  16. data/lib/avromatic/model/attributes.rb +30 -13
  17. data/lib/avromatic/model/configuration.rb +2 -0
  18. data/lib/avromatic/model/message_decoder.rb +3 -1
  19. data/lib/avromatic/model/messaging_serialization.rb +1 -0
  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 +38 -39
  36. data/gemfiles/avro1_9_rails6_0.gemfile +0 -9
  37. data/gemfiles/avro_patches_rails5_2.gemfile +0 -9
  38. data/gemfiles/avro_patches_rails6_0.gemfile +0 -9
  39. data/gemfiles/avro_patches_rails6_1.gemfile +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7401c0e72992925f02234f5d3d2ab8e762a36bc62aa25c631569d6f4d9918d2
4
- data.tar.gz: f2121b2a62bf60893ea0eb143ed83a142ea069d07839fb8121d0618483b469d5
3
+ metadata.gz: 6b0efb11db848986a5f33f4d499ac919bb1e217b8e1d0b06311f485a26f0c94a
4
+ data.tar.gz: 2aee989c64a77e875894146a98ebdcd8a15409ba556b86a1d4bce9c18ee977fa
5
5
  SHA512:
6
- metadata.gz: 42be85747686fca4ec66d6e178f7e5aab6d9e9ab88cd4d107ed1c5de8a83017f083a510889ced33a2eb284f45865b3c32b3a9edad200011aed5b17c284f3a593
7
- data.tar.gz: be13122bca8693d33e20d1317ae5cd88d8c1c975c08d94ed522d0330d70f202bc43d79d7f2941032bc7540ad83b2d647d3c7d29bb5b72c03a560dfedb85e62f7
6
+ metadata.gz: a84b530d3d65135ff5520e412cb8517062dfa964bc3cff4511f81d310429d6b804f93a392c5a6add8c9f7c278207ed38e19a0117b3e17b2dfec948ffb2a10748
7
+ data.tar.gz: 7a1e707977d52b68b2bcd78de6bf8db5db097c7afc32493db2d242c1733aa1b3610d53fd640e68db0d23f9192f9e6e7910793512a6001b36976e700d66ae36ee
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,17 +69,20 @@ workflows:
69
69
  matrix:
70
70
  parameters:
71
71
  gemfile:
72
- - "gemfiles/avro1_8_rails5_2.gemfile"
73
- - "gemfiles/avro1_9_rails5_2.gemfile"
74
72
  - "gemfiles/avro1_10_rails5_2.gemfile"
75
- - "gemfiles/avro1_9_rails6_0.gemfile"
76
73
  - "gemfiles/avro1_10_rails6_0.gemfile"
77
- - "gemfiles/avro_patches_rails5_2.gemfile"
78
- - "gemfiles/avro_patches_rails6_0.gemfile"
79
74
  - "gemfiles/avro1_10_rails6_1.gemfile"
80
- - "gemfiles/avro1_9_rails6_1.gemfile"
81
- - "gemfiles/avro_patches_rails6_1.gemfile"
75
+ - "gemfiles/avro1_10_rails7_0.gemfile"
76
+ - "gemfiles/avro1_11_rails7_0.gemfile"
82
77
  ruby-version:
83
- - "2.5.8"
84
- - "2.6.6"
85
- - "2.7.1"
78
+ - "2.7.4"
79
+ - test:
80
+ matrix:
81
+ parameters:
82
+ gemfile:
83
+ - "gemfiles/avro1_10_rails6_1.gemfile"
84
+ - "gemfiles/avro1_10_rails7_0.gemfile"
85
+ - "gemfiles/avro1_11_rails7_0.gemfile"
86
+ ruby-version:
87
+ - "3.0.3"
88
+ - "3.1.0"
@@ -0,0 +1 @@
1
+ * @jturkel @kphelps @tjwp
data/.gitignore CHANGED
@@ -9,3 +9,6 @@
9
9
  /tmp/
10
10
  /gemfiles/*.lock
11
11
  /log/*
12
+
13
+ *.iml
14
+ .idea
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.4
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,61 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise 'avro1_8-rails5_2' do
4
- gem 'avro', '1.8.2'
5
- gem 'activesupport', '~> 5.2.0'
6
- gem 'activemodel', '~> 5.2.0'
7
- end
8
-
9
- appraise 'avro1_9-rails5_2' do
10
- gem 'avro', '1.9.2'
11
- gem 'activesupport', '~> 5.2.0'
12
- gem 'activemodel', '~> 5.2.0'
13
- end
14
-
15
3
  appraise 'avro1_10-rails5_2' do
16
4
  gem 'avro', '~> 1.10.0'
17
5
  gem 'activesupport', '~> 5.2.0'
18
6
  gem 'activemodel', '~> 5.2.0'
19
7
  end
20
8
 
21
- appraise 'avro1_9-rails6_0' do
22
- gem 'avro', '1.9.2'
23
- gem 'activesupport', '~> 6.0.0'
24
- gem 'activemodel', '~> 6.0.0'
25
- end
26
-
27
9
  appraise 'avro1_10-rails6_0' do
28
10
  gem 'avro', '~> 1.10.0'
29
11
  gem 'activesupport', '~> 6.0.0'
30
12
  gem 'activemodel', '~> 6.0.0'
31
13
  end
32
14
 
33
- appraise 'avro-patches-rails5_2' do
34
- gem 'avro-patches', '>= 0.4.1', '< 1.0'
35
- gem 'activesupport', '~> 5.2.0'
36
- gem 'activemodel', '~> 5.2.0'
37
- end
38
-
39
- appraise 'avro-patches-rails6_0' do
40
- gem 'avro-patches', '>= 1.0.0'
41
- gem 'activesupport', '~> 6.0.0'
42
- gem 'activemodel', '~> 6.0.0'
43
- end
44
-
45
- appraise 'avro1_9-rails6_1' do
46
- gem 'avro', '1.9.2'
15
+ appraise 'avro1_10-rails6_1' do
16
+ gem 'avro', '~> 1.10.0'
47
17
  gem 'activesupport', '~> 6.1.0'
48
18
  gem 'activemodel', '~> 6.1.0'
49
19
  end
50
20
 
51
- appraise 'avro1_10-rails6_1' do
21
+ appraise 'avro1_10-rails7_0' do
52
22
  gem 'avro', '~> 1.10.0'
53
- gem 'activesupport', '~> 6.1.0'
54
- gem 'activemodel', '~> 6.1.0'
23
+ gem 'activesupport', '~> 7.0.0'
24
+ gem 'activemodel', '~> 7.0.0'
55
25
  end
56
26
 
57
- appraise 'avro-patches-rails6_1' do
58
- gem 'avro-patches', '>= 1.0.0'
27
+ appraise 'avro1_11-rails6_1' do
28
+ gem 'avro', '~> 1.11.0'
59
29
  gem 'activesupport', '~> 6.1.0'
60
30
  gem 'activemodel', '~> 6.1.0'
61
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,27 @@
1
1
  # avromatic changelog
2
2
 
3
+ ## 4.0.0
4
+ - Drop support for Ruby 2.6.
5
+ - Drop support for Avro 1.9.
6
+ - Add support for Avro 1.11.
7
+ - Add support for Rails 7.0.
8
+
9
+ ## 3.0.2
10
+ - Reset the schema registry client between RSpec tests to ensure any cached values are
11
+ consistent with the fake schema registry.
12
+
13
+ ## 3.0.1
14
+ - Raise an error when registering a nested model that has already been auto-generated.
15
+ This avoids hard to troubleshoot coercion errors when instantiating models and fixes
16
+ a regression introduced in Avromatic 2.2.2.
17
+
18
+ ## 3.0.0
19
+ - Drop support for Ruby 2.4.
20
+ - Add support for Ruby 3.0.
21
+ - Drop support for Avro < 1.9.
22
+ - Drop support for Rails < 5.2.
23
+ - Fix decoding of unions containing false boolean values.
24
+
3
25
  ## v2.4.0
4
26
  - Ignore the `validate` argument and always validate during serialization. This
5
27
  argument will be removed in Avromatic 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
  ```
@@ -430,23 +433,6 @@ Validation of required fields is done automatically when serializing a model to
430
433
  explicitly by calling the `valid?` or `invalid?` methods from the
431
434
  [ActiveModel::Validations](https://edgeapi.rubyonrails.org/classes/ActiveModel/Validations.html) interface.
432
435
 
433
- ### Logical Types
434
-
435
- Currently the official Apache Avro Ruby library does not support logical types ([AVRO-1695](https://issues.apache.org/jira/browse/AVRO-1695)).
436
- That feature is in progress and will hopefully be merged soon.
437
-
438
- Avromatic supports logical types as implemented in the [pull request](https://github.com/apache/avro/pull/116) referenced in AVRO-1695.
439
-
440
- Until that change is included in the official library, you can
441
- use the [avro-patches gem](https://github.com/salsify/avro-patches) which includes
442
- the changes from the above pull request.
443
-
444
- To use this gem, reference it in your Gemfile instead of `avro`:
445
-
446
- ```ruby
447
- gem 'avro-patches'
448
- ````
449
-
450
436
  ### RSpec Support
451
437
 
452
438
  This gem also includes an `"avromatic/rspec"` file that can be required to support
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,28 +15,37 @@ 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.4'
30
+ spec.metadata['rubygems_mfa_required'] = 'true'
31
+
32
+ spec.required_ruby_version = '>= 2.7'
24
33
 
25
- spec.add_runtime_dependency 'activemodel', '>= 5.0', '< 6.2'
26
- spec.add_runtime_dependency 'activesupport', '>= 5.0', '< 6.2'
27
- spec.add_runtime_dependency 'avro', '>= 1.7.7', '< 1.11'
28
- spec.add_runtime_dependency 'avro_schema_registry-client', '>= 0.3.0'
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'
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'
31
40
 
32
41
  spec.add_development_dependency 'appraisal'
33
42
  spec.add_development_dependency 'avro-builder', '>= 0.12.0'
34
- spec.add_development_dependency 'bundler', '>= 1.11'
43
+ spec.add_development_dependency 'bundler', '~> 2.0'
35
44
  spec.add_development_dependency 'overcommit', '0.35.0'
36
- spec.add_development_dependency 'rake', '~> 10.0'
37
- spec.add_development_dependency 'rspec', '~> 3.0'
45
+ spec.add_development_dependency 'rake', '~> 13.0'
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.8.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", "~> 5.2.0"
7
- gem "activemodel", "~> 5.2.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)
@@ -27,6 +27,7 @@ module Avromatic
27
27
  def initialize(**options)
28
28
  @avro_schema = find_avro_schema(**options)
29
29
  raise ArgumentError.new('value_schema(_name) or schema(_name) must be specified') unless avro_schema
30
+
30
31
  @key_avro_schema = find_schema_by_option(:key_schema, **options)
31
32
  @nested_models = options[:nested_models]
32
33
  @mutable = options.fetch(:mutable, false)
@@ -46,6 +47,7 @@ module Avromatic
46
47
  (options[:schema] || options[:schema_name])
47
48
  raise ArgumentError.new('Only one of value_schema(_name) and schema(_name) can be specified')
48
49
  end
50
+
49
51
  find_schema_by_option(:value_schema, **options) || find_schema_by_option(:schema, **options)
50
52
  end
51
53
 
@@ -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
@@ -22,6 +22,7 @@ module Avromatic
22
22
 
23
23
  def avro_message_key
24
24
  raise 'Model has no key schema' unless key_avro_schema
25
+
25
26
  avro_messaging.encode(
26
27
  key_attributes_for_avro,
27
28
  schema_name: key_avro_schema.fullname
@@ -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
- unless result
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 = '2.4.0'
4
+ VERSION = '4.0.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: 2.4.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Salsify Engineering
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-05 00:00:00.000000000 Z
11
+ date: 2022-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -16,74 +16,74 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '5.0'
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
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '5.0'
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
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '5.0'
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
46
46
  requirements:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: '5.0'
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.7.7
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.7.7
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
76
76
  requirements:
77
77
  - - ">="
78
78
  - !ruby/object:Gem::Version
79
- version: 0.3.0
79
+ version: 0.4.0
80
80
  type: :runtime
81
81
  prerelease: false
82
82
  version_requirements: !ruby/object:Gem::Requirement
83
83
  requirements:
84
84
  - - ">="
85
85
  - !ruby/object:Gem::Version
86
- version: 0.3.0
86
+ version: 0.4.0
87
87
  - !ruby/object:Gem::Dependency
88
88
  name: avro_turf
89
89
  requirement: !ruby/object:Gem::Requirement
@@ -144,16 +144,16 @@ dependencies:
144
144
  name: bundler
145
145
  requirement: !ruby/object:Gem::Requirement
146
146
  requirements:
147
- - - ">="
147
+ - - "~>"
148
148
  - !ruby/object:Gem::Version
149
- version: '1.11'
149
+ version: '2.0'
150
150
  type: :development
151
151
  prerelease: false
152
152
  version_requirements: !ruby/object:Gem::Requirement
153
153
  requirements:
154
- - - ">="
154
+ - - "~>"
155
155
  - !ruby/object:Gem::Version
156
- version: '1.11'
156
+ version: '2.0'
157
157
  - !ruby/object:Gem::Dependency
158
158
  name: overcommit
159
159
  requirement: !ruby/object:Gem::Requirement
@@ -174,28 +174,28 @@ dependencies:
174
174
  requirements:
175
175
  - - "~>"
176
176
  - !ruby/object:Gem::Version
177
- version: '10.0'
177
+ version: '13.0'
178
178
  type: :development
179
179
  prerelease: false
180
180
  version_requirements: !ruby/object:Gem::Requirement
181
181
  requirements:
182
182
  - - "~>"
183
183
  - !ruby/object:Gem::Version
184
- version: '10.0'
184
+ version: '13.0'
185
185
  - !ruby/object:Gem::Dependency
186
186
  name: rspec
187
187
  requirement: !ruby/object:Gem::Requirement
188
188
  requirements:
189
189
  - - "~>"
190
190
  - !ruby/object:Gem::Version
191
- version: '3.0'
191
+ version: '3.8'
192
192
  type: :development
193
193
  prerelease: false
194
194
  version_requirements: !ruby/object:Gem::Requirement
195
195
  requirements:
196
196
  - - "~>"
197
197
  - !ruby/object:Gem::Version
198
- version: '3.0'
198
+ version: '3.8'
199
199
  - !ruby/object:Gem::Dependency
200
200
  name: rspec_junit_formatter
201
201
  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,13 +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_8_rails5_2.gemfile
297
- - gemfiles/avro1_9_rails5_2.gemfile
298
- - gemfiles/avro1_9_rails6_0.gemfile
299
- - gemfiles/avro1_9_rails6_1.gemfile
300
- - gemfiles/avro_patches_rails5_2.gemfile
301
- - gemfiles/avro_patches_rails6_0.gemfile
302
- - gemfiles/avro_patches_rails6_1.gemfile
297
+ - gemfiles/avro1_10_rails7_0.gemfile
298
+ - gemfiles/avro1_11_rails6_1.gemfile
299
+ - gemfiles/avro1_11_rails7_0.gemfile
303
300
  - lib/avromatic.rb
304
301
  - lib/avromatic/io.rb
305
302
  - lib/avromatic/io/datum_reader.rb
@@ -349,8 +346,10 @@ files:
349
346
  homepage: https://github.com/salsify/avromatic.git
350
347
  licenses:
351
348
  - MIT
352
- metadata: {}
353
- post_install_message:
349
+ metadata:
350
+ allowed_push_host: https://rubygems.org
351
+ rubygems_mfa_required: 'true'
352
+ post_install_message:
354
353
  rdoc_options: []
355
354
  require_paths:
356
355
  - lib
@@ -358,15 +357,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
358
357
  requirements:
359
358
  - - ">="
360
359
  - !ruby/object:Gem::Version
361
- version: '2.4'
360
+ version: '2.7'
362
361
  required_rubygems_version: !ruby/object:Gem::Requirement
363
362
  requirements:
364
363
  - - ">="
365
364
  - !ruby/object:Gem::Version
366
365
  version: '0'
367
366
  requirements: []
368
- rubygems_version: 3.0.8
369
- signing_key:
367
+ rubygems_version: 3.2.22
368
+ signing_key:
370
369
  specification_version: 4
371
370
  summary: Generate Ruby models from Avro schemas
372
371
  test_files: []
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "avro", "1.9.2"
6
- gem "activesupport", "~> 6.0.0"
7
- gem "activemodel", "~> 6.0.0"
8
-
9
- gemspec path: "../"
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "avro-patches", ">= 0.4.1", "< 1.0"
6
- gem "activesupport", "~> 5.2.0"
7
- gem "activemodel", "~> 5.2.0"
8
-
9
- gemspec path: "../"
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "avro-patches", ">= 1.0.0"
6
- gem "activesupport", "~> 6.0.0"
7
- gem "activemodel", "~> 6.0.0"
8
-
9
- gemspec path: "../"
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "avro-patches", ">= 1.0.0"
6
- gem "activesupport", "~> 6.1.0"
7
- gem "activemodel", "~> 6.1.0"
8
-
9
- gemspec path: "../"