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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/Appraisals +2 -0
  4. data/CHANGELOG.md +15 -0
  5. data/Gemfile +2 -0
  6. data/README.md +57 -25
  7. data/Rakefile +2 -0
  8. data/avromatic.gemspec +7 -3
  9. data/lib/avromatic/io/datum_reader.rb +2 -0
  10. data/lib/avromatic/io/datum_writer.rb +7 -1
  11. data/lib/avromatic/io.rb +4 -2
  12. data/lib/avromatic/messaging.rb +2 -0
  13. data/lib/avromatic/model/attributes.rb +112 -158
  14. data/lib/avromatic/model/builder.rb +4 -7
  15. data/lib/avromatic/model/coercion_error.rb +8 -0
  16. data/lib/avromatic/model/configurable.rb +2 -0
  17. data/lib/avromatic/model/configuration.rb +2 -0
  18. data/lib/avromatic/model/{custom_type.rb → custom_type_configuration.rb} +2 -2
  19. data/lib/avromatic/model/{type_registry.rb → custom_type_registry.rb} +15 -18
  20. data/lib/avromatic/model/field_helper.rb +20 -0
  21. data/lib/avromatic/model/message_decoder.rb +2 -0
  22. data/lib/avromatic/model/messaging_serialization.rb +2 -0
  23. data/lib/avromatic/model/nested_models.rb +2 -17
  24. data/lib/avromatic/model/raw_serialization.rb +21 -84
  25. data/lib/avromatic/model/types/abstract_timestamp_type.rb +57 -0
  26. data/lib/avromatic/model/types/abstract_type.rb +37 -0
  27. data/lib/avromatic/model/types/array_type.rb +53 -0
  28. data/lib/avromatic/model/types/boolean_type.rb +39 -0
  29. data/lib/avromatic/model/types/custom_type.rb +64 -0
  30. data/lib/avromatic/model/types/date_type.rb +41 -0
  31. data/lib/avromatic/model/types/enum_type.rb +56 -0
  32. data/lib/avromatic/model/types/fixed_type.rb +45 -0
  33. data/lib/avromatic/model/types/float_type.rb +48 -0
  34. data/lib/avromatic/model/types/integer_type.rb +39 -0
  35. data/lib/avromatic/model/types/map_type.rb +74 -0
  36. data/lib/avromatic/model/types/null_type.rb +39 -0
  37. data/lib/avromatic/model/types/record_type.rb +57 -0
  38. data/lib/avromatic/model/types/string_type.rb +48 -0
  39. data/lib/avromatic/model/types/timestamp_micros_type.rb +32 -0
  40. data/lib/avromatic/model/types/timestamp_millis_type.rb +32 -0
  41. data/lib/avromatic/model/types/type_factory.rb +118 -0
  42. data/lib/avromatic/model/types/union_type.rb +87 -0
  43. data/lib/avromatic/model/unknown_attribute_error.rb +15 -0
  44. data/lib/avromatic/model/validation.rb +57 -36
  45. data/lib/avromatic/model/validation_error.rb +8 -0
  46. data/lib/avromatic/model/value_object.rb +2 -0
  47. data/lib/avromatic/model.rb +6 -2
  48. data/lib/avromatic/model_registry.rb +2 -0
  49. data/lib/avromatic/patches/schema_validator_patch.rb +2 -0
  50. data/lib/avromatic/patches.rb +2 -0
  51. data/lib/avromatic/railtie.rb +2 -0
  52. data/lib/avromatic/rspec.rb +2 -0
  53. data/lib/avromatic/version.rb +3 -1
  54. data/lib/avromatic.rb +9 -4
  55. metadata +32 -21
  56. data/lib/avromatic/model/allowed_type_validator.rb +0 -7
  57. data/lib/avromatic/model/allowed_writer_methods_memoization.rb +0 -16
  58. data/lib/avromatic/model/attribute/abstract_timestamp.rb +0 -26
  59. data/lib/avromatic/model/attribute/record.rb +0 -26
  60. data/lib/avromatic/model/attribute/timestamp_micros.rb +0 -26
  61. data/lib/avromatic/model/attribute/timestamp_millis.rb +0 -26
  62. data/lib/avromatic/model/attribute/union.rb +0 -66
  63. data/lib/avromatic/model/attribute_type/union.rb +0 -29
  64. data/lib/avromatic/model/logical_types.rb +0 -19
  65. data/lib/avromatic/model/null_custom_type.rb +0 -21
  66. data/lib/avromatic/model/passthrough_serializer.rb +0 -10
@@ -1,8 +1,10 @@
1
- require 'avromatic/model/custom_type'
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 TypeRegistry
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::CustomType.new(value_class).tap do |type|
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. If there is no custom type, then NullCustomType is returned.
26
- # @field_class [Object] Value class that has been determined for a field.
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
- if field_class && field_type.type_sym == :union && !union_attribute?(field_class)
31
- field_type = Avromatic::Model::Attributes.first_union_schema(field_type)
32
- end
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, NullCustomType)
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
  module Avromatic
2
4
  module Model
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Avromatic
2
4
  module Model
3
5
 
@@ -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
- require 'avromatic/model/passthrough_serializer'
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 :avro_serializer, :datum_writer, :datum_reader, :attribute_set,
15
- to: :class
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
- EMPTY_ARRAY = [].freeze
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] = self.class.recursive_serialize(value, name: key, strict: strict)
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