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,346 @@
1
+ = Release Notes: v0.6.7 to v0.7.1
2
+
3
+ == Overview
4
+
5
+ This release introduces significant enhancements to the lutaml-model library, focusing on improved handling of missing values, comprehensive polymorphic model support, and important bug fixes. This document outlines the key changes and provides guidance for upgrading your codebase.
6
+
7
+ == Key changes
8
+
9
+ === Missing values handling family
10
+
11
+ A comprehensive set of features for handling the "missing values" family (empty,
12
+ non-existent, and undefined values) across different serialization formats has
13
+ been introduced.
14
+
15
+ WARNING: The default behavior for handling uninitialized collection attributes
16
+ is changed from before.
17
+
18
+ In version 0.6.7, an uninitialized collection attribute in Lutaml::Model is
19
+ represented as an empty array (`[]`).
20
+
21
+ In version 0.7.1, an uninitialized collection attribute in Lutaml::Model is
22
+ represented as `nil`.
23
+
24
+ This change is made to ensure consistency across different serialization formats
25
+ and to provide a predictable and consistent behavior based on explicit
26
+ declarations when dealing with missing values.
27
+
28
+ Previously, Lutaml::Model had a limited ability to handle the missing value types,
29
+ for example, the appearance and round-trip behavior of the blank XML element
30
+ is uncertain. Lutaml::Model also did not support XML `nil` and YAML `null` values
31
+ in a consistent manner, nor the inconsistency where TOML does not support
32
+ the notion of `nil` or `null` values.
33
+
34
+ This change may break existing code that relies on the previous behavior. If you
35
+ were using uninitialized collection attributes in your code, you may need to
36
+ update your code to handle the new behavior.
37
+
38
+ There is now a series of features to support the missing values family, including:
39
+
40
+ * Attribute-level `initialize_empty` option, which controls the default
41
+ initialization behavior of collection attributes. Previously, collection
42
+ attributes defaulted to an empty array (`[]`) when uninitialized. The default
43
+ is now the value `nil`.
44
+ +
45
+ Revert to legacy behavior: You can return to the previous behavior by setting
46
+ `initialize_empty: true` in the attribute definition.
47
+
48
+ * Mapping-rule-level `render_nil` and `render_empty` options have been revamped
49
+ into accepting additional options that are used to control how nil and empty
50
+ values are parsed from and rendered in serialization formats.
51
+ +
52
+ The behavior of `render_nil: true` and `render_empty: true` is now equivalent to
53
+ `render_nil: :as_empty` and `render_empty: :as_empty`, respectively.
54
+ It is strongly recommended that the legacy behavior be replaced with the new
55
+ options.
56
+ +
57
+ The reason being that legacy behavior breaks round-trips between conversions of
58
+ the same format. In the case of `render_nil: true`, a model value of `nil` is
59
+ serialized into an XML blank element, but when the XML blank element is
60
+ deserialized, it is deserialized into an empty string (`""`, as per the W3C XML
61
+ standard), not `nil`.
62
+
63
+ * Mapping-rule-level `value_map` option, which provides fine-grained control
64
+ over how missing values are handled during serialization and deserialization.
65
+
66
+ * Support for a new `Uninitialized` value at the `Lutaml::Model` level, which is
67
+ used to represent uninitialized attributes.
68
+ +
69
+ This means that if you have a model with an attribute that is not set, it will
70
+ be represented as `Uninitialized` instead of `nil`. This is useful for
71
+ distinguishing between an attribute that is not set and an attribute that is
72
+ set to `nil`, and as a result will preserve the round-trip behavior of
73
+ serialization formats.
74
+
75
+ * Support for `nil` values in JSON and YAML, and XML serialization formats.
76
+ +
77
+ The `nil` value is now supported in JSON, YAML and XML serialization formats.
78
+ This means that if you have a model with an attribute that is set to `nil`, it
79
+ will be serialized as `null` in JSON/YAML and as an empty element in XML. This
80
+ is useful for representing missing values in a consistent manner across
81
+ different serialization formats.
82
+
83
+
84
+
85
+ ==== `initialize_empty` option
86
+
87
+ The `initialize_empty` option controls the default initialization behavior of
88
+ collection attributes:
89
+
90
+ [source,ruby]
91
+ ----
92
+ # Default to `nil`
93
+ class SomeModel < Lutaml::Model::Serializable
94
+ attribute :coll, :string, collection: true
95
+
96
+ xml do
97
+ root "some-model"
98
+ map_element 'collection', to: :coll
99
+ end
100
+ end
101
+ puts SomeModel.new.coll # => nil
102
+
103
+ # Default to empty array
104
+ class SomeModel < Lutaml::Model::Serializable
105
+ attribute :coll, :string, collection: true, initialize_empty: true
106
+
107
+ xml do
108
+ map_element 'collection', to: :coll
109
+ end
110
+ end
111
+ puts SomeModel.new.coll # => []
112
+ ----
113
+
114
+ ==== `value_map` option
115
+
116
+ The `value_map` option provides fine-grained control over how missing values are handled during serialization and deserialization. This is especially useful when different serialization formats represent missing values differently.
117
+
118
+ [source,ruby]
119
+ ----
120
+ class ExampleClass < Lutaml::Model::Serializable
121
+ attribute :status, :string
122
+
123
+ xml do
124
+ map_element 'status', to: :status, value_map: {
125
+ from: { empty: :nil, omitted: :omitted, nil: :nil },
126
+ to: { empty: :nil, omitted: :omitted, nil: :nil }
127
+ }
128
+ end
129
+
130
+ json do
131
+ map 'status', to: :status, value_map: {
132
+ from: { empty: :nil, omitted: :omitted, nil: :nil },
133
+ to: { empty: :nil, omitted: :omitted, nil: :nil }
134
+ }
135
+ end
136
+
137
+ toml do
138
+ map 'status', to: :status, value_map: {
139
+ from: { empty: :nil, omitted: :omitted },
140
+ to: { empty: :nil, omitted: :omitted, nil: :omitted }
141
+ }
142
+ end
143
+ end
144
+ ----
145
+
146
+ For collection attributes, the value_map behaves differently depending on the `initialize_empty` setting.
147
+
148
+ ==== `render_nil` and `render_empty` modes
149
+
150
+ These options provide shorthand methods to control how nil and empty values are rendered in serialization formats:
151
+
152
+ * `render_nil: true | :as_empty | :as_blank | :nil | :omit` - Controls how nil values are rendered
153
+ * `render_empty: :as_empty | :as_blank | :nil | :omit` - Controls how empty collections are rendered
154
+
155
+ [source,ruby]
156
+ ----
157
+ class SomeModel < Lutaml::Model::Serializable
158
+ attribute :coll, :string, collection: true
159
+
160
+ xml do
161
+ root "some-model"
162
+ map_element 'collection', to: :coll, render_nil: :omit
163
+ end
164
+
165
+ json do
166
+ map 'collection', to: :coll, render_empty: :as_nil
167
+ end
168
+ end
169
+ ----
170
+
171
+ === Polymorphic model support
172
+
173
+ From version 0.7.1, Lutaml::Model now supports polymorphic models for attribute
174
+ types.
175
+
176
+ Comprehensive support for polymorphic models has been introduced, allowing for
177
+ flexible modeling of inheritance relationships and proper
178
+ serialization/deserialization.
179
+
180
+ This means that you can define attributes that can accept multiple types of
181
+ objects, and the library will handle serialization and deserialization for these
182
+ types seamlessly.
183
+
184
+ Specifically, the following features have been added:
185
+
186
+ * Polymorphic attribute definition
187
+ * Polymorphic class differentiation in model and serializations
188
+
189
+ ==== Polymorphic attribute definition
190
+
191
+ Polymorphic attributes can be defined using the `polymorphic` option.
192
+
193
+ It is possible to define polymorphic attribute classes in the superclass
194
+ and subclasses.
195
+
196
+ The `polymorphic` option can be set to a collection of classes, and the
197
+ `polymorphic_class` option can be set to `true` to indicate acceptance of any
198
+ subclass of the polymorphic class. Alternatively, you can specify a collection
199
+ of classes to restrict the accepted types.
200
+
201
+ [source,ruby]
202
+ ----
203
+ class ReferenceSet < Lutaml::Model::Serializable
204
+ attribute :references, Reference, collection: true, polymorphic: [
205
+ DocumentReference,
206
+ AnchorReference,
207
+ ]
208
+ end
209
+ ----
210
+
211
+ When you are not requiring a specific set of subclasses, you can use the
212
+ `polymorphic: true` option to indicate that any subclass of the specified class is
213
+ acceptable.
214
+
215
+ [source,ruby]
216
+ ----
217
+ class ReferenceSet < Lutaml::Model::Serializable
218
+ attribute :references, Reference, collection: true, polymorphic: true
219
+ end
220
+ ----
221
+
222
+
223
+ ==== Polymorphic class differentiator
224
+
225
+ When serializing polymorphic models, a differentiator attribute is required to
226
+ also be serialized to identify the specific subclass of the polymorphic class.
227
+
228
+ This differentiator attribute is typically a string that indicates the type of
229
+ the object being serialized. The differentiator attribute can be defined in the
230
+ superclass or subclasses of the polymorphic class.
231
+
232
+ Typically, the differentiator attribute is an XML element or attribute (e.g. `type="document-ref"`), or in JSON a `@`-prefixed key (e.g. `@type`).
233
+
234
+ A polymorphic differentiator attribute can be set in either the superclass or subclasses:
235
+
236
+ [source,ruby]
237
+ ----
238
+ # In superclass
239
+ class Reference < Lutaml::Model::Serializable
240
+ attribute :_class, :string, polymorphic_class: true
241
+ # ...
242
+ end
243
+
244
+ # Or in subclasses
245
+ class DocumentReference < Reference
246
+ attribute :_class, :string, polymorphic_class: true
247
+ # ...
248
+ end
249
+
250
+ # Or in subclasses
251
+ class AnchorReference < Reference
252
+ attribute :_class, :string, polymorphic_class: true
253
+ # ...
254
+ end
255
+ ----
256
+
257
+ Given the differentiator attribute being `_class`, we still need to define the
258
+ mapping for the differentiator attribute in the superclass or subclasses.
259
+
260
+ Polymorphic mapping in serialization is supported through the `polymorphic_map` option:
261
+
262
+ [source,ruby]
263
+ ----
264
+ class Reference < Lutaml::Model::Serializable
265
+ attribute :_class, :string, polymorphic_class: true
266
+
267
+ xml do
268
+ map_attribute "reference-type", to: :_class, polymorphic_map: {
269
+ "document-ref" => "DocumentReference",
270
+ "anchor-ref" => "AnchorReference"
271
+ }
272
+ end
273
+
274
+ key_value do
275
+ map "_class", to: :_class, polymorphic_map: {
276
+ "Document" => "DocumentReference",
277
+ "Anchor" => "AnchorReference"
278
+ }
279
+ end
280
+ end
281
+ ----
282
+
283
+ The `polymorphic_map` option is used to indicate that when serializing a
284
+ `DocumentReference` object, the `_class` attribute will be serialized as
285
+ `document-ref`, and when serializing an `AnchorReference` object, the `_class`
286
+ attribute will be serialized as `anchor-ref`.
287
+
288
+ This is a mapping-level option so that it can be used in serialization formats
289
+ independently.
290
+
291
+ This will produce a differentiator attribute in the serialized output as such.
292
+
293
+ [source,yaml]
294
+ ----
295
+ ---
296
+ references:
297
+ - _class: Document
298
+ # other attributes...
299
+ - _class: Anchor
300
+ # other attributes...
301
+ ----
302
+
303
+
304
+ === Importable model improvements
305
+
306
+ Importable model functionality has been improved, with better support for reusable models:
307
+
308
+ * `import_model` - Imports both attributes and mappings
309
+ * `import_model_attributes` - Imports only attributes
310
+ * `import_model_mappings` - Imports only mappings
311
+
312
+ Bug fixes for the import_model functionality ensure more reliable model reuse.
313
+
314
+ === Circular reference handling
315
+
316
+ Improved handling of circular references in the `ComparableModel` module prevents stack overflow errors when comparing models with self-referential structures.
317
+
318
+ == Upgrade guide
319
+
320
+ === Missing values handling
321
+
322
+ If you were previously using `render_nil: true`, you can continue using it, but
323
+ you may want to explore the more flexible `value_map` option for fine-grained
324
+ control over different serialization formats.
325
+
326
+ For collection attributes, consider whether you want collections to initialize
327
+ as `nil` or as an empty array by setting the `initialize_empty` option
328
+ accordingly.
329
+
330
+ === Polymorphic models
331
+
332
+ If you were previously using class detection for polymorphic models without
333
+ explicit differentiators, you should now define a polymorphic differentiator
334
+ attribute and use the `polymorphic_class: true` option.
335
+
336
+ == Contributors
337
+
338
+ Thank you to all contributors who made this release possible, especially:
339
+
340
+ * HassanAkbar
341
+ * Ronald Tse
342
+ * suleman-uzair
343
+
344
+ == Compatibility
345
+
346
+ This release maintains compatibility with Ruby 2.7 and above.
@@ -0,0 +1,144 @@
1
+ = Custom Adapters in Lutaml::Model
2
+
3
+ Lutaml::Model provides a flexible system for creating custom adapters to handle different data formats. This guide explains how to create and use custom adapters in your application.
4
+
5
+ == Overview
6
+
7
+ Custom adapters allow you to extend Lutaml::Model to support additional data formats beyond the built-in ones (TOML, YAML, JSON, XML). Each adapter consists of three main components:
8
+
9
+ . An adapter class that handles parsing and serialization
10
+ . A mapping class that defines how data maps to your model
11
+ . A transform class that handles data transformation
12
+
13
+ == Creating a Custom Adapter
14
+
15
+ === 1. Adapter Class
16
+
17
+ The adapter class is responsible for parsing input data and converting it back to the target format. It must implement:
18
+
19
+ * `self.parse(data, options = {})` - Class method to parse input data
20
+ * `to_<format_name>` - Instance method to convert data back to the target format
21
+
22
+ .Example of a custom adapter for parsing string pairs
23
+ [source,ruby]
24
+ ----
25
+ class PairsAdapter < Lutaml::Model::KeyValueDocument
26
+ attr_reader :parsed_data
27
+
28
+ def initialize(parsed_data)
29
+ @parsed_data = parsed_data
30
+ end
31
+
32
+ def self.parse(data, options = {})
33
+ # Example input: "name:John|age:30"
34
+ parsed = data.split("|").map { |pair| pair.split(":") }.to_h
35
+ new(parsed)
36
+ end
37
+
38
+ def to_pairs_format
39
+ # Example output: "name:John|age:30"
40
+ parsed_data.map { |k, v| "#{k}:#{v}" }.join("|")
41
+ end
42
+ end
43
+ ----
44
+
45
+ === 2. Mapping Class
46
+
47
+ The mapping class defines how data fields map to your model attributes. It should inherit from `Lutaml::Model::Mapping`.
48
+
49
+ [source,ruby]
50
+ ----
51
+ class PairsMappingRule < Lutaml::Model::MappingRule
52
+ end
53
+
54
+ class PairsMapping < Lutaml::Model::Mapping
55
+ def map_field(name, to:)
56
+ @mappings << PairsMappingRule.new(name, to: to)
57
+ end
58
+
59
+ def mappings
60
+ @mappings
61
+ end
62
+ end
63
+ ----
64
+
65
+ === 3. Transform Class
66
+
67
+ The transform class handles the conversion between your data format and the model. It should inherit from `Lutaml::Model::Transform`.
68
+
69
+ [source,ruby]
70
+ ----
71
+ class PairsTransform < Lutaml::Model::Transform
72
+ def self.data_to_model(context, data, format, options = {})
73
+ model = context.model.new
74
+ model.name = data["name"]
75
+ model.age = data["age"]
76
+ model
77
+ end
78
+
79
+ def self.model_to_data(context, model, format, options = {})
80
+ { "name" => model.name, "age" => model.age }
81
+ end
82
+ end
83
+ ----
84
+
85
+ == Registering Your Adapter
86
+
87
+ Register your custom adapter with the FormatRegistry:
88
+
89
+ [source,ruby]
90
+ ----
91
+ Lutaml::Model::FormatRegistry.register(
92
+ :pairs,
93
+ mapping_class: PairsMapping,
94
+ adapter_class: PairsAdapter,
95
+ transformer: PairsTransform
96
+ )
97
+ ----
98
+
99
+ == Using Custom Adapters
100
+
101
+ Once registered, you can use your custom adapter with your model classes:
102
+
103
+ [source,ruby]
104
+ ----
105
+ class Person < Lutaml::Model::Serializable
106
+ attribute :name, :string
107
+ attribute :age, :string
108
+
109
+ pairs do
110
+ map_field "name", to: :name
111
+ map_field "age", to: :age
112
+ end
113
+ end
114
+ ----
115
+
116
+ === Step-by-step Example
117
+
118
+ [source,ruby]
119
+ ----
120
+ input = "name:John|age:30"
121
+
122
+ person = Person.from_pairs(input)
123
+ # => #<Person @name="John", @age="30">
124
+
125
+ output = person.to_pairs_format
126
+ # => "name:John|age:30"
127
+ ----
128
+
129
+ == Best Practices
130
+
131
+ . *Separation of Concerns*: Keep parsing, mapping, and transformation logic in their respective classes.
132
+ . *Error Handling*: Add meaningful error handling in your adapter's `.parse` method.
133
+ . *Documentation*: Clearly document the format and adapter usage.
134
+ . *Testing*: Write tests for your adapter's logic and behavior.
135
+ . *Leverage Format Features*: Support format-specific options while staying consistent with Lutaml::Model’s interfaces.
136
+
137
+ == Example Implementations
138
+
139
+ For complete examples of custom adapter implementations, see:
140
+
141
+ * `spec/lutaml/model/custom_bibtex_adapter_spec.rb`
142
+ * `spec/lutaml/model/custom_vobject_adapter_spec.rb`
143
+
144
+ These demonstrate how to build complete, custom adapters that integrate cleanly with `Lutaml::Model`.