avromatic 2.2.2 → 2.3.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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +85 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -1
- data/Appraisals +34 -22
- data/CHANGELOG.md +16 -0
- data/README.md +3 -3
- data/avromatic.gemspec +7 -6
- data/bin/console +4 -3
- data/gemfiles/{rails5_0.gemfile → avro1_10_rails5_2.gemfile} +3 -3
- data/gemfiles/avro1_10_rails6_0.gemfile +9 -0
- data/gemfiles/avro1_10_rails6_1.gemfile +9 -0
- data/gemfiles/{rails5_2.gemfile → avro1_8_rails5_2.gemfile} +0 -0
- data/gemfiles/{rails5_1.gemfile → avro1_9_rails5_2.gemfile} +3 -3
- data/gemfiles/{rails6_0.gemfile → avro1_9_rails6_0.gemfile} +1 -1
- data/gemfiles/{avro_patches_rails5_0.gemfile → avro1_9_rails6_1.gemfile} +3 -3
- data/gemfiles/avro_patches_rails5_2.gemfile +1 -1
- data/gemfiles/avro_patches_rails6_1.gemfile +9 -0
- data/lib/avromatic.rb +0 -5
- data/lib/avromatic/io.rb +1 -7
- data/lib/avromatic/io/datum_reader.rb +18 -68
- data/lib/avromatic/io/datum_writer.rb +5 -11
- data/lib/avromatic/io/union_datum.rb +25 -0
- data/lib/avromatic/messaging.rb +4 -2
- data/lib/avromatic/model/attributes.rb +6 -6
- data/lib/avromatic/model/configurable.rb +24 -0
- data/lib/avromatic/model/messaging_serialization.rb +2 -1
- data/lib/avromatic/model/raw_serialization.rb +14 -11
- data/lib/avromatic/model/types/abstract_timestamp_type.rb +1 -1
- data/lib/avromatic/model/types/abstract_type.rb +3 -1
- data/lib/avromatic/model/types/array_type.rb +2 -2
- data/lib/avromatic/model/types/boolean_type.rb +1 -1
- data/lib/avromatic/model/types/custom_type.rb +1 -1
- data/lib/avromatic/model/types/date_type.rb +1 -1
- data/lib/avromatic/model/types/enum_type.rb +1 -1
- data/lib/avromatic/model/types/fixed_type.rb +1 -1
- data/lib/avromatic/model/types/float_type.rb +1 -1
- data/lib/avromatic/model/types/integer_type.rb +1 -1
- data/lib/avromatic/model/types/map_type.rb +2 -2
- data/lib/avromatic/model/types/null_type.rb +1 -1
- data/lib/avromatic/model/types/record_type.rb +1 -5
- data/lib/avromatic/model/types/string_type.rb +1 -1
- data/lib/avromatic/model/types/timestamp_micros_type.rb +4 -4
- data/lib/avromatic/model/types/timestamp_millis_type.rb +4 -4
- data/lib/avromatic/model/types/union_type.rb +11 -8
- data/lib/avromatic/version.rb +1 -1
- metadata +62 -47
- data/.travis.yml +0 -23
- data/gemfiles/avro_patches_rails5_1.gemfile +0 -9
- data/lib/avromatic/patches.rb +0 -18
- data/lib/avromatic/patches/schema_validator_patch.rb +0 -39
@@ -7,10 +7,11 @@ module Avromatic
|
|
7
7
|
class DatumWriter < Avro::IO::DatumWriter
|
8
8
|
def write_union(writers_schema, datum, encoder)
|
9
9
|
optional = writers_schema.schemas.first.type_sym == :null
|
10
|
-
if datum.is_a?(
|
11
|
-
index_of_schema = datum
|
10
|
+
if datum.is_a?(Avromatic::IO::UnionDatum)
|
11
|
+
index_of_schema = datum.member_index
|
12
12
|
# Avromatic does not treat the null of an optional field as part of the union
|
13
13
|
index_of_schema += 1 if optional
|
14
|
+
datum = datum.datum
|
14
15
|
elsif optional && writers_schema.schemas.size == 2
|
15
16
|
# Optimize for the common case of a union that's just an optional field
|
16
17
|
index_of_schema = datum.nil? ? 0 : 1
|
@@ -19,21 +20,14 @@ module Avromatic
|
|
19
20
|
Avro::Schema.validate(schema, datum)
|
20
21
|
end
|
21
22
|
end
|
23
|
+
|
22
24
|
unless index_of_schema
|
23
25
|
raise Avro::IO::AvroTypeError.new(writers_schema, datum)
|
24
26
|
end
|
27
|
+
|
25
28
|
encoder.write_long(index_of_schema)
|
26
29
|
write_data(writers_schema.schemas[index_of_schema], datum, encoder)
|
27
30
|
end
|
28
|
-
|
29
|
-
def write_record(writers_schema, datum, encoder)
|
30
|
-
if datum.is_a?(Hash) && datum.key?(Avromatic::IO::ENCODING_PROVIDER)
|
31
|
-
# This is only used for recursive serialization so validation has already been done
|
32
|
-
encoder.write(datum[Avromatic::IO::ENCODING_PROVIDER].avro_raw_value(validate: false))
|
33
|
-
else
|
34
|
-
super
|
35
|
-
end
|
36
|
-
end
|
37
31
|
end
|
38
32
|
end
|
39
33
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avromatic
|
4
|
+
module IO
|
5
|
+
class UnionDatum
|
6
|
+
attr_reader :member_index, :datum
|
7
|
+
|
8
|
+
def initialize(member_index, datum)
|
9
|
+
@member_index = member_index
|
10
|
+
@datum = datum
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
other.is_a?(Avromatic::IO::UnionDatum) &&
|
15
|
+
member_index == other.member_index &&
|
16
|
+
datum == other.datum
|
17
|
+
end
|
18
|
+
alias_method :eql?, :==
|
19
|
+
|
20
|
+
def hash
|
21
|
+
31 * datum.hash + member_index
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/avromatic/messaging.rb
CHANGED
@@ -29,7 +29,8 @@ module Avromatic
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# The following line differs from the parent class to use a custom DatumReader
|
32
|
-
|
32
|
+
reader_class = Avromatic.use_custom_datum_reader ? Avromatic::IO::DatumReader : Avro::IO::DatumReader
|
33
|
+
reader = reader_class.new(writers_schema, readers_schema)
|
33
34
|
reader.read(decoder)
|
34
35
|
end
|
35
36
|
|
@@ -50,7 +51,8 @@ module Avromatic
|
|
50
51
|
encoder.write([schema_id].pack('N'))
|
51
52
|
|
52
53
|
# The following line differs from the parent class to use a custom DatumWriter
|
53
|
-
|
54
|
+
writer_class = Avromatic.use_custom_datum_writer ? Avromatic::IO::DatumWriter : Avro::IO::DatumWriter
|
55
|
+
writer = writer_class.new(schema)
|
54
56
|
|
55
57
|
# The actual message comes last.
|
56
58
|
writer.write(message, encoder)
|
@@ -74,23 +74,23 @@ module Avromatic
|
|
74
74
|
def initialize(data = {})
|
75
75
|
super()
|
76
76
|
|
77
|
-
|
77
|
+
num_valid_keys = 0
|
78
78
|
attribute_definitions.each do |attribute_name, attribute_definition|
|
79
79
|
if data.include?(attribute_name)
|
80
|
-
|
80
|
+
num_valid_keys += 1
|
81
81
|
value = data.fetch(attribute_name)
|
82
82
|
send(attribute_definition.setter_name, value)
|
83
83
|
elsif data.include?(attribute_definition.name_string)
|
84
|
-
|
84
|
+
num_valid_keys += 1
|
85
85
|
value = data[attribute_definition.name_string]
|
86
86
|
send(attribute_definition.setter_name, value)
|
87
|
-
elsif !
|
87
|
+
elsif !_attributes.include?(attribute_name)
|
88
88
|
send(attribute_definition.setter_name, attribute_definition.default)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
-
unless Avromatic.allow_unknown_attributes ||
|
93
|
-
unknown_attributes = (data.keys.map(&:to_s) -
|
92
|
+
unless Avromatic.allow_unknown_attributes || num_valid_keys == data.size
|
93
|
+
unknown_attributes = (data.keys.map(&:to_s) - _attributes.keys.map(&:to_s)).sort
|
94
94
|
allowed_attributes = attribute_definitions.keys.map(&:to_s).sort
|
95
95
|
message = "Unexpected arguments for #{self.class.name}#initialize: #{unknown_attributes.join(', ')}. " \
|
96
96
|
"Only the following arguments are allowed: #{allowed_attributes.join(', ')}. Provided arguments: #{data.inspect}"
|
@@ -8,6 +8,17 @@ module Avromatic
|
|
8
8
|
module Configurable
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
|
+
# Wraps a reference to a field so we can access both the string and symbolized versions of the name
|
12
|
+
# without repeated memory allocations.
|
13
|
+
class FieldReference
|
14
|
+
attr_reader :name, :name_sym
|
15
|
+
|
16
|
+
def initialize(name)
|
17
|
+
@name = -name
|
18
|
+
@name_sym = name.to_sym
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
11
22
|
module ClassMethods
|
12
23
|
attr_accessor :config
|
13
24
|
delegate :avro_schema, :value_avro_schema, :key_avro_schema, to: :config
|
@@ -20,6 +31,18 @@ module Avromatic
|
|
20
31
|
@key_avro_field_names ||= key_avro_schema.fields.map(&:name).map(&:to_sym).freeze
|
21
32
|
end
|
22
33
|
|
34
|
+
def value_avro_field_references
|
35
|
+
@value_avro_field_references ||= value_avro_schema.fields.map do |field|
|
36
|
+
Avromatic::Model::Configurable::FieldReference.new(field.name)
|
37
|
+
end.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def key_avro_field_references
|
41
|
+
@key_avro_field_references ||= key_avro_schema.fields.map do |field|
|
42
|
+
Avromatic::Model::Configurable::FieldReference.new(field.name)
|
43
|
+
end.freeze
|
44
|
+
end
|
45
|
+
|
23
46
|
def value_avro_fields_by_name
|
24
47
|
@value_avro_fields_by_name ||= mapped_by_name(value_avro_schema)
|
25
48
|
end
|
@@ -43,6 +66,7 @@ module Avromatic
|
|
43
66
|
|
44
67
|
delegate :avro_schema, :value_avro_schema, :key_avro_schema,
|
45
68
|
:value_avro_field_names, :key_avro_field_names,
|
69
|
+
:value_avro_field_references, :key_avro_field_references,
|
46
70
|
to: :class
|
47
71
|
end
|
48
72
|
end
|
@@ -48,7 +48,8 @@ module Avromatic
|
|
48
48
|
value_attributes = avro_messaging
|
49
49
|
.decode(message_value, schema_name: avro_schema.fullname)
|
50
50
|
|
51
|
-
value_attributes.merge!(key_attributes
|
51
|
+
value_attributes.merge!(key_attributes) if key_attributes
|
52
|
+
value_attributes
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
@@ -29,34 +29,37 @@ module Avromatic
|
|
29
29
|
|
30
30
|
def value_attributes_for_avro(validate: true)
|
31
31
|
if self.class.config.mutable
|
32
|
-
avro_hash(
|
32
|
+
avro_hash(value_avro_field_references, validate: validate)
|
33
33
|
else
|
34
|
-
@value_attributes_for_avro ||= avro_hash(
|
34
|
+
@value_attributes_for_avro ||= avro_hash(value_avro_field_references, validate: validate)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
def key_attributes_for_avro(validate: true)
|
39
|
-
avro_hash(
|
39
|
+
avro_hash(key_avro_field_references, validate: validate)
|
40
40
|
end
|
41
41
|
|
42
42
|
def avro_value_datum(validate: true)
|
43
43
|
if self.class.config.mutable
|
44
|
-
avro_hash(
|
44
|
+
avro_hash(value_avro_field_references, strict: true, validate: validate)
|
45
45
|
else
|
46
|
-
@avro_datum ||= avro_hash(
|
46
|
+
@avro_datum ||= avro_hash(value_avro_field_references, strict: true, validate: validate)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
50
|
def avro_key_datum(validate: true)
|
51
|
-
avro_hash(
|
51
|
+
avro_hash(key_avro_field_references, strict: true, validate: validate)
|
52
52
|
end
|
53
53
|
|
54
54
|
private
|
55
55
|
|
56
|
-
def avro_hash(
|
56
|
+
def avro_hash(field_references, strict: false, validate:)
|
57
57
|
avro_validate! if validate
|
58
|
-
|
59
|
-
|
58
|
+
field_references.each_with_object(Hash.new) do |field_reference, result|
|
59
|
+
next unless _attributes.include?(field_reference.name_sym)
|
60
|
+
|
61
|
+
value = _attributes[field_reference.name_sym]
|
62
|
+
result[field_reference.name] = attribute_definitions[field_reference.name_sym].serialize(value, strict)
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
@@ -78,8 +81,8 @@ module Avromatic
|
|
78
81
|
def avro_raw_decode(key: nil, value:, key_schema: nil, value_schema: nil)
|
79
82
|
key_attributes = key && decode_avro_datum(key, key_schema, :key)
|
80
83
|
value_attributes = decode_avro_datum(value, value_schema, :value)
|
81
|
-
|
82
|
-
new(value_attributes
|
84
|
+
value_attributes.merge!(key_attributes) if key_attributes
|
85
|
+
new(value_attributes)
|
83
86
|
end
|
84
87
|
|
85
88
|
private
|
@@ -31,7 +31,9 @@ module Avromatic
|
|
31
31
|
raise "#{__method__} must be overridden by #{self.class.name}"
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
# Note we use positional args rather than keyword args to reduce
|
35
|
+
# memory allocations
|
36
|
+
def serialize(_value, _strict)
|
35
37
|
raise "#{__method__} must be overridden by #{self.class.name}"
|
36
38
|
end
|
37
39
|
|
@@ -40,11 +40,11 @@ module Avromatic
|
|
40
40
|
value.nil? || (value.is_a?(::Array) && value.all? { |element| value_type.coerced?(element) })
|
41
41
|
end
|
42
42
|
|
43
|
-
def serialize(value, strict
|
43
|
+
def serialize(value, strict)
|
44
44
|
if value.nil?
|
45
45
|
value
|
46
46
|
else
|
47
|
-
value.map { |element| value_type.serialize(element, strict
|
47
|
+
value.map { |element| value_type.serialize(element, strict) }
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -59,12 +59,12 @@ module Avromatic
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
def serialize(value, strict
|
62
|
+
def serialize(value, strict)
|
63
63
|
if value.nil?
|
64
64
|
value
|
65
65
|
else
|
66
66
|
value.each_with_object({}) do |(element_key, element_value), result|
|
67
|
-
result[key_type.serialize(element_key, strict
|
67
|
+
result[key_type.serialize(element_key, strict)] = value_type.serialize(element_value, strict)
|
68
68
|
end
|
69
69
|
end
|
70
70
|
end
|
@@ -39,13 +39,9 @@ module Avromatic
|
|
39
39
|
value.nil? || value.is_a?(record_class)
|
40
40
|
end
|
41
41
|
|
42
|
-
def serialize(value, strict
|
42
|
+
def serialize(value, strict)
|
43
43
|
if value.nil?
|
44
44
|
value
|
45
|
-
elsif !strict && Avromatic.use_custom_datum_writer && Avromatic.use_encoding_providers? && !record_class.config.mutable
|
46
|
-
# n.b. Ideally we'd just return value here instead of wrapping it in a
|
47
|
-
# hash but then we'd have no place to stash the union member index...
|
48
|
-
{ Avromatic::IO::ENCODING_PROVIDER => value }
|
49
45
|
else
|
50
46
|
# This is only used for recursive serialization so validation has already been done
|
51
47
|
strict ? value.avro_value_datum(validate: false) : value.value_attributes_for_avro(validate: false)
|
@@ -13,6 +13,10 @@ module Avromatic
|
|
13
13
|
'timestamp-micros'
|
14
14
|
end
|
15
15
|
|
16
|
+
def referenced_model_classes
|
17
|
+
EMPTY_ARRAY
|
18
|
+
end
|
19
|
+
|
16
20
|
private
|
17
21
|
|
18
22
|
def truncated?(value)
|
@@ -25,10 +29,6 @@ module Avromatic
|
|
25
29
|
# of time zone.
|
26
30
|
::Time.at(input.to_i, input.usec)
|
27
31
|
end
|
28
|
-
|
29
|
-
def referenced_model_classes
|
30
|
-
EMPTY_ARRAY
|
31
|
-
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -13,6 +13,10 @@ module Avromatic
|
|
13
13
|
'timestamp-millis'
|
14
14
|
end
|
15
15
|
|
16
|
+
def referenced_model_classes
|
17
|
+
EMPTY_ARRAY
|
18
|
+
end
|
19
|
+
|
16
20
|
private
|
17
21
|
|
18
22
|
def truncated?(value)
|
@@ -25,10 +29,6 @@ module Avromatic
|
|
25
29
|
# of time zone.
|
26
30
|
::Time.at(input.to_i, input.usec / 1000 * 1000)
|
27
31
|
end
|
28
|
-
|
29
|
-
def referenced_model_classes
|
30
|
-
EMPTY_ARRAY
|
31
|
-
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -7,7 +7,6 @@ module Avromatic
|
|
7
7
|
module Model
|
8
8
|
module Types
|
9
9
|
class UnionType < AbstractType
|
10
|
-
MEMBER_INDEX = ::Avromatic::IO::DatumReader::UNION_MEMBER_INDEX
|
11
10
|
attr_reader :member_types, :value_classes, :input_classes
|
12
11
|
|
13
12
|
def initialize(member_types:)
|
@@ -24,8 +23,8 @@ module Avromatic
|
|
24
23
|
return input if coerced?(input)
|
25
24
|
|
26
25
|
result = nil
|
27
|
-
if input.is_a?(
|
28
|
-
result = member_types[input.
|
26
|
+
if input.is_a?(Avromatic::IO::UnionDatum)
|
27
|
+
result = member_types[input.member_index].coerce(input.datum)
|
29
28
|
else
|
30
29
|
member_types.find do |member_type|
|
31
30
|
result = safe_coerce(member_type, input)
|
@@ -40,18 +39,22 @@ module Avromatic
|
|
40
39
|
end
|
41
40
|
|
42
41
|
def coerced?(value)
|
42
|
+
return false if value.is_a?(Avromatic::IO::UnionDatum)
|
43
|
+
|
43
44
|
value.nil? || member_types.any? do |member_type|
|
44
45
|
member_type.coerced?(value)
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
49
|
def coercible?(input)
|
50
|
+
return true if value.is_a?(Avromatic::IO::UnionDatum)
|
51
|
+
|
49
52
|
coerced?(input) || member_types.any? do |member_type|
|
50
53
|
member_type.coercible?(input)
|
51
54
|
end
|
52
55
|
end
|
53
56
|
|
54
|
-
def serialize(value, strict
|
57
|
+
def serialize(value, strict)
|
55
58
|
# Avromatic does not treat the null of an optional field as part of the union
|
56
59
|
return nil if value.nil?
|
57
60
|
|
@@ -60,11 +63,11 @@ module Avromatic
|
|
60
63
|
raise ArgumentError.new("Expected #{value.inspect} to be one of #{value_classes.map(&:name)}")
|
61
64
|
end
|
62
65
|
|
63
|
-
|
64
|
-
if !strict && Avromatic.use_custom_datum_writer
|
65
|
-
|
66
|
+
serialized_value = member_types[member_index].serialize(value, strict)
|
67
|
+
if !strict && Avromatic.use_custom_datum_writer
|
68
|
+
serialized_value = Avromatic::IO::UnionDatum.new(member_index, serialized_value)
|
66
69
|
end
|
67
|
-
|
70
|
+
serialized_value
|
68
71
|
end
|
69
72
|
|
70
73
|
def referenced_model_classes
|