lutaml-model 0.6.7 → 0.7.1

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos-todo.json +7 -0
  3. data/.github/workflows/dependent-repos.json +17 -9
  4. data/.rubocop_todo.yml +18 -33
  5. data/README.adoc +4380 -2557
  6. data/lib/lutaml/model/attribute.rb +94 -15
  7. data/lib/lutaml/model/choice.rb +7 -0
  8. data/lib/lutaml/model/comparable_model.rb +48 -9
  9. data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
  10. data/lib/lutaml/model/error/polymorphic_error.rb +9 -0
  11. data/lib/lutaml/model/error.rb +1 -0
  12. data/lib/lutaml/model/mapping/json_mapping.rb +17 -0
  13. data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +58 -14
  14. data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
  15. data/lib/lutaml/model/mapping/mapping_rule.rb +299 -0
  16. data/lib/lutaml/model/mapping/toml_mapping.rb +25 -0
  17. data/lib/lutaml/model/{xml_mapping.rb → mapping/xml_mapping.rb} +97 -15
  18. data/lib/lutaml/model/{xml_mapping_rule.rb → mapping/xml_mapping_rule.rb} +20 -3
  19. data/lib/lutaml/model/mapping/yaml_mapping.rb +17 -0
  20. data/lib/lutaml/model/mapping.rb +14 -0
  21. data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
  22. data/lib/lutaml/model/sequence.rb +2 -2
  23. data/lib/lutaml/model/serialize.rb +247 -97
  24. data/lib/lutaml/model/type/date.rb +1 -1
  25. data/lib/lutaml/model/type/date_time.rb +2 -2
  26. data/lib/lutaml/model/type/hash.rb +1 -1
  27. data/lib/lutaml/model/type/time.rb +2 -2
  28. data/lib/lutaml/model/type/time_without_date.rb +2 -2
  29. data/lib/lutaml/model/uninitialized_class.rb +64 -0
  30. data/lib/lutaml/model/utils.rb +14 -0
  31. data/lib/lutaml/model/validation.rb +1 -0
  32. data/lib/lutaml/model/version.rb +1 -1
  33. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  34. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  35. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  36. data/lib/lutaml/model/xml_adapter/xml_document.rb +38 -17
  37. data/lib/lutaml/model/xml_adapter/xml_element.rb +17 -7
  38. data/lib/lutaml/model.rb +1 -0
  39. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +3 -3
  40. data/spec/fixtures/person.rb +5 -5
  41. data/spec/lutaml/model/attribute_spec.rb +37 -1
  42. data/spec/lutaml/model/cdata_spec.rb +2 -2
  43. data/spec/lutaml/model/collection_spec.rb +50 -2
  44. data/spec/lutaml/model/comparable_model_spec.rb +92 -27
  45. data/spec/lutaml/model/defaults_spec.rb +1 -1
  46. data/spec/lutaml/model/enum_spec.rb +1 -1
  47. data/spec/lutaml/model/group_spec.rb +316 -14
  48. data/spec/lutaml/model/key_value_mapping_spec.rb +41 -3
  49. data/spec/lutaml/model/polymorphic_spec.rb +348 -0
  50. data/spec/lutaml/model/render_empty_spec.rb +194 -0
  51. data/spec/lutaml/model/render_nil_spec.rb +206 -22
  52. data/spec/lutaml/model/simple_model_spec.rb +9 -9
  53. data/spec/lutaml/model/value_map_spec.rb +240 -0
  54. data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
  55. data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
  56. data/spec/lutaml/model/xml_mapping_rule_spec.rb +102 -2
  57. data/spec/lutaml/model/xml_mapping_spec.rb +45 -3
  58. data/spec/sample_model_spec.rb +3 -3
  59. metadata +20 -8
  60. data/lib/lutaml/model/mapping_rule.rb +0 -109
@@ -3,10 +3,8 @@ require_relative "xml_adapter"
3
3
  require_relative "config"
4
4
  require_relative "type"
5
5
  require_relative "attribute"
6
- require_relative "mapping_rule"
7
6
  require_relative "mapping_hash"
