lutaml-model 0.7.1 → 0.7.7

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 (259) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/dependent-tests.yml +4 -0
  4. data/.github/workflows/rake.yml +12 -0
  5. data/.github/workflows/release.yml +3 -0
  6. data/.gitignore +6 -1
  7. data/.irbrc +1 -0
  8. data/.pryrc +1 -0
  9. data/.rubocop.yml +1 -1
  10. data/.rubocop_todo.yml +58 -84
  11. data/Gemfile +4 -1
  12. data/README.adoc +3115 -365
  13. data/RELEASE_NOTES.adoc +346 -0
  14. data/docs/custom_adapters.adoc +144 -0
  15. data/docs/custom_registers.adoc +228 -0
  16. data/docs/schema_generation.adoc +898 -0
  17. data/docs/schema_import.adoc +364 -0
  18. data/flake.lock +114 -0
  19. data/flake.nix +103 -0
  20. data/lib/lutaml/model/attribute.rb +242 -100
  21. data/lib/lutaml/model/choice.rb +30 -0
  22. data/lib/lutaml/model/collection.rb +212 -0
  23. data/lib/lutaml/model/comparable_model.rb +3 -3
  24. data/lib/lutaml/model/config.rb +71 -42
  25. data/lib/lutaml/model/constants.rb +2 -0
  26. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  27. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  28. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  29. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  30. data/lib/lutaml/model/error/polymorphic_error.rb +7 -2
  31. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  32. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  33. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  34. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  35. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  36. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  37. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  38. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  39. data/lib/lutaml/model/error.rb +10 -0
  40. data/lib/lutaml/model/errors.rb +36 -0
  41. data/lib/lutaml/model/format_registry.rb +44 -0
  42. data/lib/lutaml/model/global_register.rb +41 -0
  43. data/lib/lutaml/model/hash_adapter/document.rb +11 -0
  44. data/lib/lutaml/model/hash_adapter/mapping.rb +19 -0
  45. data/lib/lutaml/model/hash_adapter/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/hash_adapter/standard_adapter.rb +17 -0
  47. data/lib/lutaml/model/hash_adapter/transform.rb +8 -0
  48. data/lib/lutaml/model/hash_adapter.rb +21 -0
  49. data/lib/lutaml/model/json/document.rb +11 -0
  50. data/lib/lutaml/model/json/mapping.rb +19 -0
  51. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  52. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  53. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  54. data/lib/lutaml/model/json/transform.rb +8 -0
  55. data/lib/lutaml/model/json.rb +21 -0
  56. data/lib/lutaml/model/jsonl/document.rb +14 -0
  57. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  58. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  59. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  60. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  61. data/lib/lutaml/model/jsonl.rb +21 -0
  62. data/lib/lutaml/model/key_value_document.rb +28 -0
  63. data/lib/lutaml/model/mapping/key_value_mapping.rb +85 -13
  64. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +5 -0
  65. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  66. data/lib/lutaml/model/mapping/mapping_rule.rb +15 -9
  67. data/lib/lutaml/model/register.rb +105 -0
  68. data/lib/lutaml/model/{toml_adapter.rb → registrable.rb} +1 -1
  69. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  70. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  71. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  72. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  73. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  74. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  75. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  76. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  77. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  78. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  79. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  80. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  81. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  82. data/lib/lutaml/model/schema/renderer.rb +36 -0
  83. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  84. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  85. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  86. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  87. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  88. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  89. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  90. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  91. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  92. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  93. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  94. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  95. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  96. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  97. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  98. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  99. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  100. data/lib/lutaml/model/schema.rb +1 -0
  101. data/lib/lutaml/model/sequence.rb +60 -30
  102. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  103. data/lib/lutaml/model/serialize.rb +296 -548
  104. data/lib/lutaml/model/services/base.rb +11 -0
  105. data/lib/lutaml/model/services/logger.rb +54 -0
  106. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  107. data/lib/lutaml/model/services/transformer.rb +48 -0
  108. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  109. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  110. data/lib/lutaml/model/services/type/validator.rb +43 -0
  111. data/lib/lutaml/model/services/validator.rb +145 -0
  112. data/lib/lutaml/model/services.rb +5 -0
  113. data/lib/lutaml/model/toml/document.rb +11 -0
  114. data/lib/lutaml/model/toml/mapping.rb +27 -0
  115. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  116. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  117. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  118. data/lib/lutaml/model/toml/transform.rb +8 -0
  119. data/lib/lutaml/model/toml.rb +30 -0
  120. data/lib/lutaml/model/transform/key_value_transform.rb +297 -0
  121. data/lib/lutaml/model/transform/xml_transform.rb +228 -0
  122. data/lib/lutaml/model/transform.rb +93 -0
  123. data/lib/lutaml/model/type/boolean.rb +1 -1
  124. data/lib/lutaml/model/type/date.rb +1 -1
  125. data/lib/lutaml/model/type/date_time.rb +1 -1
  126. data/lib/lutaml/model/type/decimal.rb +11 -9
  127. data/lib/lutaml/model/type/float.rb +2 -1
  128. data/lib/lutaml/model/type/integer.rb +24 -21
  129. data/lib/lutaml/model/type/string.rb +4 -2
  130. data/lib/lutaml/model/type/time.rb +1 -1
  131. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  132. data/lib/lutaml/model/type/value.rb +11 -10
  133. data/lib/lutaml/model/type.rb +5 -2
  134. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  135. data/lib/lutaml/model/utils.rb +56 -4
  136. data/lib/lutaml/model/validation.rb +6 -4
  137. data/lib/lutaml/model/version.rb +1 -1
  138. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  139. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  140. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  141. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +42 -25
  142. data/lib/lutaml/model/xml/element.rb +32 -0
  143. data/lib/lutaml/model/xml/mapping.rb +471 -0
  144. data/lib/lutaml/model/xml/mapping_rule.rb +149 -0
  145. data/lib/lutaml/model/xml/nokogiri_adapter.rb +234 -0
  146. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  147. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +7 -2
  148. data/lib/lutaml/model/xml/oga_adapter.rb +172 -0
  149. data/lib/lutaml/model/xml/ox_adapter.rb +229 -0
  150. data/lib/lutaml/model/xml/transform.rb +8 -0
  151. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  152. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +9 -31
  153. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  154. data/lib/lutaml/model/xml.rb +31 -0
  155. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  156. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  157. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  158. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  159. data/lib/lutaml/model/yaml/document.rb +10 -0
  160. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  161. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  162. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  163. data/lib/lutaml/model/yaml/transform.rb +8 -0
  164. data/lib/lutaml/model/yaml.rb +21 -0
  165. data/lib/lutaml/model/yamls/document.rb +14 -0
  166. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  167. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  168. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  169. data/lib/lutaml/model/yamls/transform.rb +19 -0
  170. data/lib/lutaml/model/yamls.rb +21 -0
  171. data/lib/lutaml/model.rb +15 -4
  172. data/lutaml-model.gemspec +0 -4
  173. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +5 -6
  174. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  175. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  176. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  177. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  178. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  179. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  180. data/spec/lutaml/model/attribute_spec.rb +41 -44
  181. data/spec/lutaml/model/cdata_spec.rb +7 -7
  182. data/spec/lutaml/model/choice_spec.rb +44 -0
  183. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  184. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  185. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  186. data/spec/lutaml/model/global_register_spec.rb +108 -0
  187. data/spec/lutaml/model/group_spec.rb +27 -10
  188. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  189. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  190. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  191. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  192. data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
  193. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  194. data/spec/lutaml/model/multiple_mapping_spec.rb +6 -6
  195. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  196. data/spec/lutaml/model/polymorphic_spec.rb +178 -0
  197. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  198. data/spec/lutaml/model/register/xml_spec.rb +185 -0
  199. data/spec/lutaml/model/register_spec.rb +147 -0
  200. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  201. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  202. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  203. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  204. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  205. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  206. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  207. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  208. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  209. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  210. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  211. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  212. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  213. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  214. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  215. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  216. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  217. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  218. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  219. data/spec/lutaml/model/sequence_spec.rb +36 -0
  220. data/spec/lutaml/model/serializable_spec.rb +201 -94
  221. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  222. data/spec/lutaml/model/toml_spec.rb +51 -0
  223. data/spec/lutaml/model/transformation_spec.rb +72 -15
  224. data/spec/lutaml/model/type_spec.rb +8 -4
  225. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  226. data/spec/lutaml/model/utils_spec.rb +3 -3
  227. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  228. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  229. data/spec/lutaml/model/xml/root_mappings/nested_child_mappings_spec.rb +217 -0
  230. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  231. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  232. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  233. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  234. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +12 -12
  235. data/spec/lutaml/model/xml_adapter_spec.rb +30 -6
  236. data/spec/lutaml/model/xml_mapping_rule_spec.rb +14 -7
  237. data/spec/lutaml/model/xml_mapping_spec.rb +27 -15
  238. data/spec/lutaml/model/xml_spec.rb +63 -0
  239. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  240. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  241. data/spec/lutaml/model/yamls_spec.rb +294 -0
  242. data/spec/spec_helper.rb +4 -3
  243. metadata +162 -60
  244. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  245. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  246. data/lib/lutaml/model/loggable.rb +0 -15
  247. data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
  248. data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
  249. data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
  250. data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
  251. data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
  252. data/lib/lutaml/model/mapping.rb +0 -14
  253. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  254. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  255. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  256. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  257. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  258. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  259. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -0,0 +1,297 @@
