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
66 67 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 |
# File 'lib/crystalline/metadata_fields.rb', line 66 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.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/crystalline/metadata_fields.rb', line 99 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 |
# 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' 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.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/crystalline/metadata_fields.rb', line 119 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 |