8
- require_relative "xml_mapping"
9
- require_relative "key_value_mapping"
7
+ require_relative "mapping"
10
8
  require_relative "json_adapter"
11
9
  require_relative "comparable_model"
12
10
  require_relative "schema_location"
@@ -94,22 +92,15 @@ module Lutaml
94
92
  end
95
93
  end
96
94
 
97
- # Define an attribute for the model
98
- def attribute(name, type, options = {})
99
- if type.is_a?(Hash)
100
- options[:method_name] = type[:method]
101
- type = nil
102
- end
103
-
104
- attr = Attribute.new(name, type, options)
105
- attributes[name] = attr
95
+ def define_attribute_methods(attr)
96
+ name = attr.name
106
97
 
107
98
  if attr.enum?
108
99
  add_enum_methods_to_model(
109
100
  model,
110
101
  name,
111
- options[:values],
112
- collection: options[:collection],
102
+ attr.options[:values],
103
+ collection: attr.options[:collection],
113
104
  )
114
105
  elsif attr.derived? && name != attr.method_name
115
106
  define_method(name) do
@@ -119,35 +110,74 @@ module Lutaml
119
110
  define_method(name) do
120
111
  instance_variable_get(:"@#{name}")
121
112
  end
122
-
123
113
  define_method(:"#{name}=") do |value|
124
114
  value_set_for(name)
125
115
  instance_variable_set(:"@#{name}", attr.cast_value(value))
126
116
  end
127
117
  end
118
+ end
119
+
120
+ # Define an attribute for the model
121
+ def attribute(name, type, options = {})
122
+ if type.is_a?(Hash)
123
+ options[:method_name] = type[:method]
124
+ type = nil
125
+ end
126
+
127
+ attr = Attribute.new(name, type, options)
128
+ attributes[name] = attr
129
+ define_attribute_methods(attr)
128
130
 
129
131
  attr
130
132
  end
131
133
 
132
134
  def root?
133
- mappings_for(:xml).root?
135
+ mappings_for(:xml)&.root?
136
+ end
137
+
138
+ def import_model_with_root_error(model)
139
+ return unless model.mappings.key?(:xml) && model.root?
140
+
141
+ raise Lutaml::Model::ImportModelWithRootError.new(model)
134
142
  end
135
143
 
136
144
  def import_model_attributes(model)
137
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
145
+ model.attributes.each_value do |attr|
146
+ define_attribute_methods(attr)
147
+ end
138
148
 
139
- @attributes.merge!(model.attributes)
149
+ @choice_attributes.concat(Utils.deep_dup(model.choice_attributes))
150
+ @attributes.merge!(Utils.deep_dup(model.attributes))
140
151
  end
141
152
 
142
153
  def import_model_mappings(model)
143
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
154
+ import_model_with_root_error(model)
155
+
156
+ Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
157
+ next unless model.mappings.key?(format)
158
+
159
+ mapping = model.mappings_for(format)
160
+ mapping = Utils.deep_dup(mapping)
161
+
162
+ @mappings[format] ||= format == :xml ? XmlMapping.new : KeyValueMapping.new
163
+
164
+ if format == :xml
165
+ @mappings[format].merge_mapping_attributes(mapping)
166
+ @mappings[format].merge_mapping_elements(mapping)
167
+ @mappings[format].merge_elements_sequence(mapping)
168
+ else
169
+ @mappings[format].mappings.concat(mapping.mappings)
170
+ end
171
+ end
172
+ end
144
173
 
145
- @mappings.merge!(model.mappings)
174
+ def handle_key_value_mappings(mapping, format)
175
+ @mappings[format] ||= KeyValueMapping.new
176
+ @mappings[format].mappings.concat(mapping.mappings)
146
177
  end
147
178
 
148
179
  def import_model(model)
149
- raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
150
-
180
+ import_model_with_root_error(model)
151
181
  import_model_attributes(model)
152
182
  import_model_mappings(model)
153
183
  end
@@ -213,7 +243,8 @@ module Lutaml
213
243
 
214
244
  def add_enum_setter_if_not_defined(klass, enum_name, _values, collection)
215
245
  Utils.add_method_if_not_defined(klass, "#{enum_name}=") do |value|