1
+ module Lutaml
2
+ module Model
3
+ class KeyValueTransform < Lutaml::Model::Transform
4
+ def data_to_model(data, format, options = {})
5
+ if model_class.include?(Lutaml::Model::Serialize)
6
+ instance = model_class.new(__register: __register)
7
+ else
8
+ instance = model_class.new
9
+ register_accessor_methods_for(instance, __register)
10
+ end
11
+ mappings = extract_mappings(options, format)
12
+
13
+ Utils.add_if_present(options, :key_mappings, mappings.key_mappings)
14
+ Utils.add_if_present(options, :value_mappings, mappings.value_mappings)
15
+
16
+ mappings.mappings.each do |rule|
17
+ process_mapping_rule(data, instance, format, rule, options)
18
+ end
19
+
20
+ instance
21
+ end
22
+
23
+ def model_to_data(instance, format, options = {})
24
+ mappings = extract_mappings(options, format)
25
+
26
+ hash = {}
27
+ mappings.mappings.each do |rule|
28
+ next unless valid_mapping?(rule, options)
29
+
30
+ process_rule!(instance, rule, hash, format, mappings, options)
31
+ end
32
+
33
+ hash.keys == [""] ? hash[""] : hash
34
+ end
35
+
36
+ private
37
+
38
+ def process_rule!(instance, rule, hash, format, mappings, options)
39
+ return handle_delegate(instance, rule, hash, format) if rule.delegate
40
+
41
+ value = process_mapping_for_instance(instance, hash, format, rule, options)
42
+
43
+ return unless mappings.key_value_mappings? && value
44
+
45
+ hash[rule_from_name(rule)] = handle_key_value_mappings(value, mappings)
46
+ end
47
+
48
+ def process_mapping_for_instance(instance, hash, format, rule, options)
49
+ if rule.custom_methods[:to]
50
+ return instance.send(rule.custom_methods[:to], instance, hash)
51
+ end
52
+
53
+ attribute = attributes[rule.to]
54
+ value = rule.serialize(instance)
55
+
56
+ return handle_raw_mapping(hash, value, format, options) if rule.raw_mapping?
57
+ return handle_root_mappings(hash, value, format, rule, attribute) if rule.root_mapping?
58
+
59
+ value = ExportTransformer.call(value, rule, attribute)
60
+
61
+ value = serialize_value(value, rule, attribute, format, options)
62
+
63
+ return unless rule.render?(value, instance)
64
+
65
+ value = apply_value_map(value, rule.value_map(:to, options), attribute)
66
+
67
+ hash[rule_from_name(rule)] = value
68
+ end
69
+
70
+ def valid_mapping?(rule, options)
71
+ only = options[:only]
72
+ except = options[:except]
73
+ name = rule.to
74
+
75
+ (except.nil? || !except.include?(name)) &&
76
+ (only.nil? || only.include?(name))
77
+ end
78
+
79
+ def handle_raw_mapping(hash, value, format, options)
80
+ result = Lutaml::Model::Config.adapter_for(format).parse(value, options)
81
+
82
+ hash.merge!(result)
83
+ end
84
+
85
+ def handle_root_mappings(hash, value, format, rule, attr)
86
+ hash.merge!(
87
+ generate_hash_from_child_mappings(
88
+ attr,
89
+ value,
90
+ format,
91
+ rule.root_mappings,
92
+ ),
93
+ )
94
+ end
95
+
96
+ def serialize_value(value, rule, attr, format, options)
97
+ return attr.serialize(value, format, __register, options) unless rule.child_mappings
98
+
99
+ generate_hash_from_child_mappings(
100
+ attr, value, format, rule.child_mappings
101
+ )
102
+ end
103
+
104
+ def rule_from_name(rule)
105
+ rule.multiple_mappings? ? rule.from.first.to_s : rule.from.to_s
106
+ end
107
+
108
+ def generate_hash_from_child_mappings(attr, value, format, child_mappings)
109
+ return value unless child_mappings
110
+
111
+ hash = {}
112
+
113
+ generate_remaining_mappings_for_value(child_mappings, value, format)
114
+
115
+ attr_type = attr.type(__register)
116
+ value.each do |child_obj|
117
+ rules = attr_type.mappings_for(format)
118
+
119
+ hash.merge!(
120
+ extract_hash_for_child_mapping(child_mappings, child_obj, rules, format),
121
+ )
122
+ end
123
+
124
+ hash
125
+ end
126
+
127
+ # Generates remaining child mappings for all attributes when only
128
+ # the :key mapping (e.g., { name: :key }) is provided.
129
+ # If any additional mappings (e.g., { name: :key, id: :identifier })
130
+ # are specified, no additional child mappings will be generated.
131
+ def generate_remaining_mappings_for_value(child_mappings, value, format)
132
+ return if child_mappings.values != [:key]
133
+
134
+ klass = value.first.class
135
+ mappings = klass.mappings_for(format)
136
+
137
+ klass.attributes.each_key do |name|
138
+ next if Utils.string_or_symbol_key?(child_mappings, name)
139
+
140
+ child_mappings[name.to_sym] = child_mapping_for(name, mappings)
141
+ end
142
+ end
143
+
144
+ def child_mapping_for(name, mappings)
145
+ mappings.find_by_to(name)&.name.to_s
146
+ end
147
+
148
+ def extract_hash_for_child_mapping(child_mappings, child_obj, rules, format)
149
+ key = nil
150
+ value = {}
151
+
152
+ child_mappings.each do |attr_name, path|
153
+ rule = rules.find_by_to(attr_name)
154
+ attr_value = normalize_attribute_value(child_obj, rule.from, format)
155
+
156
+ next unless rule&.render?(attr_value, nil)
157
+ next key = attr_value if path == :key
158
+
159
+ value = extract_hash_value_for_child_mapping(path, attr_value, value)
160
+ end
161
+
162
+ value = nil if value.empty?
163
+ { key => value }
164
+ end
165
+
166
+ def normalize_attribute_value(value, attr_name, format)
167
+ Lutaml::Model::Config.adapter_for(format).parse(
168
+ value.public_send(:"to_#{format}"),
169
+ )[attr_name.to_s]
170
+ end
171
+
172
+ def extract_hash_value_for_child_mapping(path, value, map_value)
173
+ return value if path == :value
174
+
175
+ path = [path] unless path.is_a?(Array)
176
+ path[0...-1].inject(map_value) do |acc, k|
177
+ acc[k.to_s] ||= {}
178
+ end.public_send(:[]=, path.last.to_s, value)
179
+
180
+ map_value
181
+ end
182
+
183
+ def handle_delegate(instance, rule, hash, format)
184
+ value = extract_value_for_delegate(instance, rule)
185
+ return if value.nil? && !rule.render_nil
186
+
187
+ attribute = instance.send(rule.delegate).class.attributes[rule.to]
188
+ hash[rule_from_name(rule)] = attribute.serialize(value, format, __register)
189
+ end
190
+
191
+ def extract_value_for_delegate(instance, rule)
192
+ instance.send(rule.delegate).send(rule.to)
193
+ end
194
+
195
+ def extract_mappings(options, format)
196
+ options[:mappings] || mappings_for(format)
197
+ end
198
+
199
+ def process_mapping_rule(doc, instance, format, rule, options = {})
200
+ attr = attribute_for_rule(rule)
201
+ return if attr&.derived?
202
+
203
+ raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule, attr)
204
+
205
+ value = rule_value_extractor_class.call(rule, doc, format, attr, __register, options)
206
+ value = apply_value_map(value, rule.value_map(:from, options), attr)
207
+
208
+ return process_custom_method(rule, instance, value) if rule.has_custom_method_for_deserialization?
209
+
210
+ value = translate_mappings(value, rule.hash_mappings, attr, format)
211
+ value = cast_value(value, attr, format, rule) unless rule.hash_mappings
212
+
213
+ attr.valid_collection!(value, context)
214
+ rule.deserialize(instance, value, attributes, self)
215
+ end
216
+
217
+ def process_custom_method(rule, instance, value)
218
+ return unless Utils.present?(value)
219
+
220
+ model_class.new.send(rule.custom_methods[:from], instance, value)
221
+ end
222
+
223
+ def cast_value(value, attr, format, rule)
224
+ cast_options = rule.polymorphic ? { polymorphic: rule.polymorphic } : {}
225
+ attr.cast(value, format, __register, cast_options)
226
+ end
227
+
228
+ def translate_mappings(hash, child_mappings, attr, format)
229
+ return hash unless child_mappings
230
+
231
+ hash.map do |key, value|
232
+ process_child_mapping(key, value, child_mappings, attr, format, hash)
233
+ end
234
+ end
235
+
236
+ def process_child_mapping(key, value, child_mappings, attr, format, hash)
237
+ child_hash = build_child_hash(key, value, child_mappings, attr, format)
238
+
239
+ if only_keys_mapped?(child_mappings, hash)
240
+ child_hash.merge!(value)
241
+ end
242
+
243
+ map_child_data(child_hash, attr, format)
244
+ end
245
+
246
+ def build_child_hash(key, value, child_mappings, attr, format)
247
+ attr_type = attr.type(__register)
248
+ child_mappings.to_h do |attr_name, path|
249
+ attr_value = extract_attr_value(path, key, value)
250
+ attr_rule = attr_type.mappings_for(format).find_by_to(attr_name)
251
+ [attr_rule.from.to_s, attr_value]
252
+ end
253
+ end
254
+
255
+ def extract_attr_value(path, key, value)
256
+ case path
257
+ when :key then key
258
+ when :value then value
259
+ else
260
+ path = Array(path)
261
+ value.dig(*path.map(&:to_s))
262
+ end
263
+ end
264
+
265
+ def only_keys_mapped?(child_mappings, hash)
266
+ child_mappings.values == [:key] && hash.values.all?(Hash)
267
+ end
268
+
269
+ def map_child_data(child_hash, attr, format)
270
+ attr_type = attr.type(__register)
271
+ self.class.data_to_model(
272
+ attr_type,
273
+ child_hash,
274
+ format,
275
+ { mappings: attr_type.mappings_for(format) },
276
+ )
277
+ end
278
+
279
+ def handle_key_value_mappings(value, mappings)
280
+ value.to_h do |v|
281
+ [
282
+ v[mappings.key_mappings.to_instance.to_s],
283
+ if mappings.value_mappings
284
+ v[mappings.value_mappings.as_attribute.to_s]
285
+ else
286
+ v.except(mappings.key_mappings.to_instance.to_s)
287
+ end,
288
+ ]
289
+ end
290
+ end
291
+
292
+ def rule_value_extractor_class
293
+ Lutaml::Model::RuleValueExtractor
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,228 @@
1
+ module Lutaml
2
+ module Model
3
+ class XmlTransform < Lutaml::Model::Transform
4
+ def data_to_model(data, _format, options = {})
5
+ if model_class.include?(Lutaml::Model::Serialize)
6
+ instance = model_class.new({ __register: __register })
7
+ else
8
+ instance = model_class.new
9
+ register_accessor_methods_for(instance, __register)
10
+ end
11
+ apply_xml_mapping(data, instance, options)
12
+ end
13
+
14
+ # TODO: this should be extracted from adapters and moved here to be reused
15
+ def model_to_data(model, _format, _options = {})
16
+ model
17
+ end
18
+
19
+ private
20
+
21
+ def apply_xml_mapping(doc, instance, options = {})
22
+ options = prepare_options(options)
23
+ instance.encoding = options[:encoding]
24
+ return instance unless doc
25
+
26
+ mappings = options[:mappings] || mappings_for(:xml).mappings
27
+
28
+ validate_document!(doc, options)
29
+
30
+ set_instance_ordering(instance, doc, options)
31
+ set_schema_location(instance, doc)
32
+
33
+ defaults_used = []
34
+ validate_sequence!(doc.root.order)
35
+
36
+ mappings.each do |rule|
37
+ attr = attribute_for_rule(rule)
38
+ next if attr&.derived?
39
+
40
+ raise "Attribute '#{rule.to}' not found in #{context}" unless valid_rule?(rule, attr)
41
+
42
+ new_opts = options.dup
43
+ if rule.namespace_set?
44
+ new_opts[:default_namespace] = rule.namespace
45
+ end
46
+
47
+ value = if rule.raw_mapping?
48
+ doc.root.inner_xml
49
+ elsif rule.content_mapping?
50
+ rule.cdata ? doc.cdata : doc.text
51
+ else
52
+ val = value_for_rule(doc, rule, new_opts, instance)
53
+
54
+ if (val.nil? || Utils.uninitialized?(val)) && (instance.using_default?(rule.to) || rule.render_default)
55
+ defaults_used << rule.to
56
+ attr&.default(__register) || rule.to_value_for(instance)
57
+ else
58
+ val
59
+ end
60
+ end
61
+
62
+ value = apply_value_map(value, rule.value_map(:from, new_opts), attr)
63
+ value = normalize_xml_value(value, rule, attr, new_opts)
64
+ rule.deserialize(instance, value, attributes, context)
65
+ end
66
+
67
+ defaults_used.each do |attr_name|
68
+ instance.using_default_for(attr_name)
69
+ end
70
+
71
+ instance
72
+ end
73
+
74
+ def prepare_options(options)
75
+ opts = Utils.deep_dup(options)
76
+ opts[:default_namespace] ||= mappings_for(:xml)&.namespace_uri
77
+
78
+ opts
79
+ end
80
+
81
+ def validate_document!(doc, options)
82
+ return unless doc.is_a?(Array)
83
+
84
+ raise Lutaml::Model::CollectionTrueMissingError(
85
+ context,
86
+ options[:caller_class],
87
+ )
88
+ end
89
+
90
+ def set_instance_ordering(instance, doc, options)
91
+ return unless instance.respond_to?(:ordered=)
92
+
93
+ instance.element_order = doc.root.order
94
+ instance.ordered = mappings_for(:xml).ordered? || options[:ordered]
95
+ instance.mixed = mappings_for(:xml).mixed_content? || options[:mixed_content]
96
+ end
97
+
98
+ def set_schema_location(instance, doc)
99
+ schema_location = doc.attributes.values.find do |a|
100
+ a.unprefixed_name == "schemaLocation"
101
+ end
102
+
103
+ return if schema_location.nil?
104
+
105
+ instance.schema_location = Lutaml::Model::SchemaLocation.new(
106
+ schema_location: schema_location.value,
107
+ prefix: schema_location.namespace_prefix,
108
+ namespace: schema_location.namespace,
109
+ )
110
+ end
111
+
112
+ def value_for_rule(doc, rule, options, instance)
113
+ rule_names = rule.namespaced_names(options[:default_namespace])
114
+ return doc.root.find_attribute_value(rule_names) if rule.attribute?
115
+
116
+ attr = attribute_for_rule(rule)
117
+ attr_type = attr&.type(__register)
118
+
119
+ children = doc.children.select do |child|
120
+ rule_names.include?(child.namespaced_name) && !child.text?
121
+ end
122
+
123
+ if rule.has_custom_method_for_deserialization? || attr_type == Lutaml::Model::Type::Hash
124
+ return_child = attr_type == Lutaml::Model::Type::Hash || !attr.collection? if attr
125
+ return return_child ? children.first : children
126
+ end
127
+
128
+ return handle_cdata(children) if rule.cdata
129
+ return Lutaml::Model::UninitializedClass.instance if children.empty?
130
+
131
+ values = attr.build_collection
132
+
133
+ instance.value_set_for(attr.name)
134
+
135
+ children.each do |child|
136
+ if !rule.has_custom_method_for_deserialization? && attr_type <= Serialize
137
+ cast_options = options.except(:mappings)
138
+ cast_options[:polymorphic] = rule.polymorphic if rule.polymorphic
139
+ cast_options[:register] = __register
140
+
141
+ values << attr.cast(child, :xml, __register, cast_options)
142
+ elsif attr.raw?
143
+ values << inner_xml_of(child)
144
+ else
145
+ return nil if rule.render_nil_as_nil? && child.nil_element?
146
+
147
+ text = child.nil_element? ? nil : (child&.text&.+ child&.cdata)
148
+ values << text
149
+ end
150
+ end
151
+
152
+ normalized_value_for_attr(values, attr)
153
+ end
154
+
155
+ def handle_cdata(children)
156
+ values = children.map do |child|
157
+ child.cdata_children&.map(&:text)
158
+ end.flatten
159
+
160
+ children.count > 1 ? values : values.first
161
+ end
162
+
163
+ def normalized_value_for_attr(values, attr)
164
+ # for xml collection: true cases like
165
+ # <store><items /></store>
166
+ # <store><items xsi:nil="true"/></store>
167
+ # <store><items></items></store>
168
+ #
169
+ # these are considered empty collection
170
+ return [] if attr&.collection? && [[nil], [""]].include?(values)
171
+ return values if attr&.collection?
172
+
173
+ values.is_a?(Array) ? values.first : values
174
+ end
175
+
176
+ def normalize_xml_value(value, rule, attr, options = {})
177
+ collection_class = attr&.collection_class || Array
178
+ value = [value].compact if !value.nil? && attr&.collection? && !value.is_a?(collection_class)
179
+
180
+ return value unless cast_value?(attr, rule)
181
+
182
+ attr.cast(value, :xml, __register, options)
183
+ end
184
+
185
+ def cast_value?(attr, rule)
186
+ attr && rule.castable?
187
+ end
188
+
189
+ def ensure_utf8(value)
190
+ case value
191
+ when String
192
+ value.encode("UTF-8", invalid: :replace, undef: :replace,
193
+ replace: "")
194
+ when Array
195
+ value.map { |v| ensure_utf8(v) }
196
+ when Hash
197
+ value.transform_keys do |k|
198
+ ensure_utf8(k)
199
+ end.transform_values do |v|
200
+ ensure_utf8(v)
201
+ end
202
+ else
203
+ value
204
+ end
205
+ end
206
+
207
+ def inner_xml_of(node)
208
+ case node
209
+ when Xml::XmlElement
210
+ node.inner_xml
211
+ else
212
+ node.children.map(&:to_xml).join
213
+ end
214
+ end
215
+
216
+ def validate_sequence!(element_order)
217
+ mapping_sequence = mappings_for(:xml).element_sequence
218
+ return if mapping_sequence.empty?
219
+
220
+ current_order = element_order.filter_map(&:element_tag)
221
+
222
+ mapping_sequence.each do |mapping|
223
+ mapping.validate_content!(current_order, context)
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,93 @@
1
+ module Lutaml
2
+ module Model
3
+ class Transform
4
+ def self.data_to_model(context, data, format, options = {})
5
+ new(context, options[:register]).data_to_model(data, format, options)
6
+ end
7
+
8
+ def self.model_to_data(context, model, format, options = {})
9
+ register = model.__register if model.respond_to?(:__register)
10
+ new(context, register).model_to_data(model, format, options)
11
+ end
12
+
13
+ attr_reader :context, :attributes, :__register
14
+
15
+ def initialize(context, register = nil)
16
+ @context = context
17
+ @attributes = context.attributes
18
+ @__register = register || Lutaml::Model::Config.default_register
19
+ end
20
+
21
+ def model_class
22
+ @context.model
23
+ end
24
+
25
+ def data_to_model(data, options = {})
26
+ raise NotImplementedError, "#{self.class.name} must implement `data_to_model`."
27
+ end
28
+
29
+ def model_to_data(model, options = {})
30
+ raise NotImplementedError, "#{self.class.name} must implement `model_to_data`."
31
+ end
32
+
33
+ protected
34
+
35
+ def apply_value_map(value, value_map, attr)
36
+ if value.nil?
37
+ value_for_option(value_map[:nil], attr)
38
+ elsif Utils.empty?(value)
39
+ value_for_option(value_map[:empty], attr, value)
40
+ elsif Utils.uninitialized?(value)
41
+ value_for_option(value_map[:omitted], attr)
42
+ else
43
+ value
44
+ end
45
+ end
46
+
47
+ def value_for_option(option, attr, empty_value = nil)
48
+ return nil if option == :nil
49
+ return empty_value || empty_object(attr) if option == :empty
50
+
51
+ Lutaml::Model::UninitializedClass.instance
52
+ end
53
+
54
+ def empty_object(attr)
55
+ return [] if attr.collection?
56
+
57
+ ""
58
+ end
59
+
60
+ def mappings_for(format)
61
+ context.mappings_for(format)
62
+ end
63
+
64
+ def defined_mappings_for(format)
65
+ context.mappings[format]
66
+ end
67
+
68
+ def valid_rule?(rule, attribute)
69
+ attribute || rule.custom_methods[:from]
70
+ end
71
+
72
+ def attribute_for_rule(rule)
73
+ return attributes[rule.to] unless rule.delegate
74
+
75
+ attributes[rule.delegate].type(__register).attributes[rule.to]
76
+ end
77
+
78
+ def register_accessor_methods_for(object, register)
79
+ klass = object.class
80
+ Utils.add_method_if_not_defined(klass, :__register) do
81
+ @__register
82
+ end
83
+ Utils.add_method_if_not_defined(klass, :__register=) do |value|
84
+ @__register = value
85
+ end
86
+ object.__register = register
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ require_relative "transform/key_value_transform"
93
+ require_relative "transform/xml_transform"
@@ -2,7 +2,7 @@ module Lutaml
2
2
  module Model
