Module: Crystalline::MetadataFields::ClassMethods
- Defined in:
- lib/crystalline/metadata_fields.rb
Constant Summary collapse
- UNRESOLVED_UNION =
Sentinel returned when a union value cannot be resolved to any member type, so the caller can leave the field unset (instead of assigning nil).
Object.new
Instance Method Summary collapse
- #field(field_name, type, metadata = {}) ⇒ Object
- #field_augmented? ⇒ Boolean
- #fields ⇒ Object
- #from_dict(d) ⇒ Object
-
#unmarshal_field_value(field, field_type, value, format_metadata) ⇒ Object
Dispatches unmarshalling of a single field value based on its declared type.
- #unmarshal_single(field_type, value, format_metadata = nil) ⇒ Object
-
#unmarshal_union(field, field_type, value) ⇒ Object
Resolves a union-typed value, using a discriminator when present and otherwise trying each candidate type in turn.
Instance Method Details
#field(field_name, type, metadata = {}) ⇒ Object
27 28 29 30 31 |
# File 'lib/crystalline/metadata_fields.rb', line 27 def field(field_name, type, = {}) attr_accessor field_name fields << Field.new(field_name, type, ) end |
#field_augmented? ⇒ Boolean
33 34 35 |
# File 'lib/crystalline/metadata_fields.rb', line 33 def field_augmented? true end |
#fields ⇒ Object
21 22 23 24 25 |
# File 'lib/crystalline/metadata_fields.rb', line 21 def fields @__fields__ = [] if @__fields__.nil? @__fields__ end |
#from_dict(d) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/crystalline/metadata_fields.rb', line 68 def from_dict(d) to_build = {} fields.each do |field| key = field.name = field..fetch(:format_json, {}) lookup = .fetch(:letter_case, nil).call value = d[lookup] field_type = field.type if ::Crystalline::Utils.nilable? field_type if value == 'null' to_build[key] = nil next end field_type = ::Crystalline::Utils.nilable_of(field_type) end # If field is not nilable, and the value is not in the dict, raise a KeyError raise KeyError, "key #{lookup} not found in hash" if value.nil? && !::Crystalline::Utils.nilable?(field.type) # If field is nilable, and the value is not in the dict, just move to the next field next if value.nil? result = unmarshal_field_value(field, field_type, value, ) to_build[key] = result unless result.equal?(UNRESOLVED_UNION) end new(**to_build) end |
#unmarshal_field_value(field, field_type, value, format_metadata) ⇒ Object
Dispatches unmarshalling of a single field value based on its declared type.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/crystalline/metadata_fields.rb', line 101 def unmarshal_field_value(field, field_type, value, ) if Crystalline::Utils.arr? field_type inner_type = Crystalline::Utils.arr_of(field_type) value.map { |f| unmarshal_single(inner_type, f, ) } elsif Crystalline::Utils.hash? field_type val_type = Crystalline::Utils.hash_of(field_type) # rubocop:disable Style/HashTransformValues value.map { |k, v| [k, unmarshal_single(val_type, v, )] }.to_h # rubocop:enable Style/HashTransformValues elsif Crystalline::Utils.union? field_type unmarshal_union(field, field_type, value) elsif field_type.instance_of?(Class) && field_type.include?(::Crystalline::MetadataFields) Crystalline.unmarshal_json(value, field_type) else unmarshal_single(field_type, value, ) end end |
#unmarshal_single(field_type, value, format_metadata = nil) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/crystalline/metadata_fields.rb', line 37 def unmarshal_single(field_type, value, = nil) decoder = .fetch(:decoder, nil) if field_type.instance_of?(Class) && field_type.include?(::Crystalline::MetadataFields) return field_type.from_dict(value) elsif field_type.to_s == 'Date' return Date.parse(value) elsif field_type.to_s == 'DateTime' return DateTime.parse(value) elsif field_type.to_s == 'Object' # rubocop:disable Lint/SuppressedException begin value = JSON.parse(value) rescue TypeError, JSON::ParserError # Not valid JSON; keep the original value unchanged. end # rubocop:enable Lint/SuppressedException return value elsif field_type.to_s == 'Float' raise TypeError, "Cannot convert #{value.inspect} to Float. The value is of type #{value.class}" unless value.is_a?(::Numeric) return value.to_f end if decoder.nil? value else decoder.call(value) end end |
#unmarshal_union(field, field_type, value) ⇒ Object
Resolves a union-typed value, using a discriminator when present and otherwise trying each candidate type in turn. Returns UNRESOLVED_UNION if none match.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/crystalline/metadata_fields.rb', line 121 def unmarshal_union(field, field_type, value) discriminator = field..fetch(:discriminator, nil) unless discriminator.nil? discriminator_mapping = field..fetch(:discriminator_mapping, nil) discriminator_value = value.fetch(discriminator) type_to_deserialize = if discriminator_mapping.nil? # Fallback: try to match discriminator value against type name Crystalline::Utils.get_union_types(field_type).find { |t| t.name.split('::').last == discriminator_value } else # Use explicit mapping from discriminator value to type discriminator_mapping[discriminator_value] end return Crystalline.unmarshal_json(value, type_to_deserialize) end union_types = Crystalline::Utils.get_union_types(field_type) union_types = union_types.sort_by { |klass| Crystalline.non_nilable_attr_count(klass) } union_types.each do |union_type| return Crystalline.unmarshal_json(value, union_type) rescue TypeError, NoMethodError, KeyError next end UNRESOLVED_UNION end |