216
- value = [value] unless value.is_a?(Array)
246
+ value = [] if value.nil?
247
+ value = [value] if !value.is_a?(Array)
217
248
 
218
249
  value_set_for(enum_name)
219
250
 
@@ -233,8 +264,8 @@ module Lutaml
233
264
 
234
265
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
235
266
  define_method(format) do |&block|
236
- klass = format == :xml ? XmlMapping : KeyValueMapping
237
- mappings[format] ||= klass.new
267
+ mapping_class = const_get("Lutaml::Model::#{format.to_s.capitalize}Mapping")
268
+ mappings[format] ||= mapping_class.new
238
269
  mappings[format].instance_eval(&block)
239
270
 
240
271
  if format == :xml && !mappings[format].root_element && !mappings[format].no_root?
@@ -243,6 +274,8 @@ module Lutaml
243
274
  end
244
275
 
245
276
  define_method(:"from_#{format}") do |data, options = {}|
277
+ return data if Utils.uninitialized?(data)
278
+
246
279
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
247
280
 
248
281
  doc = adapter.parse(data, options)
@@ -260,7 +293,7 @@ module Lutaml
260
293
  options[:encoding] = doc.encoding
261
294
  apply_mappings(doc, format, options)
262
295
  else
263
- apply_mappings(doc.to_h, format)
296
+ apply_mappings(doc.to_h, format, options)
264
297
  end
265
298
  end
266
299
 
@@ -298,7 +331,7 @@ module Lutaml
298
331
 
299
332
  def key_value(&block)
300
333
  Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
301
- mappings[format] ||= KeyValueMapping.new
334
+ mappings[format] ||= KeyValueMapping.new(format)
302
335
  mappings[format].instance_eval(&block)
303
336
  end
304
337
  end
@@ -310,43 +343,68 @@ module Lutaml
310
343
 
311
344
  mappings.each_with_object({}) do |rule, hash|
312
345
  name = rule.to
313
- next if except&.include?(name) || (only && !only.include?(name))
314
- next if !rule.custom_methods[:to] && (!rule.render_default? && instance.using_default?(rule.to))
346
+ attr = attributes[name]
315
347
 
348
+ next if except&.include?(name) || (only && !only.include?(name))
316
349
  next handle_delegate(instance, rule, hash, format) if rule.delegate
317
350
 
318
351
  if rule.custom_methods[:to]
319
352
  next instance.send(rule.custom_methods[:to], instance, hash)
320
353
  end
321
354
 
322
- value = instance.send(name)
355
+ value = rule.serialize(instance)
323
356
 
324
357
  if rule.raw_mapping?
325
358
  adapter = Lutaml::Model::Config.send(:"#{format}_adapter")
326
359
  return adapter.parse(value, options)
327
360
  end
328
361
 
329
- attribute = attributes[name]
330
-
331
- if export_method = rule.transform[:export] || attribute.transform_export_method
362
+ if export_method = rule.transform[:export] || attr.transform_export_method
332
363
  value = export_method.call(value)
333
364
  end
334
365
 
335
- next hash.merge!(generate_hash_from_child_mappings(attribute, value, format, rule.root_mappings)) if rule.root_mapping?
366
+ next hash.merge!(generate_hash_from_child_mappings(attr, value, format, rule.root_mappings)) if rule.root_mapping?
336
367
 
337
368
  value = if rule.child_mappings
338
- generate_hash_from_child_mappings(attribute, value, format, rule.child_mappings)
369
+ generate_hash_from_child_mappings(attr, value, format, rule.child_mappings)
339
370
  else
340
- attribute.serialize(value, format, options)
371
+ attr.serialize(value, format, options)
341
372
  end
342
373
 
343
- next unless rule.render?(value)
374
+ next if !rule.render?(value, instance, options)
375
+
376
+ value = apply_value_map(value, rule.value_map(:to, options), attr)
344
377
 
345
378
  rule_from_name = rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
346
379
  hash[rule_from_name] = value
347
380
  end
348
381
  end
349
382
 
