Skip to content

Commit

Permalink
Support Avro 1.11 and Rails 7.0, drop Ruby 2.6 and Avro 1.9 support (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tjwp authored Jan 12, 2022
1 parent 20a495e commit 459e64a
Show file tree
Hide file tree
Showing 37 changed files with 163 additions and 108 deletions.
23 changes: 11 additions & 12 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ version: 2.1
jobs:
lint:
docker:
- image: salsify/ruby_ci:2.5.8
- image: salsify/ruby_ci:2.7.4
working_directory: ~/avromatic
steps:
- checkout
- restore_cache:
keys:
- v2-gems-ruby-2.5.8-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
- v2-gems-ruby-2.5.8-
- v2-gems-ruby-2.7.4-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
- v2-gems-ruby-2.7.4-
- run:
name: Install Gems
command: |
Expand All @@ -18,7 +18,7 @@ jobs:
bundle clean
fi
- save_cache:
key: v2-gems-ruby-2.5.8-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
key: v2-gems-ruby-2.7.4-{{ checksum "avromatic.gemspec" }}-{{ checksum "Gemfile" }}
paths:
- "vendor/bundle"
- "gemfiles/vendor/bundle"
Expand Down Expand Up @@ -69,21 +69,20 @@ workflows:
matrix:
parameters:
gemfile:
- "gemfiles/avro1_9_rails5_2.gemfile"
- "gemfiles/avro1_10_rails5_2.gemfile"
- "gemfiles/avro1_9_rails6_0.gemfile"
- "gemfiles/avro1_10_rails6_0.gemfile"
- "gemfiles/avro1_10_rails6_1.gemfile"
- "gemfiles/avro1_9_rails6_1.gemfile"
- "gemfiles/avro1_10_rails7_0.gemfile"
- "gemfiles/avro1_11_rails7_0.gemfile"
ruby-version:
- "2.5.8"
- "2.6.6"
- "2.7.2"
- "2.7.4"
- test:
matrix:
parameters:
gemfile:
- "gemfiles/avro1_10_rails6_1.gemfile"
- "gemfiles/avro1_9_rails6_1.gemfile"
- "gemfiles/avro1_10_rails7_0.gemfile"
- "gemfiles/avro1_11_rails7_0.gemfile"
ruby-version:
- "3.0.0"
- "3.0.3"
- "3.1.0"
4 changes: 2 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ inherit_gem:
salsify_rubocop: conf/rubocop.yml

AllCops:
TargetRubyVersion: 2.5
TargetRubyVersion: 2.7
Exclude:
- 'vendor/**/*'
- 'gemfiles/vendor/**/*'
- 'gemfiles/**/*'

Style/MultilineBlockChain:
Enabled: false
Expand Down
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.5.8
2.7.4
30 changes: 15 additions & 15 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
# frozen_string_literal: true

appraise 'avro1_9-rails5_2' do
gem 'avro', '1.9.2'
gem 'activesupport', '~> 5.2.0'
gem 'activemodel', '~> 5.2.0'
end

appraise 'avro1_10-rails5_2' do
gem 'avro', '~> 1.10.0'
gem 'activesupport', '~> 5.2.0'
gem 'activemodel', '~> 5.2.0'
end

appraise 'avro1_9-rails6_0' do
gem 'avro', '1.9.2'
gem 'activesupport', '~> 6.0.0'
gem 'activemodel', '~> 6.0.0'
end

appraise 'avro1_10-rails6_0' do
gem 'avro', '~> 1.10.0'
gem 'activesupport', '~> 6.0.0'
gem 'activemodel', '~> 6.0.0'
end

appraise 'avro1_9-rails6_1' do
gem 'avro', '1.9.2'
appraise 'avro1_10-rails6_1' do
gem 'avro', '~> 1.10.0'
gem 'activesupport', '~> 6.1.0'
gem 'activemodel', '~> 6.1.0'
end

appraise 'avro1_10-rails6_1' do
appraise 'avro1_10-rails7_0' do
gem 'avro', '~> 1.10.0'
gem 'activesupport', '~> 7.0.0'
gem 'activemodel', '~> 7.0.0'
end

appraise 'avro1_11-rails6_1' do
gem 'avro', '~> 1.11.0'
gem 'activesupport', '~> 6.1.0'
gem 'activemodel', '~> 6.1.0'
end

appraise 'avro1_11-rails7_0' do
gem 'avro', '~> 1.11.0'
gem 'activesupport', '~> 7.0.0'
gem 'activemodel', '~> 7.0.0'
end
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# avromatic changelog

## 4.0.0
- Drop support for Ruby 2.6.
- Drop support for Avro 1.9.
- Add support for Avro 1.11.
- Add support for Rails 7.0.

## 3.0.2
- Reset the schema registry client between RSpec tests to ensure any cached values are
consistent with the fake schema registry.
Expand Down
14 changes: 8 additions & 6 deletions avromatic.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

lib = File.expand_path('../lib', __FILE__)
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'avromatic/version'

Expand All @@ -27,11 +27,13 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']

spec.required_ruby_version = '>= 2.5'
spec.metadata['rubygems_mfa_required'] = 'true'

spec.add_runtime_dependency 'activemodel', '>= 5.2', '< 6.2'
spec.add_runtime_dependency 'activesupport', '>= 5.2', '< 6.2'
spec.add_runtime_dependency 'avro', '>= 1.9.0', '< 1.11'
spec.required_ruby_version = '>= 2.7'

spec.add_runtime_dependency 'activemodel', '>= 5.2', '< 7.1'
spec.add_runtime_dependency 'activesupport', '>= 5.2', '< 7.1'
spec.add_runtime_dependency 'avro', '>= 1.10.0', '< 1.12'
spec.add_runtime_dependency 'avro_schema_registry-client', '>= 0.4.0'
spec.add_runtime_dependency 'avro_turf'
spec.add_runtime_dependency 'ice_nine'
Expand All @@ -43,7 +45,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rspec', '~> 3.8'
spec.add_development_dependency 'rspec_junit_formatter'
spec.add_development_dependency 'salsify_rubocop', '~> 0.52.1.1'
spec.add_development_dependency 'salsify_rubocop', '~> 1.1.0'
spec.add_development_dependency 'simplecov'
spec.add_development_dependency 'webmock'
# For AvroSchemaRegistry::FakeServer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

source "https://rubygems.org"

gem "avro", "1.9.2"
gem "activesupport", "~> 5.2.0"
gem "activemodel", "~> 5.2.0"
gem "avro", "~> 1.10.0"
gem "activesupport", "~> 7.0.0"
gem "activemodel", "~> 7.0.0"

gemspec path: "../"
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

source "https://rubygems.org"

gem "avro", "1.9.2"
gem "avro", "~> 1.11.0"
gem "activesupport", "~> 6.1.0"
gem "activemodel", "~> 6.1.0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

source "https://rubygems.org"

gem "avro", "1.9.2"
gem "activesupport", "~> 6.0.0"
gem "activemodel", "~> 6.0.0"
gem "avro", "~> 1.11.0"
gem "activesupport", "~> 7.0.0"
gem "activemodel", "~> 7.0.0"

gemspec path: "../"
2 changes: 2 additions & 0 deletions lib/avromatic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def self.configure

def self.build_schema_registry
raise 'Avromatic must be configured with a registry_url' unless registry_url

if use_schema_fingerprint_lookup
AvroSchemaRegistry::CachedClient.new(
AvroSchemaRegistry::Client.new(registry_url, logger: logger)
Expand All @@ -51,6 +52,7 @@ def self.build_schema_registry!

def self.build_messaging
raise 'Avromatic must be configured with a schema_store' unless schema_store

Avromatic::Messaging.new(
registry: schema_registry || build_schema_registry,
schema_store: schema_store,
Expand Down
4 changes: 1 addition & 3 deletions lib/avromatic/io/datum_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ def write_union(writers_schema, datum, encoder)
end
end

unless index_of_schema
raise Avro::IO::AvroTypeError.new(writers_schema, datum)
end
raise Avro::IO::AvroTypeError.new(writers_schema, datum) unless index_of_schema

encoder.write_long(index_of_schema)
write_data(writers_schema.schemas[index_of_schema], datum, encoder)
Expand Down
6 changes: 2 additions & 4 deletions lib/avromatic/messaging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ def decode(data, schema_name: nil, namespace: @namespace)
# The first byte is MAGIC!!!
magic_byte = decoder.read(1)

if magic_byte != MAGIC_BYTE
raise "Expected data to begin with a magic byte, got `#{magic_byte.inspect}`"
end
raise "Expected data to begin with a magic byte, got `#{magic_byte.inspect}`" if magic_byte != MAGIC_BYTE

# The schema id is a 4-byte big-endian integer.
schema_id = decoder.read(4).unpack('N').first
schema_id = decoder.read(4).unpack1('N')

writers_schema = @schemas_by_id.fetch(schema_id) do
schema_json = @registry.fetch(schema_id)
Expand Down
43 changes: 30 additions & 13 deletions lib/avromatic/model/attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def initialize(field)

class AttributeDefinition
attr_reader :name, :name_string, :setter_name, :type, :field, :default, :owner

delegate :serialize, to: :type

def initialize(owner:, field:, type:)
Expand Down Expand Up @@ -58,21 +59,31 @@ def values_immutable?
def coerce(input)
type.coerce(input)
rescue Avromatic::Model::UnknownAttributeError => e
raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
raise Avromatic::Model::CoercionError.new(
"Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
"because the following unexpected attributes were provided: #{e.unknown_attributes.join(', ')}. " \
"Only the following attributes are allowed: #{e.allowed_attributes.join(', ')}. " \
"Provided argument: #{input.inspect}")
"Provided argument: #{input.inspect}"
)
rescue StandardError
if type.input_classes && type.input_classes.none? { |input_class| input.is_a?(input_class) }
raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
"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 ')}. " \
"Provided argument: #{input.inspect}")
raise Avromatic::Model::CoercionError.new(
"Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
"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 '
)}. " \
"Provided argument: #{input.inspect}"
)
elsif input.is_a?(Hash) && type.is_a?(Avromatic::Model::Types::UnionType)
raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
"because no union member type matches the provided attributes: #{input.inspect}")
raise Avromatic::Model::CoercionError.new(
"Value for #{owner.name}##{name} could not be coerced to a #{type.name} " \
"because no union member type matches the provided attributes: #{input.inspect}"
)
else
raise Avromatic::Model::CoercionError.new("Value for #{owner.name}##{name} could not be coerced to a #{type.name}. " \
"Provided argument: #{input.inspect}")
raise Avromatic::Model::CoercionError.new(
"Value for #{owner.name}##{name} could not be coerced to a #{type.name}. " \
"Provided argument: #{input.inspect}"
)
end
end
end
Expand Down Expand Up @@ -106,7 +117,8 @@ def initialize(data = {})
unknown_attributes = (data.keys.map(&:to_s) - _attributes.keys.map(&:to_s)).sort
allowed_attributes = attribute_definitions.keys.map(&:to_s).sort
message = "Unexpected arguments for #{self.class.name}#initialize: #{unknown_attributes.join(', ')}. " \
"Only the following arguments are allowed: #{allowed_attributes.join(', ')}. Provided arguments: #{data.inspect}"
"Only the following arguments are allowed: #{allowed_attributes.join(', ')}. " \
"Provided arguments: #{data.inspect}"
raise Avromatic::Model::UnknownAttributeError.new(message, unknown_attributes: unknown_attributes,
allowed_attributes: allowed_attributes)
end
Expand Down Expand Up @@ -136,8 +148,8 @@ def add_avro_fields(generated_methods_module)
begin
define_avro_attributes(key_avro_schema, generated_methods_module,
allow_optional: config.allow_optional_key_fields)
rescue OptionalFieldError => ex
raise "Optional field '#{ex.field.name}' not allowed in key schema."
rescue OptionalFieldError => e
raise "Optional field '#{e.field.name}' not allowed in key schema."
end
end
define_avro_attributes(avro_schema, generated_methods_module)
Expand All @@ -155,6 +167,7 @@ def check_for_field_conflicts!
conflicts =
(key_avro_field_names & value_avro_field_names).each_with_object([]) do |name, msgs|
next unless schema_fields_differ?(name)