3
3
  module Type
4
4
  class Boolean < Value
5
- def self.cast(value)
5
+ def self.cast(value, _options = {})
6
6
  return nil if value.nil?
7
7
  return true if value == true || value.to_s.match?(/^(true|t|yes|y|1)$/i)
8
8
  return false if value == false || value.to_s.match?(/^(false|f|no|n|0)$/i)
@@ -2,7 +2,7 @@ module Lutaml
2
2
  module Model
3
3
  module Type
4
4
  class Date < Value
5
- def self.cast(value)
5
+ def self.cast(value, _options = {})
6
6
  return value if value.nil? || Utils.uninitialized?(value)
7
7
 
8
8
  case value
@@ -5,7 +5,7 @@ module Lutaml
5
5
  module Type
6
6
  # Date and time representation
7
7
  class DateTime < Value
8
- def self.cast(value)
8
+ def self.cast(value, _options = {})
9
9
  return value if value.nil? || Utils.uninitialized?(value)
10
10
 
11
11
  case value
@@ -2,18 +2,20 @@ module Lutaml
2
2
  module Model
3
3
  module Type
4
4
  class Decimal < Value
5
- def self.cast(value)
5
+ def self.cast(value, options = {})
6
6
  return nil if value.nil?
7
7
 
8
8
  check_dependencies!(value)
9
- case value
10
- when BigDecimal
11
- # If already a BigDecimal, return as-is
12
- value
13
- else
14
- # Convert to string first to handle various input types
15
- BigDecimal(value.to_s)
16
- end
9
+ value = case value
10
+ when BigDecimal
11
+ # If already a BigDecimal, return as-is
12
+ value
13
+ else
14
+ # Convert to string first to handle various input types
15
+ BigDecimal(value.to_s)
16
+ end
17
+ Model::Services::Type::Validator::Number.validate!(value, options)
18
+ value
17
19
  rescue ArgumentError
18
20
  nil
19
21
  end
@@ -2,9 +2,10 @@ module Lutaml
2
2
  module Model
3
3
  module Type
4
4
  class Float < Value
5
- def self.cast(value)
5
+ def self.cast(value, options = {})
6
6
  return nil if value.nil?
7
7
 
8
+ Model::Services::Type::Validator::Number.validate!(value, options)
8
9
  value.to_f
9
10
  end
10
11