383
+ def apply_value_map(value, value_map, attr)
384
+ if value.nil?
385
+ value_for_option(value_map[:nil], attr)
386
+ elsif Utils.empty?(value)
387
+ value_for_option(value_map[:empty], attr, value)
388
+ elsif Utils.uninitialized?(value)
389
+ value_for_option(value_map[:omitted], attr)
390
+ else
391
+ value
392
+ end
393
+ end
394
+
395
+ def value_for_option(option, attr, empty_value = nil)
396
+ return nil if option == :nil
397
+ return empty_value || empty_object(attr) if option == :empty
398
+
399
+ Lutaml::Model::UninitializedClass.instance
400
+ end
401
+
402
+ def empty_object(attr)
403
+ return [] if attr.collection?
404
+
405
+ ""
406
+ end
407
+
350
408
  def handle_delegate(instance, rule, hash, format)
351
409
  name = rule.to
352
410
  value = instance.send(rule.delegate).send(name)
@@ -362,9 +420,9 @@ module Lutaml
362
420
  end
363
421
 
364
422
  def default_mappings(format)
365
- klass = format == :xml ? XmlMapping : KeyValueMapping
423
+ _mappings = format == :xml ? XmlMapping.new : KeyValueMapping.new(format)
366
424
 
367
- klass.new.tap do |mapping|
425
+ _mappings.tap do |mapping|
368
426
  attributes&.each_key do |name|
369
427
  mapping.map_element(
370
428
  name.to_s,
@@ -441,7 +499,7 @@ module Lutaml
441
499
  attr_value
442
500
  end
443
501
 
444
- next unless mapping_rule&.render?(attr_value)
502
+ next unless mapping_rule&.render?(attr_value, nil)
445
503
 
446
504
  if path == :key
447
505
  map_key = attr_value
@@ -484,46 +542,43 @@ module Lutaml
484
542
  instance = options[:instance] || model.new
485
543
  return instance if Utils.blank?(doc)
486
544
 
487
- options[:mappings] = mappings_for(format).mappings
545
+ mappings = mappings_for(format)
488
546
 
547
+ if mappings.polymorphic_mapping
548
+ return resolve_polymorphic(doc, format, mappings, instance, options)
549
+ end
550
+
551
+ options[:mappings] = mappings.mappings
489
552
  return apply_xml_mapping(doc, instance, options) if format == :xml
490
553
 
491
554
  apply_hash_mapping(doc, instance, format, options)
492
555
  end
493
556
 
557
+ def resolve_polymorphic(doc, format, mappings, instance, options = {})
558
+ polymorphic_mapping = mappings.polymorphic_mapping
559
+ return instance if polymorphic_mapping.polymorphic_map.empty?
560
+
561
+ klass_key = doc[polymorphic_mapping.name]
562
+ klass_name = polymorphic_mapping.polymorphic_map[klass_key]
563
+ klass = Object.const_get(klass_name)
564
+
565
+ klass.apply_mappings(doc, format, options)
566
+ end
567
+
494
568
  def apply_xml_mapping(doc, instance, options = {})
495
- options = Utils.deep_dup(options)
569
+ options = prepare_options(options)
496
570
  instance.encoding = options[:encoding]
497
571
  return instance unless doc
498
572
 
499
- if options[:default_namespace].nil?
500
- options[:default_namespace] = mappings_for(:xml)&.namespace_uri
501
- end
502
573
  mappings = options[:mappings] || mappings_for(:xml).mappings
503
574
 
504
- raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class]) if doc.is_a?(Array)
575
+ validate_document!(doc, options)
505
576
 
506
- doc_order = doc.root.order
507
- if instance.respond_to?(:ordered=)
508
- instance.element_order = doc_order
509
- instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
510
- instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
511
- end
512
-
513
- schema_location = doc.attributes.values.find do |a|
514
- a.unprefixed_name == "schemaLocation"
515
- end
516
-
517
- if !schema_location.nil?
518
- instance.schema_location = Lutaml::Model::SchemaLocation.new(
519
- schema_location: schema_location.value,
520
- prefix: schema_location.namespace_prefix,
521
- namespace: schema_location.namespace,
522
- )
523
- end
577
+ set_instance_ordering(instance, doc, options)
578
+ set_schema_location(instance, doc)
524
579
 