msgs << "Field '#{name}' has a different type in each schema: "\
"value #{value_avro_fields_by_name[name]}, "\
"key #{key_avro_fields_by_name[name]}"
Expand Down Expand Up @@ -190,7 +203,11 @@ def define_avro_attributes(schema, generated_methods_module, allow_optional: tru

# Add all generated methods to a module so they can be overridden
generated_methods_module.send(:define_method, field.name) { _attributes[symbolized_field_name] }
generated_methods_module.send(:define_method, "#{field.name}?") { !!_attributes[symbolized_field_name] } if FieldHelper.boolean?(field)
if FieldHelper.boolean?(field)
generated_methods_module.send(:define_method, "#{field.name}?") do
!!_attributes[symbolized_field_name]
end
end

generated_methods_module.send(:define_method, "#{field.name}=") do |value|
_attributes[symbolized_field_name] = attribute_definition.coerce(value)
Expand Down
2 changes: 2 additions & 0 deletions lib/avromatic/model/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Configuration
def initialize(**options)
@avro_schema = find_avro_schema(**options)
raise ArgumentError.new('value_schema(_name) or schema(_name) must be specified') unless avro_schema

@key_avro_schema = find_schema_by_option(:key_schema, **options)
@nested_models = options[:nested_models]
@mutable = options.fetch(:mutable, false)
Expand All @@ -46,6 +47,7 @@ def find_avro_schema(**options)
(options[:schema] || options[:schema_name])
raise ArgumentError.new('Only one of value_schema(_name) and schema(_name) can be specified')
end

find_schema_by_option(:value_schema, **options) || find_schema_by_option(:schema, **options)
end

Expand Down
4 changes: 3 additions & 1 deletion lib/avromatic/model/message_decoder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def extract_decode_args(*args)
message_key, message_value = extract_key_and_value(*args)
model_key = model_key_for_message(message_key, message_value)
raise UnexpectedKeyError.new(*model_key) unless model_map.key?(model_key)

[model_map[model_key], message_key, message_value]
end

Expand All @@ -106,7 +107,7 @@ def lookup_schema_name(schema_id)
end

def extract_schema_id(data)
data[1..4].unpack('N').first
data[1..4].unpack1('N')
end

def validate_magic_byte!(data)
Expand All @@ -118,6 +119,7 @@ def build_model_map(models)
models.each_with_object(Hash.new) do |model, map|
key = model_key(model)
raise DuplicateKeyError.new(map[key], model) if map.key?(key) && !model.equal?(map[key])

map[key] = model
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/avromatic/model/messaging_serialization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def avro_message_value

def avro_message_key
raise 'Model has no key schema' unless key_avro_schema

avro_messaging.encode(
key_attributes_for_avro,
schema_name: key_avro_schema.fullname
Expand Down
Loading

0 comments on commit 459e64a

Please sign in to comment.