avromatic 1.0.0 → 2.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -1
- data/Appraisals +2 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -0
- data/README.md +57 -25
- data/Rakefile +2 -0
- data/avromatic.gemspec +7 -3
- data/lib/avromatic/io/datum_reader.rb +2 -0
- data/lib/avromatic/io/datum_writer.rb +7 -1
- data/lib/avromatic/io.rb +4 -2
- data/lib/avromatic/messaging.rb +2 -0
- data/lib/avromatic/model/attributes.rb +112 -158
- data/lib/avromatic/model/builder.rb +4 -7
- data/lib/avromatic/model/coercion_error.rb +8 -0
- data/lib/avromatic/model/configurable.rb +2 -0
- data/lib/avromatic/model/configuration.rb +2 -0
- data/lib/avromatic/model/{custom_type.rb → custom_type_configuration.rb} +2 -2
- data/lib/avromatic/model/{type_registry.rb → custom_type_registry.rb} +15 -18
- data/lib/avromatic/model/field_helper.rb +20 -0
- data/lib/avromatic/model/message_decoder.rb +2 -0
- data/lib/avromatic/model/messaging_serialization.rb +2 -0
- data/lib/avromatic/model/nested_models.rb +2 -17
- data/lib/avromatic/model/raw_serialization.rb +21 -84
- data/lib/avromatic/model/types/abstract_timestamp_type.rb +57 -0
- data/lib/avromatic/model/types/abstract_type.rb +37 -0
- data/lib/avromatic/model/types/array_type.rb +53 -0
- data/lib/avromatic/model/types/boolean_type.rb +39 -0
- data/lib/avromatic/model/types/custom_type.rb +64 -0
- data/lib/avromatic/model/types/date_type.rb +41 -0
- data/lib/avromatic/model/types/enum_type.rb +56 -0
- data/lib/avromatic/model/types/fixed_type.rb +45 -0
- data/lib/avromatic/model/types/float_type.rb +48 -0
- data/lib/avromatic/model/types/integer_type.rb +39 -0
- data/lib/avromatic/model/types/map_type.rb +74 -0
- data/lib/avromatic/model/types/null_type.rb +39 -0
- data/lib/avromatic/model/types/record_type.rb +57 -0
- data/lib/avromatic/model/types/string_type.rb +48 -0
- data/lib/avromatic/model/types/timestamp_micros_type.rb +32 -0
- data/lib/avromatic/model/types/timestamp_millis_type.rb +32 -0
- data/lib/avromatic/model/types/type_factory.rb +118 -0
- data/lib/avromatic/model/types/union_type.rb +87 -0
- data/lib/avromatic/model/unknown_attribute_error.rb +15 -0
- data/lib/avromatic/model/validation.rb +57 -36
- data/lib/avromatic/model/validation_error.rb +8 -0
- data/lib/avromatic/model/value_object.rb +2 -0
- data/lib/avromatic/model.rb +6 -2
- data/lib/avromatic/model_registry.rb +2 -0
- data/lib/avromatic/patches/schema_validator_patch.rb +2 -0
- data/lib/avromatic/patches.rb +2 -0
- data/lib/avromatic/railtie.rb +2 -0
- data/lib/avromatic/rspec.rb +2 -0
- data/lib/avromatic/version.rb +3 -1
- data/lib/avromatic.rb +9 -4
- metadata +32 -21
- data/lib/avromatic/model/allowed_type_validator.rb +0 -7
- data/lib/avromatic/model/allowed_writer_methods_memoization.rb +0 -16
- data/lib/avromatic/model/attribute/abstract_timestamp.rb +0 -26
- data/lib/avromatic/model/attribute/record.rb +0 -26
- data/lib/avromatic/model/attribute/timestamp_micros.rb +0 -26
- data/lib/avromatic/model/attribute/timestamp_millis.rb +0 -26
- data/lib/avromatic/model/attribute/union.rb +0 -66
- data/lib/avromatic/model/attribute_type/union.rb +0 -29
- data/lib/avromatic/model/logical_types.rb +0 -19
- data/lib/avromatic/model/null_custom_type.rb +0 -21
- data/lib/avromatic/model/passthrough_serializer.rb +0 -10
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'avromatic/model/custom_type_configuration'
|
2
4
|
|
3
5
|
module Avromatic
|
4
6
|
module Model
|
5
|
-
class
|
7
|
+
class CustomTypeRegistry
|
6
8
|
|
7
9
|
delegate :clear, to: :custom_types
|
8
10
|
|
@@ -16,35 +18,30 @@ module Avromatic
|
|
16
18
|
# @block If a block is specified then the CustomType is yielded for
|
17
19
|
# additional configuration.
|
18
20
|
def register_type(fullname, value_class = nil)
|
19
|
-
custom_types[fullname.to_s] = Avromatic::Model::
|
21
|
+
custom_types[fullname.to_s] = Avromatic::Model::CustomTypeConfiguration.new(value_class).tap do |type|
|
20
22
|
yield(type) if block_given?
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
# @object [Avro::Schema] Custom type may be fetched based on a Avro field
|
25
|
-
# or schema.
|
26
|
-
|
27
|
-
def fetch(object, field_class = nil)
|
27
|
+
# or schema.
|
28
|
+
def registered?(object)
|
28
29
|
field_type = object.is_a?(Avro::Schema::Field) ? object.type : object
|
30
|
+
custom_types.include?(field_type.fullname) if field_type.is_a?(Avro::Schema::NamedSchema)
|
31
|
+
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
# @object [Avro::Schema] Custom type may be fetched based on a Avro field
|
34
|
+
# or schema. If there is no custom type, then an exception will be thrown.
|
35
|
+
# @field_class [Object] Value class that has been determined for a field.
|
36
|
+
def fetch(object)
|
37
|
+
field_type = object.is_a?(Avro::Schema::Field) ? object.type : object
|
34
38
|
fullname = field_type.fullname if field_type.is_a?(Avro::Schema::NamedSchema)
|
35
|
-
custom_types.fetch(fullname
|
39
|
+
custom_types.fetch(fullname)
|
36
40
|
end
|
37
41
|
|
38
42
|
private
|
39
43
|
|
40
44
|
attr_reader :custom_types
|
41
|
-
|
42
|
-
# The type that is used to define a Virtus attribute may be a Class or
|
43
|
-
# for an array or map field it may be an instance of an Array or Hash.
|
44
|
-
# This method safely checks if a Union class has been selected.
|
45
|
-
def union_attribute?(attribute_type)
|
46
|
-
attribute_type.is_a?(Class) && attribute_type < Avromatic::Model::AttributeType::Union
|
47
|
-
end
|
48
45
|
end
|
49
46
|
end
|
50
47
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avromatic
|
4
|
+
module Model
|
5
|
+
module FieldHelper
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# An optional field is represented as a union where the first member
|
9
|
+
# is null.
|
10
|
+
def optional?(field)
|
11
|
+
field.type.type_sym == :union &&
|
12
|
+
field.type.schemas.first.type_sym == :null
|
13
|
+
end
|
14
|
+
|
15
|
+
def required?(field)
|
16
|
+
!optional?(field)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/inflector/methods'
|
2
4
|
|
3
5
|
module Avromatic
|
@@ -8,26 +10,9 @@ module Avromatic
|
|
8
10
|
extend ActiveSupport::Concern
|
9
11
|
|
10
12
|
module ClassMethods
|
11
|
-
def build_nested_model(schema)
|
12
|
-
fullname = nested_models.remove_prefix(schema.fullname)
|
13
|
-
|
14
|
-
if nested_models.registered?(fullname)
|
15
|
-
nested_models[fullname]
|
16
|
-
else
|
17
|
-
Avromatic::Model.model(schema: schema,
|
18
|
-
nested_models: nested_models)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
13
|
# Register this model if it can be used as a nested model.
|
23
14
|
def register!
|
24
15
|
if key_avro_schema.nil? && value_avro_schema.type_sym == :record
|
25
|
-
# Register the generated model with Axiom to prevent it being
|
26
|
-
# treated as a BasicObject.
|
27
|
-
# See https://github.com/solnic/virtus/issues/284#issuecomment-56405137
|
28
|
-
nested_model = self
|
29
|
-
Axiom::Types::Object.new { primitive(nested_model) }
|
30
|
-
|
31
16
|
nested_models.register(self)
|
32
17
|
end
|
33
18
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Avromatic
|
4
4
|
module Model
|
@@ -11,108 +11,52 @@ module Avromatic
|
|
11
11
|
module Encode
|
12
12
|
extend ActiveSupport::Concern
|
13
13
|
|
14
|
-
delegate :
|
15
|
-
|
16
|
-
private :avro_serializer, :datum_writer, :datum_reader
|
14
|
+
delegate :datum_writer, :datum_reader, to: :class
|
15
|
+
private :datum_writer, :datum_reader
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
included do
|
21
|
-
@attribute_member_types = {}
|
22
|
-
end
|
23
|
-
|
24
|
-
module ClassMethods
|
25
|
-
def recursive_serialize(value, name: nil, member_types: nil, strict: false) # rubocop:disable Lint/ShadowedArgument
|
26
|
-
member_types = attribute_member_types(name) if name
|
27
|
-
member_types ||= EMPTY_ARRAY
|
28
|
-
|
29
|
-
if value.is_a?(Avromatic::Model::Attributes)
|
30
|
-
hash = strict ? value.avro_value_datum : value.value_attributes_for_avro
|
31
|
-
if !strict && Avromatic.use_custom_datum_writer
|
32
|
-
if Avromatic.use_encoding_providers? && !value.class.config.mutable
|
33
|
-
# n.b. Ideally we'd just return value here instead of wrapping it in a
|
34
|
-
# hash but then we'd have no place to stash the union member index...
|
35
|
-
hash = { Avromatic::IO::ENCODING_PROVIDER => value }
|
36
|
-
end
|
37
|
-
member_index = member_types.index(value.class) if member_types.any?
|
38
|
-
hash[Avromatic::IO::UNION_MEMBER_INDEX] = member_index if member_index
|
39
|
-
end
|
40
|
-
hash
|
41
|
-
elsif value.is_a?(Array)
|
42
|
-
value.map { |v| recursive_serialize(v, member_types: member_types, strict: strict) }
|
43
|
-
elsif value.is_a?(Hash)
|
44
|
-
value.each_with_object({}) do |(k, v), map|
|
45
|
-
map[k] = recursive_serialize(v, member_types: member_types, strict: strict)
|
46
|
-
end
|
47
|
-
else
|
48
|
-
avro_serializer[name].call(value)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def attribute_member_types(name)
|
55
|
-
@attribute_member_types.fetch(name) do
|
56
|
-
member_types = nil
|
57
|
-
attribute = attribute_set[name] if name
|
58
|
-
if attribute
|
59
|
-
if attribute.primitive == Array &&
|
60
|
-
attribute.member_type.is_a?(Avromatic::Model::Attribute::Union)
|
61
|
-
member_types = attribute.member_type.primitive.types
|
62
|
-
elsif attribute.primitive == Hash &&
|
63
|
-
attribute.value_type.is_a?(Avromatic::Model::Attribute::Union)
|
64
|
-
member_types = attribute.value_type.primitive.types
|
65
|
-
elsif attribute.options[:primitive] == Avromatic::Model::AttributeType::Union
|
66
|
-
member_types = attribute.primitive.types
|
67
|
-
end
|
68
|
-
end
|
69
|
-
@attribute_member_types[name] = member_types
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def avro_raw_value
|
17
|
+
def avro_raw_value(validate: true)
|
75
18
|
if self.class.config.mutable
|
76
|
-
avro_raw_encode(value_attributes_for_avro, :value)
|
19
|
+
avro_raw_encode(value_attributes_for_avro(validate: validate), :value)
|
77
20
|
else
|
78
|
-
@avro_raw_value ||= avro_raw_encode(value_attributes_for_avro, :value)
|
21
|
+
@avro_raw_value ||= avro_raw_encode(value_attributes_for_avro(validate: validate), :value)
|
79
22
|
end
|
80
23
|
end
|
81
24
|
|
82
|
-
def avro_raw_key
|
25
|
+
def avro_raw_key(validate: true)
|
83
26
|
raise 'Model has no key schema' unless key_avro_schema
|
84
|
-
avro_raw_encode(key_attributes_for_avro, :key)
|
27
|
+
avro_raw_encode(key_attributes_for_avro(validate: validate), :key)
|
85
28
|
end
|
86
29
|
|
87
|
-
def value_attributes_for_avro
|
30
|
+
def value_attributes_for_avro(validate: true)
|
88
31
|
if self.class.config.mutable
|
89
|
-
avro_hash(value_avro_field_names)
|
32
|
+
avro_hash(value_avro_field_names, validate: validate)
|
90
33
|
else
|
91
|
-
@value_attributes_for_avro ||= avro_hash(value_avro_field_names)
|
34
|
+
@value_attributes_for_avro ||= avro_hash(value_avro_field_names, validate: validate)
|
92
35
|
end
|
93
36
|
end
|
94
37
|
|
95
|
-
def key_attributes_for_avro
|
96
|
-
avro_hash(key_avro_field_names)
|
38
|
+
def key_attributes_for_avro(validate: true)
|
39
|
+
avro_hash(key_avro_field_names, validate: validate)
|
97
40
|
end
|
98
41
|
|
99
|
-
def avro_value_datum
|
42
|
+
def avro_value_datum(validate: true)
|
100
43
|
if self.class.config.mutable
|
101
|
-
avro_hash(value_avro_field_names, strict: true)
|
44
|
+
avro_hash(value_avro_field_names, strict: true, validate: validate)
|
102
45
|
else
|
103
|
-
@avro_datum ||= avro_hash(value_avro_field_names, strict: true)
|
46
|
+
@avro_datum ||= avro_hash(value_avro_field_names, strict: true, validate: validate)
|
104
47
|
end
|
105
48
|
end
|
106
49
|
|
107
|
-
def avro_key_datum
|
108
|
-
avro_hash(key_avro_field_names, strict: true)
|
50
|
+
def avro_key_datum(validate: true)
|
51
|
+
avro_hash(key_avro_field_names, strict: true, validate: validate)
|
109
52
|
end
|
110
53
|
|
111
54
|
private
|
112
55
|
|
113
|
-
def avro_hash(fields, strict: false)
|
56
|
+
def avro_hash(fields, strict: false, validate:)
|
57
|
+
avro_validate! if validate
|
114
58
|
attributes.slice(*fields).each_with_object(Hash.new) do |(key, value), result|
|
115
|
-
result[key.to_s] =
|
59
|
+
result[key.to_s] = attribute_definitions[key].serialize(value, strict: strict)
|
116
60
|
end
|
117
61
|
end
|
118
62
|
|
@@ -161,13 +105,6 @@ module Avromatic
|
|
161
105
|
Avromatic.use_custom_datum_writer ? Avromatic::IO::DatumWriter : Avro::IO::DatumWriter
|
162
106
|
end
|
163
107
|
|
164
|
-
# Store a hash of Procs by field name (as a symbol) to convert
|
165
|
-
# the value before Avro serialization.
|
166
|
-
# Returns the default PassthroughSerializer if a key is not present.
|
167
|
-
def avro_serializer
|
168
|
-
@avro_serializer ||= Hash.new(PassthroughSerializer)
|
169
|
-
end
|
170
|
-
|
171
108
|
def datum_writer
|
172
109
|
@datum_writer ||= begin
|
173
110
|
hash = { value: datum_writer_class.new(value_avro_schema) }
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/time_with_zone'
|
4
|
+
|
5
|
+
module Avromatic
|
6
|
+
module Model
|
7
|
+
module Types
|
8
|
+
class AbstractTimestampType < AbstractType
|
9
|
+
VALUE_CLASSES = [::Time].freeze
|
10
|
+
INPUT_CLASSES = [::Time, ::DateTime, ::ActiveSupport::TimeWithZone].freeze
|
11
|
+
|
12
|
+
def value_classes
|
13
|
+
VALUE_CLASSES
|
14
|
+
end
|
15
|
+
|
16
|
+
def input_classes
|
17
|
+
INPUT_CLASSES
|
18
|
+
end
|
19
|
+
|
20
|
+
def coerce(input)
|
21
|
+
if input.nil? || coerced?(input)
|
22
|
+
input
|
23
|
+
elsif input.is_a?(::Time) || input.is_a?(::DateTime)
|
24
|
+
coerce_time(input)
|
25
|
+
else
|
26
|
+
raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def coercible?(input)
|
31
|
+
input.nil? || input.is_a?(::Time) || input.is_a?(::DateTime)
|
32
|
+
end
|
33
|
+
|
34
|
+
def coerced?(value)
|
35
|
+
# ActiveSupport::TimeWithZone overrides is_a? is to make it look like a Time
|
36
|
+
# even though it's not which can lead to unexpected behavior if we don't force
|
37
|
+
# a coercion
|
38
|
+
value.is_a?(::Time) && value.class != ActiveSupport::TimeWithZone && truncated?(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def serialize(value, **)
|
42
|
+
value
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def truncated?(_value)
|
48
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def coerce_time(_input)
|
52
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avromatic
|
4
|
+
module Model
|
5
|
+
module Types
|
6
|
+
class AbstractType
|
7
|
+
def value_classes
|
8
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def input_classes
|
12
|
+
value_classes
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def coerce(_input)
|
20
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def coercible?(_input)
|
24
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def coerced?(_value)
|
28
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def serialize(_value, **)
|
32
|
+
raise "#{__method__} must be overridden by #{self.class.name}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'avromatic/model/types/abstract_type'
|
4
|
+
|
5
|
+
module Avromatic
|
6
|
+
module Model
|
7
|
+
module Types
|
8
|
+
class ArrayType < AbstractType
|
9
|
+
VALUE_CLASSES = [::Array].freeze
|
10
|
+
|
11
|
+
attr_reader :value_type
|
12
|
+
|
13
|
+
def initialize(value_type:)
|
14
|
+
@value_type = value_type
|
15
|
+
end
|
16
|
+
|
17
|
+
def value_classes
|
18
|
+
VALUE_CLASSES
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
"array[#{value_type.name}]"
|
23
|
+
end
|
24
|
+
|
25
|
+
def coerce(input)
|
26
|
+
if input.nil?
|
27
|
+
input
|
28
|
+
elsif input.is_a?(::Array)
|
29
|
+
input.map { |element_input| value_type.coerce(element_input) }
|
30
|
+
else
|
31
|
+
raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def coercible?(input)
|
36
|
+
input.nil? || (input.is_a?(::Array) && input.all? { |element_input| value_type.coercible?(element_input) })
|
37
|
+
end
|
38
|
+
|
39
|
+
def coerced?(value)
|
40
|
+
value.nil? || (value.is_a?(::Array) && value.all? { |element| value_type.coerced?(element) })
|
41
|
+
end
|
42
|
+
|
43
|
+
def serialize(value, strict:)
|
44
|
+
if value.nil?
|
45
|
+
value
|
46
|
+
else
|
47
|
+
value.map { |element| value_type.serialize(element, strict: strict) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'avromatic/model/types/abstract_type'
|
4
|
+
|
5
|
+
module Avromatic
|
6
|
+
module Model
|
7
|
+
module Types
|
8
|
+
class BooleanType < AbstractType
|
9
|
+
VALUE_CLASSES = [::TrueClass, ::FalseClass].freeze
|
10
|
+
|
11
|
+
def value_classes
|
12
|
+
VALUE_CLASSES
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
'boolean'
|
17
|
+
end
|
18
|
+
|
19
|
+
def coerce(input)
|
20
|
+
if coercible?(input)
|
21
|
+
input
|
22
|
+
else
|
23
|
+
raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def coercible?(input)
|
28
|
+
input.nil? || input.is_a?(::TrueClass) || input.is_a?(::FalseClass)
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :coerced?, :coercible?
|
32
|
+
|
33
|
+
def serialize(value, **)
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'avromatic/model/types/abstract_type'
|
4
|
+
|
5
|
+
module Avromatic
|
6
|
+
module Model
|
7
|
+
module Types
|
8
|
+
class CustomType < AbstractType
|
9
|
+
IDENTITY_PROC = Proc.new { |value| value }
|
10
|
+
|
11
|
+
attr_reader :custom_type_configuration, :value_classes, :default_type
|
12
|
+
|
13
|
+
def initialize(custom_type_configuration:, default_type:)
|
14
|
+
@custom_type_configuration = custom_type_configuration
|
15
|
+
@default_type = default_type
|
16
|
+
@deserializer = custom_type_configuration.deserializer || IDENTITY_PROC
|
17
|
+
@serializer = custom_type_configuration.serializer || IDENTITY_PROC
|
18
|
+
@value_classes = if custom_type_configuration.value_class
|
19
|
+
[custom_type_configuration.value_class].freeze
|
20
|
+
else
|
21
|
+
default_type.value_classes
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def input_classes
|
26
|
+
# We don't know the valid input classes for a custom type
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
custom_type_configuration.value_class ? custom_type_configuration.value_class.name.to_s.freeze : default_type.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def coerce(input)
|
34
|
+
if input.nil?
|
35
|
+
input
|
36
|
+
else
|
37
|
+
@deserializer.call(input)
|
38
|
+
end
|
39
|
+
rescue StandardError => e
|
40
|
+
# TODO: Don't swallow this
|
41
|
+
raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}: #{e.message}")
|
42
|
+
end
|
43
|
+
|
44
|
+
def coercible?(input)
|
45
|
+
# TODO: Delegate this to optional configuration
|
46
|
+
input.nil? || !coerce(input).nil?
|
47
|
+
rescue ArgumentError
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def coerced?(value)
|
52
|
+
# TODO: Delegate this to optional configuration
|
53
|
+
coerce(value) == value
|
54
|
+
rescue ArgumentError
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def serialize(value, **)
|
59
|
+
@serializer.call(value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'avromatic/model/types/abstract_type'
|
4
|
+
|
5
|
+
module Avromatic
|
6
|
+
module Model
|
7
|
+
module Types
|
8
|
+
class DateType < AbstractType
|
9
|
+
VALUE_CLASSES = [::Date].freeze
|
10
|
+
|
11
|
+
def value_classes
|
12
|
+
VALUE_CLASSES
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
'date'
|
17
|
+
end
|
18
|
+
|
19
|
+
def coerce(input)
|
20
|
+
if input.is_a?(::Time) || input.is_a?(::DateTime)
|
21
|
+
::Date.new(input.year, input.month, input.day)
|
22
|
+
elsif input.nil? || input.is_a?(::Date)
|
23
|
+
input
|
24
|
+
else
|
25
|
+
raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def coercible?(input)
|
30
|
+
input.nil? || input.is_a?(::Date) || input.is_a?(::Time)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :coerced?, :coercible?
|
34
|
+
|
35
|
+
def serialize(value, **)
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'avromatic/model/types/abstract_type'
|
4
|
+
|
5
|
+
module Avromatic
|
6
|
+
module Model
|
7
|
+
module Types
|
8
|
+
class EnumType < AbstractType
|
9
|
+
VALUE_CLASSES = [::String].freeze
|
10
|
+
INPUT_CLASSES = [::String, ::Symbol].freeze
|
11
|
+
|
12
|
+
attr_reader :allowed_values
|
13
|
+
|
14
|
+
def initialize(allowed_values)
|
15
|
+
@allowed_values = allowed_values.to_set
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
"enum#{allowed_values.to_a}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def value_classes
|
23
|
+
VALUE_CLASSES
|
24
|
+
end
|
25
|
+
|
26
|
+
def input_classes
|
27
|
+
INPUT_CLASSES
|
28
|
+
end
|
29
|
+
|
30
|
+
def coerce(input)
|
31
|
+
if input.nil?
|
32
|
+
input
|
33
|
+
elsif coercible?(input)
|
34
|
+
input.to_s
|
35
|
+
else
|
36
|
+
raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def coerced?(input)
|
41
|
+
input.nil? || (input.is_a?(::String) && allowed_values.include?(input))
|
42
|
+
end
|
43
|
+
|
44
|
+
def coercible?(input)
|
45
|
+
input.nil? ||
|
46
|
+
(input.is_a?(::String) && allowed_values.include?(input)) ||
|
47
|
+
(input.is_a?(::Symbol) && allowed_values.include?(input.to_s))
|
48
|
+
end
|
49
|
+
|
50
|
+
def serialize(value, **)
|
51
|
+
value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'avromatic/model/types/abstract_type'
|
4
|
+
|
5
|
+
module Avromatic
|
6
|
+
module Model
|
7
|
+
module Types
|
8
|
+
class FixedType < AbstractType
|
9
|
+
VALUE_CLASSES = [::String].freeze
|
10
|
+
|
11
|
+
attr_reader :size
|
12
|
+
|
13
|
+
def initialize(size)
|
14
|
+
@size = size
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
"fixed(#{size})"
|
19
|
+
end
|
20
|
+
|
21
|
+
def value_classes
|
22
|
+
VALUE_CLASSES
|
23
|
+
end
|
24
|
+
|
25
|
+
def coerce(input)
|
26
|
+
if coercible?(input)
|
27
|
+
input
|
28
|
+
else
|
29
|
+
raise ArgumentError.new("Could not coerce '#{input.inspect}' to #{name}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def coercible?(input)
|
34
|
+
input.nil? || (input.is_a?(::String) && input.length == size)
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :coerced?, :coercible?
|
38
|
+
|
39
|
+
def serialize(value, **)
|
40
|
+
value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|