525
580
  defaults_used = []
526
- validate_sequence!(doc_order)
581
+ validate_sequence!(doc.root.order)
527
582
 
528
583
  mappings.each do |rule|
529
584
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
@@ -531,26 +586,76 @@ module Lutaml
531
586
  attr = attribute_for_rule(rule)
532
587
  next if attr&.derived?
533
588
 
589
+ new_opts = options.dup
590
+ if rule.namespace_set?
591
+ new_opts[:default_namespace] = rule.namespace
592
+ end
593
+
534
594
  value = if rule.raw_mapping?
535
595
  doc.root.inner_xml
536
596
  elsif rule.content_mapping?
537
597
  rule.cdata ? doc.cdata : doc.text
538
- elsif val = value_for_rule(doc, rule, options, instance)
539
- val
540
- elsif instance.using_default?(rule.to) || rule.render_default
541
- defaults_used << rule.to
542
- attr&.default || rule.to_value_for(instance)
598
+ else
599
+ val = value_for_rule(doc, rule, new_opts, instance)
600
+
601
+ if (Utils.uninitialized?(val) || val.nil?) && (instance.using_default?(rule.to) || rule.render_default)
602
+ defaults_used << rule.to
603
+ attr&.default || rule.to_value_for(instance)
604
+ else
605
+ val
606
+ end
543
607
  end
544
608
 
545
- value = normalize_xml_value(value, rule, attr, options)
609
+ value = apply_value_map(value, rule.value_map(:from, new_opts), attr)
610
+ value = normalize_xml_value(value, rule, attr, new_opts)
546
611
  rule.deserialize(instance, value, attributes, self)
547
612
  end
548
613
 
549
- defaults_used.each { |attr_name| instance.using_default_for(attr_name) }
614
+ defaults_used.each do |attr_name|
615
+ instance.using_default_for(attr_name)
616
+ end
550
617
 
551
618
  instance
552
619
  end
553
620
 
621
+ def prepare_options(options)
622
+ opts = Utils.deep_dup(options)
623
+ opts[:default_namespace] ||= mappings_for(:xml)&.namespace_uri
624
+
625
+ opts
626
+ end
627
+
628
+ def validate_document!(doc, options)
629
+ return unless doc.is_a?(Array)
630
+
631
+ raise Lutaml::Model::CollectionTrueMissingError(
632
+ self,
633
+ options[:caller_class],
634
+ )
635
+ end
636
+
637
+ def set_instance_ordering(instance, doc, options)
638
+ return unless instance.respond_to?(:ordered=)
639
+
640
+ instance.element_order = doc.root.order
641
+ instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
642
+ instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
643
+ end
644
+
645
+ def set_schema_location(instance, doc)
646
+ schema_location = doc.attributes.values.find do |a|
647
+ a.unprefixed_name == "schemaLocation"
648
+ end
649
+
650
+ return if schema_location.nil?
651
+
652
+ instance.schema_location = Lutaml::Model::SchemaLocation.new(
653
+ schema_location: schema_location.value,
654
+ prefix: schema_location.namespace_prefix,
655
+ namespace: schema_location.namespace,
656
+ )
657
+ end
658
+
554
659
  def value_for_rule(doc, rule, options, instance)
555
660
  rule_names = rule.namespaced_names(options[:default_namespace])
556
661
 
@@ -567,40 +672,69 @@ module Lutaml
567
672
  return return_child ? children.first : children
568
673
  end
569
674
 
675
+ return handle_cdata(children) if rule.cdata
676
+
677
+ values = []
678
+
570
679
  if Utils.present?(children)
571
680
  instance.value_set_for(attr.name)
681
+ else
682
+ children = nil
683
+ values = Lutaml::Model::UninitializedClass.instance
572
684
  end
573
685
 
574
- if rule.cdata
575
- values = children.map do |child|
576
- child.cdata_children&.map(&:text)
577
- end.flatten
578
- return children.count > 1 ? values : values.first
579
- end
580
-
581
- values = children.map do |child|
686
+ children&.each do |child|
582
687
  if !rule.using_custom_methods? && attr.type <= Serialize
583
- attr.cast(child, :xml, options.except(:mappings))
688
+ cast_options = options.except(:mappings)
689
+ cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
690
+
691
+ values << attr.cast(child, :xml, cast_options)
584
692
  elsif attr.raw?
585
- inner_xml_of(child)
693
+ values << inner_xml_of(child)
586
694
  else
587
- child&.children&.first&.text
695
+ return nil if rule.render_nil_as_nil? && child.nil_element?
696
+
697
+ text = child.nil_element? ? nil : (child&.text&.+ child&.cdata)
698
+ values << text
588
699
  end
589
700
  end
590
- attr&.collection? ? values : values.first
701
+
702
+ normalized_value_for_attr(values, attr)
591
703
  end
592
704
  end
593
705
 
706
+ def handle_cdata(children)
707
+ values = children.map do |child|
708
+ child.cdata_children&.map(&:text)
709
+ end.flatten
710
+
711
+ children.count > 1 ? values : values.first
712
+ end
713
+
714
+ def normalized_value_for_attr(values, attr)
715
+ # for xml collection true cases like
716
+ # <store><items /></store>
717
+ # <store><items xsi:nil="true"/></store>
718
+ # <store><items></items></store>
719
+ #
720
+ # these are considered empty collection
721
+ return [] if attr&.collection? && [[nil], [""]].include?(values)
722
+ return values if attr&.collection?
723
+
724
+ values.is_a?(Array) ? values.first : values
725
+ end
726
+
594
727
  def apply_hash_mapping(doc, instance, format, options = {})
595
728
  mappings = options[:mappings] || mappings_for(format).mappings
596
729
  mappings.each do |rule|
597
730
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
598
731
 
599
732
  attr = attribute_for_rule(rule)
733
+ next if attr&.derived?
600
734
 
601
735
  names = rule.multiple_mappings? ? rule.name : [rule.name]
602
736
 
603
- value = names.collect do |rule_name|
737
+ values = names.collect do |rule_name|
604
738
  if rule.root_mapping?
605
739
  doc
606
740
  elsif rule.raw_mapping?
@@ -610,10 +744,16 @@ module Lutaml
610
744
  doc[rule_name.to_s]
611
745
  elsif doc.key?(rule_name.to_sym)
612
746
  doc[rule_name.to_sym]
613
- else
747
+ elsif attr&.default_set?
614
748
  attr&.default
749
+ else
750
+ Lutaml::Model::UninitializedClass.instance
615
751
  end
616
- end.compact.first
752
+ end.compact
753
+
754
+ value = values.find { |v| Utils.initialized?(v) } || values.first
755
+
756
+ value = apply_value_map(value, rule.value_map(:from, options), attr)
617
757
 
618
758
  if rule.using_custom_methods?
619
759
  if Utils.present?(value)
@@ -624,7 +764,11 @@ module Lutaml
624
764
  end
625
765
 
626
766
  value = translate_mappings(value, rule.hash_mappings, attr, format)
627
- value = attr.cast(value, format) unless rule.hash_mappings
767
+
768
+ cast_options = {}
769
+ cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
770
+
771
+ value = attr.cast(value, format, cast_options) unless rule.hash_mappings
628
772
  attr.valid_collection!(value, self)
629
773
 
630
774
  rule.deserialize(instance, value, attributes, self)
@@ -634,7 +778,7 @@ module Lutaml
634
778
  end
635
779
 
636
780
  def normalize_xml_value(value, rule, attr, options = {})
637
- value = [value].compact if attr&.collection? && !value.is_a?(Array)
781
+ value = [value].compact if attr&.collection? && !value.is_a?(Array) && !value.nil?
638
782
 
639
783
  return value unless cast_value?(attr, rule)
640
784
 
@@ -702,7 +846,7 @@ module Lutaml
702
846
  attr_accessor :element_order, :schema_location, :encoding
703
847
  attr_writer :ordered, :mixed
704
848
 
705
- def initialize(attrs = {})
849
+ def initialize(attrs = {}, options = {})
706
850
  @using_default = {}
707
851
 
708
852
  return unless self.class.attributes
@@ -721,22 +865,28 @@ module Lutaml
721
865
 
722
866
  value = if attrs.key?(name) || attrs.key?(name.to_s)
723
867
  attr_value(attrs, name, attr)
724
- else
868
+ elsif attr.default_set?
725
869
  using_default_for(name)
726
870
  attr.default
871
+ else
872
+ Lutaml::Model::UninitializedClass.instance
727
873
  end
728
874
 
729
- # Initialize collections with an empty array if no value is provided
730
- if attr.collection? && value.nil?
731
- value = []
732
- end
733
-
734
875
  default = using_default?(name)
876
+ value = self.class.apply_value_map(value, value_map(options), attr)
735
877
  public_send(:"#{name}=", self.class.ensure_utf8(value))
736
878
  using_default_for(name) if default
737
879
  end
738
880
  end
739
881
 
882
+ def value_map(options)
883
+ {
884
+ omitted: options[:omitted] || :nil,
885
+ nil: options[:nil] || :nil,
886
+ empty: options[:empty] || :empty,
887
+ }
888
+ end
889
+
740
890
  def attr_value(attrs, name, attr_rule)
741
891
  value = if attrs.key?(name.to_sym)
742
892
  attrs[name.to_sym]
@@ -747,7 +897,7 @@ module Lutaml
747
897
  end
748
898
 
749
899
  if attr_rule.collection? || value.is_a?(Array)
750
- (value || []).map do |v|
900
+ value&.map do |v|
751
901
  if v.is_a?(Hash)
752
902
  attr_rule.type.new(v)
753
903
  else
@@ -3,7 +3,7 @@ module Lutaml
3
3
  module Type
4
4
  class Date < Value
5
5
  def self.cast(value)
6
- return nil if value.nil?
6
+ return value if value.nil? || Utils.uninitialized?(value)
7
7
 
8
8
  case value
9
9
  when ::DateTime, ::Time
@@ -6,7 +6,7 @@ module Lutaml
6
6
  # Date and time representation
7
7
  class DateTime < Value
8
8
  def self.cast(value)
9
- return nil if value.nil?
9
+ return value if value.nil? || Utils.uninitialized?(value)
10
10
 
11
11
  case value
12
12
  when ::DateTime then value
@@ -18,7 +18,7 @@ module Lutaml
18
18
  end
19
19
 
20
20
  def self.serialize(value)
21
- return nil if value.nil?
21
+ return value if value.nil? || Utils.uninitialized?(value)
22
22
 
23
23
  cast(value)&.iso8601
24
24
  end
@@ -3,7 +3,7 @@ module Lutaml
3
3
  module Type
4
4
  class Hash < Value
5
5
  def self.cast(value)
6
- return nil if value.nil?
6
+ return value if value.nil? || Utils.uninitialized?(value)
7
7
 
8
8
  hash = if value.respond_to?(:to_h)
9
9
  value.to_h
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Type
6
6
  class Time < Value
7
7
  def self.cast(value)
8
- return nil if value.nil?
8
+ return value if value.nil? || Utils.uninitialized?(value)
9
9
 
10
10
  case value
11
11
  when ::Time then value
@@ -17,7 +17,7 @@ module Lutaml
17
17
  end
18
18
 
19
19
  def self.serialize(value)
20
- return nil if value.nil?
20
+ return value if value.nil? || Utils.uninitialized?(value)
21
21
 
22
22
  value = cast(value)
23
23
  # value&.strftime("%Y-%m-%dT%H:%M:%S%:z")
@@ -11,7 +11,7 @@ module Lutaml
11
11
  # ::Time.new(1, 1, 1, time.hour, time.min, time.sec)
12
12
 
13
13
  def self.cast(value)
14
- return nil if value.nil?
14
+ return value if value.nil? || Utils.uninitialized?(value)
15
15
 
16
16
  case value
17
17
  when ::Time then value
@@ -22,7 +22,7 @@ module Lutaml
22
22
  end
23
23
 
24
24
  def self.serialize(value)
25
- return nil if value.nil?
25
+ return value if value.nil? || Utils.uninitialized?(value)
26
26
 
27
27
  value = cast(value)
28
28
  value.strftime("%H:%M:%S") # Format as HH:MM:SS