lutaml-model 0.7.3 → 0.7.5

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/release.yml +3 -0
  4. data/.gitignore +6 -1
  5. data/.irbrc +1 -0
  6. data/.pryrc +1 -0
  7. data/.rubocop_todo.yml +25 -52
  8. data/README.adoc +2177 -192
  9. data/docs/custom_registers.adoc +228 -0
  10. data/docs/schema_generation.adoc +898 -0
  11. data/docs/schema_import.adoc +364 -0
  12. data/flake.lock +114 -0
  13. data/flake.nix +103 -0
  14. data/lib/lutaml/model/attribute.rb +230 -94
  15. data/lib/lutaml/model/choice.rb +30 -0
  16. data/lib/lutaml/model/collection.rb +194 -0
  17. data/lib/lutaml/model/comparable_model.rb +3 -3
  18. data/lib/lutaml/model/config.rb +26 -3
  19. data/lib/lutaml/model/constants.rb +2 -0
  20. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  21. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  22. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  23. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  24. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  25. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  26. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  27. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  28. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  29. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  30. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  31. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  32. data/lib/lutaml/model/error.rb +10 -0
  33. data/lib/lutaml/model/errors.rb +36 -0
  34. data/lib/lutaml/model/format_registry.rb +5 -2
  35. data/lib/lutaml/model/global_register.rb +41 -0
  36. data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
  37. data/lib/lutaml/model/jsonl/document.rb +14 -0
  38. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  39. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  40. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  41. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  42. data/lib/lutaml/model/jsonl.rb +21 -0
  43. data/lib/lutaml/model/key_value_document.rb +3 -2
  44. data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
  45. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
  46. data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
  47. data/lib/lutaml/model/register.rb +105 -0
  48. data/lib/lutaml/model/registrable.rb +6 -0
  49. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  50. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  51. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  52. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  53. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  54. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  55. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  56. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  57. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  58. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  59. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  60. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  61. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  62. data/lib/lutaml/model/schema/renderer.rb +36 -0
  63. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  64. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  65. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  66. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  67. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  68. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  69. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  70. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  71. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  72. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  73. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  74. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  75. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  76. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  77. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  78. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  79. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  80. data/lib/lutaml/model/schema.rb +1 -0
  81. data/lib/lutaml/model/sequence.rb +60 -30
  82. data/lib/lutaml/model/serialize.rb +175 -53
  83. data/lib/lutaml/model/services/base.rb +11 -0
  84. data/lib/lutaml/model/services/logger.rb +2 -2
  85. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  86. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  87. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  88. data/lib/lutaml/model/services/type/validator.rb +43 -0
  89. data/lib/lutaml/model/services/validator.rb +145 -0
  90. data/lib/lutaml/model/services.rb +3 -0
  91. data/lib/lutaml/model/transform/key_value_transform.rb +60 -50
  92. data/lib/lutaml/model/transform/xml_transform.rb +46 -57
  93. data/lib/lutaml/model/transform.rb +22 -8
  94. data/lib/lutaml/model/type/boolean.rb +1 -1
  95. data/lib/lutaml/model/type/date.rb +1 -1
  96. data/lib/lutaml/model/type/date_time.rb +1 -1
  97. data/lib/lutaml/model/type/decimal.rb +11 -9
  98. data/lib/lutaml/model/type/float.rb +2 -1
  99. data/lib/lutaml/model/type/integer.rb +24 -21
  100. data/lib/lutaml/model/type/string.rb +4 -2
  101. data/lib/lutaml/model/type/time.rb +1 -1
  102. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  103. data/lib/lutaml/model/type/value.rb +5 -1
  104. data/lib/lutaml/model/type.rb +5 -2
  105. data/lib/lutaml/model/utils.rb +30 -8
  106. data/lib/lutaml/model/validation.rb +6 -4
  107. data/lib/lutaml/model/version.rb +1 -1
  108. data/lib/lutaml/model/xml/document.rb +37 -19
  109. data/lib/lutaml/model/xml/mapping.rb +74 -13
  110. data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
  111. data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
  112. data/lib/lutaml/model/xml/oga/element.rb +4 -1
  113. data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
  114. data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
  115. data/lib/lutaml/model/xml/xml_element.rb +3 -28
  116. data/lib/lutaml/model/xml_adapter/element.rb +1 -1
  117. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  118. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  119. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  120. data/lib/lutaml/model/yamls/document.rb +14 -0
  121. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  122. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  123. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  124. data/lib/lutaml/model/yamls/transform.rb +19 -0
  125. data/lib/lutaml/model/yamls.rb +21 -0
  126. data/lib/lutaml/model.rb +7 -31
  127. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
  128. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  129. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  130. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  131. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  132. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  133. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  134. data/spec/lutaml/model/attribute_spec.rb +41 -44
  135. data/spec/lutaml/model/choice_spec.rb +44 -0
  136. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  137. data/spec/lutaml/model/custom_model_spec.rb +15 -3
  138. data/spec/lutaml/model/defaults_spec.rb +5 -1
  139. data/spec/lutaml/model/global_register_spec.rb +108 -0
  140. data/spec/lutaml/model/group_spec.rb +9 -3
  141. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  142. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  143. data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
  144. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  145. data/spec/lutaml/model/register/xml_spec.rb +187 -0
  146. data/spec/lutaml/model/register_spec.rb +147 -0
  147. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  148. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  149. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  150. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  151. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  152. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  153. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  154. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  155. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  156. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  157. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  158. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  159. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  160. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  161. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  162. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  163. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  164. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  165. data/spec/lutaml/model/sequence_spec.rb +36 -0
  166. data/spec/lutaml/model/serializable_spec.rb +31 -0
  167. data/spec/lutaml/model/type_spec.rb +8 -4
  168. data/spec/lutaml/model/utils_spec.rb +3 -3
  169. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  170. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  171. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  172. data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
  173. data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
  174. data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
  175. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  176. data/spec/lutaml/model/yamls_spec.rb +294 -0
  177. data/spec/spec_helper.rb +1 -0
  178. metadata +105 -9
  179. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  180. /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
  181. /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
  182. /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
  183. /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
  184. /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
data/README.adoc CHANGED
@@ -39,7 +39,11 @@ Lutaml::Model are provided in <<migrate-from-shale>>.
39
39
  * Support for collections and default values
40
40
  * Custom serialization/deserialization methods
41
41
  * XML namespaces and mappings
42
+ * Generate serialization schemas from model definitions (<<schema-generation>>)
43
+ * Import serialization schemas to define models (<<schema-import>>)
42
44
  * Create custom adapters for additional data formats (see <<custom-adapters>>)
45
+ * Dynamically modify model attribute types using registers (see <<custom-registers>>)
46
+
43
47
 
44
48
 
45
49
  == Data modeling in a nutshell
@@ -270,10 +274,26 @@ Or install it yourself as:
270
274
  gem install lutaml-model
271
275
  ----
272
276
 
277
+
278
+ == Components
279
+
280
+ LutaML provides the following set of components to model information in a
281
+ structured way.
282
+
283
+ * <<model-definition,Basic models>>
284
+ * <<attribute-definition,Attributes in models>>
285
+ * <<value-definition,Values assigned to attributes>>
286
+ * <<collection-definition,Collections of models>>
287
+
288
+
289
+ [[model-definition]]
273
290
  == Model
274
291
 
275
292
  === General
276
293
 
294
+ A LutaML model is used to represent a class of information, of which a model
295
+ instance is a set of information representing a coherent concept.
296
+
277
297
  There are two ways to define an information model in Lutaml::Model:
278
298
 
279
299
  * Inheriting from the `Lutaml::Model::Serializable` class
@@ -377,7 +397,7 @@ same class and all their attributes are equal.
377
397
  > # true
378
398
  ----
379
399
 
380
-
400
+ [[value-definition]]
381
401
  == Value types
382
402
 
383
403
  === General types
@@ -565,6 +585,7 @@ part lost due to the inability of JSON to handle high-precision date-time.
565
585
  ====
566
586
 
567
587
 
588
+ [[attribute-definition]]
568
589
  == Attributes
569
590
 
570
591
 
@@ -616,6 +637,56 @@ puts s.established
616
637
  ----
617
638
  ====
618
639
 
640
+ ==== Restricting the value of an attribute
641
+
642
+ The `restrict` class method is used to update or refine the validation rules for an attribute that has already been defined. This allows you to apply additional or stricter constraints to an existing attribute without redefining it.
643
+
644
+ .Using the `restrict` class method to update the options of an existing attribute
645
+ [example]
646
+ ====
647
+ [source,ruby]
648
+ ----
649
+ class Studio < Lutaml::Model::Serializable
650
+ attribute :name, :string
651
+ restrict :name, collection: 1..3, pattern: /[A-Za-z]+/
652
+ end
653
+ ----
654
+ ====
655
+
656
+ .Apply different restrictions to the existing attribute in multiple subclasses
657
+ [example]
658
+ ====
659
+ [source,ruby]
660
+ ----
661
+ class Document < Lutaml::Model::Serializable
662
+ attribute :status, :string
663
+ end
664
+
665
+ class DraftDocument < Document
666
+ # Only allow "draft" or "in_review" as valid statuses for drafts
667
+ restrict :status, values: %w[draft in_review]
668
+ end
669
+
670
+ class PublishedDocument < Document
671
+ # Only allow "published" or "archived" as valid statuses for published documents
672
+ restrict :status, values: %w[published archived]
673
+ end
674
+
675
+ # Usage
676
+ # Call .validate! to trigger validation and raise an error if the value is not allowed
677
+ Document.new(status: "draft").validate! # valid, there are no validation rules for `Document`
678
+ Document.new(status: "published").validate! # valid, there are no validation rules for `Document`
679
+ DraftDocument.new(status: "draft").validate! # valid
680
+ DraftDocument.new(status: "in_review").validate! # valid
681
+ DraftDocument.new(status: "published").validate! # raises error (not allowed)
682
+ PublishedDocument.new(status: "published").validate! # valid
683
+ PublishedDocument.new(status: "archived").validate! # valid
684
+ PublishedDocument.new(status: "draft").validate! # raises error (not allowed)
685
+ ----
686
+ ====
687
+
688
+ All options that are supported by the `attribute` class method are also supported by the `restrict` method. Any unsupported option passed to `restrict` will result in a `Lutaml::Model::InvalidAttributeOptionsError` being raised.
689
+
619
690
  === Polymorphic attributes
620
691
 
621
692
  ==== General
@@ -821,7 +892,7 @@ end
821
892
  <1> The name of the polymorphic attribute.
822
893
  <2> The polymorphic superclass class.
823
894
  <3> Any options for the attribute.
824
- <4> The `polymorphic` option that determines the acceptable polymorphic subclasses.
895
+ <4> The `polymorphic` option that determines the acceptable polymorphic subclasses, or just `true`.
825
896
  <5> The polymorphic subclasses.
826
897
 
827
898
  The `polymorphic` option is an array of polymorphic subclasses that the
@@ -864,6 +935,25 @@ end
864
935
  ----
865
936
  ====
866
937
 
938
+ * If the attribute (collection or not) is meant to contain instances belonging
939
+ to any polymorphic subclass of a defined base class, then set the `polymorphic:
940
+ true` option.
941
+ +
942
+ [example]
943
+ ====
944
+ In the following code, `ReferenceSet` is a class that has a polymorphic
945
+ attribute `references`. The `references` attribute can accept instances of
946
+ any polymorphic subclass of the `Reference` base class, so `polymorphic: true`
947
+ is set.
948
+
949
+ [source,ruby]
950
+ ----
951
+ class ReferenceSet < Lutaml::Model::Serializable
952
+ attribute :references, Reference, collection: true, polymorphic: true
953
+ end
954
+ ----
955
+ ====
956
+
867
957
  * If the attribute (collection or not) is meant to contain instances belonging
868
958
  to more than one polymorphic subclass, then those acceptable polymorphic
869
959
  subclasses should be explicitly specified in the `polymorphic: [...]` option.
@@ -1485,9 +1575,13 @@ puts SomeModel.new.to_yaml
1485
1575
  ----
1486
1576
  ====
1487
1577
 
1578
+
1488
1579
  === Derived attributes
1489
1580
 
1490
- A derived attribute is computed dynamically based on an instance method instead of storing a static value. It is defined using the `method:` option.
1581
+ A derived attribute has a value computed dynamically on evaluation of an
1582
+ instance method.
1583
+
1584
+ It is defined using the `method:` option.
1491
1585
 
1492
1586
  Syntax:
1493
1587
 
@@ -1573,6 +1667,7 @@ attributes.
1573
1667
  * The last attribute `completeName` is optional.
1574
1668
  ====
1575
1669
 
1670
+ NOTE: The `choice` directive can be used with `import_model_attributes`. For more details, see <<import-model-attributes-inside-choice, Using import_model_attributes inside a choice block>>.
1576
1671
 
1577
1672
 
1578
1673
  === Importable models for reuse
@@ -1629,6 +1724,7 @@ class ComplexType < Lutaml::Model::Serializable
1629
1724
 
1630
1725
  xml do
1631
1726
  root "GroupOfItems"
1727
+
1632
1728
  map_attribute "tag", to: :tag
1633
1729
  map_content to: :content
1634
1730
  map_element :group, to: :group
@@ -1660,6 +1756,161 @@ end
1660
1756
  ----
1661
1757
  ====
1662
1758
 
1759
+ [[import-model-mappings-inside-sequence]]
1760
+ ==== Using `import_model_mappings` inside a `sequence`
1761
+
1762
+ You can use `import_model_mappings` within a `sequence` block to include the element mappings from another model. This is useful for composing complex XML structures from reusable model components.
1763
+
1764
+ The element mappings will be imported inside this specific `sequence` block that calls the import method, rest of the mappings like `content`, `attributes`, etc. will be inserted at the class level.
1765
+
1766
+ NOTE: `import_model` and `import_model_attributes` are not supported inside a `sequence` block.
1767
+
1768
+ [example]
1769
+ ====
1770
+ [source,ruby]
1771
+ ----
1772
+ class Address < Lutaml::Model::Serializable
1773
+ attribute :street, :string
1774
+ attribute :city, :string
1775
+ attribute :zip, :string
1776
+
1777
+ xml do
1778
+ no_root
1779
+
1780
+ map_element :street, to: :street
1781
+ map_element :city, to: :city
1782
+ map_element :zip, to: :zip
1783
+ end
1784
+ end
1785
+
1786
+ class Person < Lutaml::Model::Serializable
1787
+ attribute :name, :string
1788
+ import_model_attributes Address
1789
+
1790
+ xml do
1791
+ root "Person"
1792
+
1793
+ map_element :name, to: :name
1794
+ sequence do
1795
+ import_model_mappings Address
1796
+ end
1797
+ end
1798
+ end
1799
+
1800
+ # Example XML output:
1801
+ valid_xml = <<~XML
1802
+ <Person>
1803
+ <name>John Doe</name>
1804
+ <street>123 Main St</street>
1805
+ <city>Metropolis</city>
1806
+ <zip>12345</zip>
1807
+ </Person>
1808
+ XML
1809
+ invalid_xml = <<~XML
1810
+ <Person>
1811
+ <name>John Doe</name>
1812
+ <street>123 Main St</street>
1813
+ <zip>12345</zip>
1814
+ </Person>
1815
+ XML
1816
+ Person.from_xml(valid_xml) # #<Person:0x00000002d56b3988 @city="Metropolis", @name="John Doe", @street="123 Main St", @zip="12345">
1817
+ Person.from_xml(invalid_xml) # raises `Element `zip` does not match the expected sequence order element `city` (Lutaml::Model::IncorrectSequenceError)`
1818
+ ----
1819
+ ====
1820
+
1821
+ [[import-model-attributes-inside-choice]]
1822
+ ==== Using import_model_attributes inside a choice block
1823
+
1824
+ You can use `import_model_attributes` within a `choice` block to allow a model to accept one or more sets of attributes from other models, with flexible cardinality. This is especially useful when you want to allow a user to provide one or more alternative forms of information (e.g., contact methods) in your model.
1825
+
1826
+ For example, suppose you want a `Person` model that can have either an `email`, a `phone`, or both as contact information. You can define `ContactEmail` and `ContactPhone` as importable models, and then use `import_model_attributes` for both, inside a `choice` block in the `Person` model.
1827
+
1828
+ NOTE: The `import_model_attributes` method is used to import the attributes from the other model into the current model. The imported attributes will be associated to the `choice` block that calls the import method.
1829
+
1830
+ [example]
1831
+ ====
1832
+ [source,ruby]
1833
+ ----
1834
+ class ContactEmail < Lutaml::Model::Serializable
1835
+ attribute :email, :string
1836
+
1837
+ xml do
1838
+ no_root
1839
+
1840
+ map_element :email, to: :email
1841
+ end
1842
+ end
1843
+
1844
+ class ContactPhone < Lutaml::Model::Serializable
1845
+ attribute :phone, :string
1846
+
1847
+ xml do
1848
+ no_root
1849
+
1850
+ map_element :phone, to: :phone
1851
+ end
1852
+ end
1853
+
1854
+ class Person < Lutaml::Model::Serializable
1855
+ # Allow either or both contact methods, but at least one must be present
1856
+ choice(min: 1, max: 2) do
1857
+ import_model_attributes ContactEmail
1858
+ import_model_attributes ContactPhone
1859
+ end
1860
+
1861
+ xml do
1862
+ root "Person"
1863
+
1864
+ map_element :email, to: :email
1865
+ map_element :phone, to: :phone
1866
+ end
1867
+ end
1868
+
1869
+ valid_xml = <<~XML
1870
+ <Person>
1871
+ <email>john.doe@example.com</email>
1872
+ <phone>1234567890</phone>
1873
+ </Person>
1874
+ XML
1875
+
1876
+ Person.from_xml(valid_xml).validate! # #<Person:0x00000002d0e27fe8 @email="john.doe@example.com", @phone="1234567890">
1877
+
1878
+ invalid_xml = <<~XML
1879
+ <Person></Person>
1880
+ XML
1881
+
1882
+ Person.from_xml(invalid_xml).validate! # raises `Lutaml::Model::ValidationError` error
1883
+ ----
1884
+ ====
1885
+
1886
+ ==== Using register functionality
1887
+
1888
+ The register functionality is useful when you want to reference or reuse a model by a symbolic name (e.g., across files or in dynamic scenarios), rather than by direct class reference.
1889
+
1890
+ .Importing a model using a `Register`
1891
+ [example]
1892
+ ====
1893
+ [source,ruby]
1894
+ ----
1895
+ register = Lutaml::Model::Register.new(:importable_model)
1896
+ register.register_model(GroupOfItems, id: :group_of_items)
1897
+ ----
1898
+
1899
+ The `id: :group_of_items` assigns a symbolic name to the registered model, which can then be used in `import_model :group_of_items`.
1900
+
1901
+ [source,ruby]
1902
+ ----
1903
+ class GroupOfSubItems < Lutaml::Model::Serializable
1904
+ import_model :group_of_items
1905
+ end
1906
+ ----
1907
+ ====
1908
+
1909
+ The `import_model :group_of_items` will behave the same as `import_model GroupOfItems` except the class is resolved from the provided `register`.
1910
+
1911
+ NOTE: All the `import_*` methods support the use of `register` functionality.
1912
+
1913
+ NOTE: For more details on registers, see <<custom_registers, Custom Registers>>.
1663
1914
 
1664
1915
  [[attribute-value-transform]]
1665
1916
  === Attribute value transform
@@ -1973,22 +2224,1205 @@ For the following XML snippet:
1973
2224
  @name="John Doe",
1974
2225
  @ordered=nil>
1975
2226
  ----
1976
- ====
2227
+ ====
2228
+
2229
+
2230
+ == Collections
2231
+
2232
+ === General
2233
+
2234
+ Collections are used to represent a contained group of multiple instances of models.
2235
+
2236
+ Typically, a collection represents an "Array" or a "Set" in information modeling
2237
+ and programming languages. In LutaML, a collection represents an array of model
2238
+ instances.
2239
+
2240
+ Models in a collection may be:
2241
+
2242
+ * constrained to be of a single kind;
2243
+
2244
+ * constrained to be of multiple kinds sharing common characteristics;
2245
+
2246
+ * unbounded of any kind.
2247
+
2248
+ LutaML Model provides the `Lutaml::Model::Collection` class for defining
2249
+ collections of model instances.
2250
+
2251
+ === Configuration
2252
+
2253
+ ==== All formats
2254
+
2255
+ The `instances` directive defined at the `Collection` class level is used to
2256
+ define the collection attribute and the model type of the collection elements.
2257
+
2258
+ Syntax:
2259
+
2260
+ [source,ruby]
2261
+ ----
2262
+ class MyCollection < Lutaml::Model::Collection
2263
+ instances {attribute}, {ModelType}
2264
+ end
2265
+ ----
2266
+
2267
+ Where,
2268
+
2269
+ `attribute`:: The name of the attribute that contains the collection.
2270
+ `ModelType`:: The model type of the collection elements.
2271
+
2272
+
2273
+ ==== Mapping instances: key-value formats only
2274
+
2275
+ The `map_instances` directive is only used in the `key_value` block.
2276
+
2277
+ Syntax:
2278
+
2279
+ [source,ruby]
2280
+ ----
2281
+ class MyCollection < Lutaml::Model::Collection
2282
+ instances {attribute}, ModelType
2283
+
2284
+ key_value do
2285
+ map_instances to: {attribute}
2286
+ end
2287
+ end
2288
+ ----
2289
+
2290
+ Where,
2291
+
2292
+ `attribute`:: The name of the attribute that contains model instances.
2293
+
2294
+ This directive maps individual array elements to the defined `instances`
2295
+ attribute. These are the items considered part of the Collection and reflected
2296
+ as Enumerable elements.
2297
+
2298
+
2299
+ ==== Mapping instances: XML only
2300
+
2301
+ In the `xml` block, the `map_element`, `map_attribute` directives are used instead.
2302
+
2303
+ These directives map individual array elements to the defined `instances`
2304
+ attribute. These are the items considered part of the Collection and reflected
2305
+ as Enumerable elements.
2306
+
2307
+ Syntax for an element collection:
2308
+
2309
+ [source,ruby]
2310
+ ----
2311
+ class MyCollection < Lutaml::Model::Collection
2312
+ instances {attribute}, ModelType
2313
+
2314
+ xml do
2315
+ map_element "element-name", to: {attribute}
2316
+ end
2317
+ end
2318
+ ----
2319
+
2320
+ Where,
2321
+
2322
+ `element-name`:: The name of the XML element of each model instance.
2323
+
2324
+
2325
+ Syntax for an attribute collection:
2326
+
2327
+ [source,ruby]
2328
+ ----
2329
+ class MyCollection < Lutaml::Model::Collection
2330
+ instances {attribute}, ModelType
2331
+ xml do
2332
+ map_attribute "attribute-name", to: {attribute}
2333
+ end
2334
+ end
2335
+ ----
2336
+
2337
+ Where,
2338
+
2339
+ `attribute-name`:: The name of the XML attribute that contains all model instances.
2340
+
2341
+
2342
+ === Collection types
2343
+
2344
+ A LutaML collections is used for a number of scenarios:
2345
+
2346
+ * Root collections (for key-value formats)
2347
+ * Named collections
2348
+ * Keyed collections (for key-value formats)
2349
+ * Attribute collections
2350
+ * Nested collections
2351
+ // * Polymorphic collections
2352
+ // * Polymorphic attribute collections
2353
+
2354
+
2355
+ === Root collections (key-value formats only)
2356
+
2357
+ ==== General
2358
+
2359
+ TODO: This case needs to be fixed for JSON.
2360
+
2361
+ A root collection is a collection that is not contained within a parent
2362
+ collection.
2363
+
2364
+ Root collections only apply to key-value serialization formats.
2365
+ The XML format does not support root collections.
2366
+
2367
+ NOTE: The https://www.w3.org/TR/xml11/[XML standard] mandates the existence
2368
+ of a non-repeated "root element" in an XML document. This means that a valid XML
2369
+ document must have a root element, and all elements in an XML document must
2370
+ exist within the root. This is why an XML document cannot be a "root collection".
2371
+
2372
+ NOTE: A root collection cannot be represented using a non-collection model.
2373
+
2374
+ Root collections store multiple instances of the same model type at the root
2375
+ level. In other words, these are model instances that do not have a defined
2376
+ container at the level of the LutaML Model.
2377
+
2378
+ There are two kinds of root collections depending on the type of the instance
2379
+ value:
2380
+
2381
+ "Root value collection":: the value is a "primitive type"
2382
+
2383
+ "Root object collection":: the value is a "model instance"
2384
+
2385
+ Regardless of the type of root collection, the instance in a collection is
2386
+ always a LutaML model instance.
2387
+
2388
+
2389
+ ==== Root value collections
2390
+
2391
+ A root value collection is a collection that directly contains values of a
2392
+ primitive type.
2393
+
2394
+ .Simple root collection with each instance being a value
2395
+ [example]
2396
+ ====
2397
+ [source,yaml]
2398
+ ----
2399
+ ---
2400
+ - Item One
2401
+ - Item Two
2402
+ - Item Three
2403
+ ----
2404
+
2405
+ [source,json]
2406
+ ----
2407
+ [
2408
+ "Item One",
2409
+ "Item Two",
2410
+ "Item Three"
2411
+ ]
2412
+ ----
2413
+ ====
2414
+
2415
+
2416
+ Syntax:
2417
+
2418
+ [source,ruby]
2419
+ ----
2420
+ class MyCollection < Lutaml::Model::Collection
2421
+ instances :items, ModelType
2422
+ end
2423
+
2424
+ class ModelType < Lutaml::Model::Serializable
2425
+ attribute :name, :string
2426
+ end
2427
+ ----
2428
+
2429
+ .Handling a root collection where each instance is a value
2430
+ [example]
2431
+ ====
2432
+ Code:
2433
+
2434
+ [source,ruby]
2435
+ ----
2436
+ class Title < Lutaml::Model::Serializable
2437
+ attribute :content, :string
2438
+ end
2439
+
2440
+ class TitleCollection < Lutaml::Model::Collection
2441
+ instances :titles, Title
2442
+
2443
+ key_value do
2444
+ no_root # default
2445
+ map_instances to: :titles
2446
+ end
2447
+ end
2448
+ ----
2449
+
2450
+ Data:
2451
+
2452
+ [source,yaml]
2453
+ ----
2454
+ ---
2455
+ - Title One
2456
+ - Title Two
2457
+ - Title Three
2458
+ ----
2459
+
2460
+ [source,json]
2461
+ ----
2462
+ [
2463
+ "Title One",
2464
+ "Title Two",
2465
+ "Title Three"
2466
+ ]
2467
+ ----
2468
+
2469
+ Usage:
2470
+
2471
+ [source,ruby]
2472
+ ----
2473
+ titles = TitleCollection.from_yaml(yaml_data)
2474
+ titles.count
2475
+ # => 3
2476
+ titles.first.content
2477
+ # => "Title One"
2478
+ ----
2479
+ ====
2480
+
2481
+
2482
+ ==== Root object collections
2483
+
2484
+ A root object collection is a collection that directly contains model instances,
2485
+ each containing at least one serialized attribute.
2486
+
2487
+ .Simple root collection in YAML with each instance being a models with an attribute `name`
2488
+ [example]
2489
+ ====
2490
+ [source,yaml]
2491
+ ----
2492
+ ---
2493
+ - name: Item One
2494
+ - name: Item Two
2495
+ - name: Item Three
2496
+ ----
2497
+
2498
+ [source,json]
2499
+ ----
2500
+ [
2501
+ {"name": "Item One"},
2502
+ {"name": "Item Two"},
2503
+ {"name": "Item Three"}
2504
+ ]
2505
+ ----
2506
+ ====
2507
+
2508
+
2509
+ .Handling a root collection where each instance is defined by a model with attributes
2510
+ [example]
2511
+ ====
2512
+ Code:
2513
+
2514
+ [source,ruby]
2515
+ ----
2516
+ class Title < Lutaml::Model::Serializable
2517
+ attribute :content, :string
2518
+ end
2519
+
2520
+ class TitleCollection < Lutaml::Model::Collection
2521
+ instances :titles, Title
2522
+
2523
+ key_value do
2524
+ no_root # default
2525
+ map_instances to: :titles
2526
+ end
2527
+ end
2528
+ ----
2529
+
2530
+ Data:
2531
+
2532
+ [source,yaml]
2533
+ ----
2534
+ ---
2535
+ - content: Title One
2536
+ - content: Title Two
2537
+ - content: Title Three
2538
+ ----
2539
+
2540
+ [source,json]
2541
+ ----
2542
+ [
2543
+ {"content": "Title One"},
2544
+ {"content": "Title Two"},
2545
+ {"content": "Title Three"}
2546
+ ]
2547
+ ----
2548
+
2549
+ Usage:
2550
+
2551
+ [source,ruby]
2552
+ ----
2553
+ titles = TitleCollection.from_yaml(yaml_data)
2554
+ titles.count
2555
+ # => 3
2556
+ titles.first.content
2557
+ # => "Title One"
2558
+ ----
2559
+ ====
2560
+
2561
+
2562
+ === Named collections
2563
+
2564
+ ==== General
2565
+
2566
+ Named collections are collections wrapped inside a name or a key. The "name" of
2567
+ the collection serves as the container root of its contained model instances.
2568
+
2569
+ The named collection setup applies to XML and key-value serialization formats.
2570
+
2571
+ In a named collection setup, the collection is defined as a
2572
+ Lutaml::Model::Collection class, and each instance is defined as a
2573
+ Lutaml::Model::Serializable class.
2574
+
2575
+ There are two kinds of named collections depending on the type of the instance
2576
+ value:
2577
+
2578
+ "Named value collection":: the value is a "primitive type"
2579
+
2580
+ "Named object collection":: the value is a "model instance"
2581
+
2582
+ Regardless of the name of root collection, the instance in a collection is
2583
+ always a LutaML model instance.
2584
+
2585
+
2586
+ ==== Named value collections
2587
+
2588
+ A named value collection is a collection that contains values of a
2589
+ primitive type.
2590
+
2591
+ .Named value collection in XML with models each containing an element with content
2592
+ [source,xml]
2593
+ ----
2594
+ <names>
2595
+ <name>Item One</name>
2596
+ <name>Item Two</name>
2597
+ <name>Item Three</name>
2598
+ </names>
2599
+ ----
2600
+
2601
+ .Named value collection in YAML with models each containing a value
2602
+ [source,yaml]
2603
+ ----
2604
+ ---
2605
+ names:
2606
+ - Item One
2607
+ - Item Two
2608
+ - Item Three
2609
+ ----
2610
+
2611
+ Syntax:
2612
+
2613
+ [source,ruby]
2614
+ ----
2615
+ class MyCollection < Lutaml::Model::Collection
2616
+ instances :items, ModelType
2617
+
2618
+ xml do
2619
+ root "name-of-xml-container-element"
2620
+ end
2621
+
2622
+ key_value do
2623
+ root "name-of-key-value-container-element"
2624
+ end
2625
+ end
2626
+
2627
+ class ModelType < Lutaml::Model::Serializable
2628
+ attribute :name, :string
2629
+ end
2630
+ ----
2631
+
2632
+ A named collection can alternatively be implemented as a non-collection model
2633
+ ("Model class with an attribute") that contains the collection of instances. In
2634
+ this case, the attribute will be an Array object, which does not contain
2635
+ additional attributes and methods.
2636
+
2637
+ .Handling a named collection with instance elements directly containing values
2638
+ [example]
2639
+ ====
2640
+ [source,ruby]
2641
+ ----
2642
+ class Title < Lutaml::Model::Serializable
2643
+ attribute :title, :string
2644
+
2645
+ xml do
2646
+ root "title"
2647
+ map_content to: :title
2648
+ end
2649
+ end
2650
+
2651
+ class DirectTitleCollection < Lutaml::Model::Collection
2652
+ instances :items, Title
2653
+
2654
+ xml do
2655
+ root "titles"
2656
+ map_element "title", to: :items
2657
+ end
2658
+
2659
+ key_value do
2660
+ map_instances to: :items
2661
+ end
2662
+ end
2663
+ ----
2664
+
2665
+ [source,xml]
2666
+ ----
2667
+ <titles>
2668
+ <title>Title One</title>
2669
+ <title>Title Two</title>
2670
+ <title>Title Three</title>
2671
+ </titles>
2672
+ ----
2673
+
2674
+ [source,yaml]
2675
+ ----
2676
+ ---
2677
+ titles:
2678
+ - Title One
2679
+ - Title Two
2680
+ - Title Three
2681
+ ----
2682
+
2683
+ [source,json]
2684
+ ----
2685
+ {
2686
+ "titles": [
2687
+ "Title One",
2688
+ "Title Two",
2689
+ "Title Three"
2690
+ ]
2691
+ }
2692
+ ----
2693
+
2694
+ [source,ruby]
2695
+ ----
2696
+ titles = DirectTitleCollection.from_yaml(yaml_data)
2697
+ titles.count
2698
+ # => 3
2699
+ titles.first.title
2700
+ # => "Title One"
2701
+ titles.last.title
2702
+ # => "Title Three"
2703
+ ----
2704
+ ====
2705
+
2706
+
2707
+ ==== Named object collections
2708
+
2709
+ A named object collection is a collection that contains model instances,
2710
+ each containing at least one serialized attribute.
2711
+
2712
+ NOTE: A named object collection can alternatively be implemented as a
2713
+ non-collection model ("Model class with an attribute") that contains the
2714
+ collection of instances. In this case, the attribute will be an Array object,
2715
+ which does not contain additional attributes and methods.
2716
+
2717
+
2718
+ .Named object collection in XML with instances each containing an element with a model attribute
2719
+ [source,xml]
2720
+ ----
2721
+ <names>
2722
+ <name><content>Item One</content></name>
2723
+ <name><content>Item Two</content></name>
2724
+ <name><content>Item Three</content></name>
2725
+ </names>
2726
+ ----
2727
+
2728
+ .Named object collection in YAML with instances each containing a model attribute
2729
+ [source,yaml]
2730
+ ----
2731
+ ---
2732
+ names:
2733
+ - name: Item One
2734
+ - name: Item Two
2735
+ - name: Item Three
2736
+ ----
2737
+
2738
+
2739
+ .Named object collection with each instance containing at least one model attribute
2740
+ [example]
2741
+ ====
2742
+ Data:
2743
+
2744
+ [source,xml]
2745
+ ----
2746
+ <titles>
2747
+ <title><content>Title One</content></title>
2748
+ <title><content>Title Two</content></title>
2749
+ <title><content>Title Three</content></title>
2750
+ </titles>
2751
+ ----
2752
+
2753
+ [source,yaml]
2754
+ ----
2755
+ ---
2756
+ titles:
2757
+ - title: Title One
2758
+ - title: Title Two
2759
+ - title: Title Three
2760
+ ----
2761
+
2762
+ [source,json]
2763
+ ----
2764
+ {
2765
+ "titles": [
2766
+ {"title": "Title One"},
2767
+ {"title": "Title Two"},
2768
+ {"title": "Title Three"}
2769
+ ]
2770
+ }
2771
+ ----
2772
+
2773
+ Code:
2774
+
2775
+ [source,ruby]
2776
+ ----
2777
+ class Title < Lutaml::Model::Serializable
2778
+ attribute :title, :string
2779
+
2780
+ xml do
2781
+ root "title"
2782
+ map_element "content", to: :title
2783
+ end
2784
+
2785
+ key_value do
2786
+ map "title", to: :title
2787
+ end
2788
+ end
2789
+
2790
+ class TitleCollection < Lutaml::Model::Collection
2791
+ instances :items, Title
2792
+
2793
+ xml do
2794
+ root "titles"
2795
+ map_element 'title', to: :items
2796
+ end
2797
+
2798
+ key_value do
2799
+ root "titles"
2800
+ map_instances to: :items
2801
+ end
2802
+ end
2803
+ ----
2804
+
2805
+ Usage:
2806
+
2807
+ [source,ruby]
2808
+ ----
2809
+ titles = TitleCollection.from_yaml(yaml_data)
2810
+ titles.count
2811
+ # => 3
2812
+ titles.first.title
2813
+ # => "Title One"
2814
+ titles.last.title
2815
+ # => "Title Three"
2816
+ ----
2817
+ ====
2818
+
2819
+
2820
+ === Attribute collection class
2821
+
2822
+ A model attribute that is a collection can be contained within a custom
2823
+ collection class.
2824
+
2825
+ A custom collection class can be defined to provide custom behavior for the
2826
+ collection inside a non-collection model, with attributes using
2827
+ `collection: true`.
2828
+
2829
+ Syntax:
2830
+
2831
+ [source,ruby]
2832
+ ----
2833
+ class MyModel < Lutaml::Model::Serializable
2834
+ attribute {model-attribute}, ModelType, collection: MyCollection
2835
+ end
2836
+
2837
+ class MyCollection < Lutaml::Model::Collection
2838
+ instances {instance-name}, ModelType
2839
+
2840
+ # Custom behavior for the collection
2841
+ def custom_method
2842
+ # Custom logic here
2843
+ end
2844
+ end
2845
+ ----
2846
+
2847
+ .Using a custom collection class for custom collection behavior
2848
+ [example]
2849
+ ====
2850
+ Data:
2851
+
2852
+ [source,xml]
2853
+ ----
2854
+ <titles>
2855
+ <title>Title One</title>
2856
+ <title>Title Two</title>
2857
+ <title>Title Three</title>
2858
+ </titles>
2859
+ ----
2860
+
2861
+ [source,yaml]
2862
+ ----
2863
+ titles:
2864
+ - title: Title One
2865
+ - title: Title Two
2866
+ - title: Title Three
2867
+ ----
2868
+
2869
+
2870
+ [source,ruby]
2871
+ ----
2872
+ class StringParts < Lutaml::Model::Collection
2873
+ instances :parts, :string
2874
+
2875
+ def to_s
2876
+ parts.join(' -- ')
2877
+ end
2878
+ end
2879
+
2880
+ class BibliographicItem < Lutaml::Model::Serializable
2881
+ attribute :title_parts, :string, collection: StringParts
2882
+
2883
+ xml do
2884
+ root "titles"
2885
+ map_element "title", to: :title_parts
2886
+ end
2887
+
2888
+ key_value do
2889
+ root "titles"
2890
+ map_instances to: :title_parts
2891
+ end
2892
+
2893
+ def render_title
2894
+ title_parts.to_s
2895
+ end
2896
+ end
2897
+ ----
2898
+
2899
+ [source,ruby]
2900
+ ----
2901
+ > bib_item = BibliographicItem.from_xml(xml_data)
2902
+ > bib_item.title_parts
2903
+ > # StringParts:0x0000000104ac7240 @parts=["Title One", "Title Two", "Title Three"]
2904
+ > bib_item.render_title
2905
+ > # "Title One -- Title Two -- Title Three"
2906
+ ----
2907
+ ====
2908
+
2909
+
2910
+
2911
+ === Nested collections
2912
+
2913
+ TODO: This case needs to be fixed.
2914
+
2915
+ Collections can be nested within other models and define their own serialization
2916
+ rules.
2917
+
2918
+ Nested collections can be defined in the same way as root collections, but
2919
+ they are defined within the context of a parent model.
2920
+
2921
+ [example]
2922
+ ====
2923
+ Data:
2924
+
2925
+ [source,xml]
2926
+ ----
2927
+ <titles>
2928
+ <title-group>
2929
+ <artifact>
2930
+ <content>Title One</content>
2931
+ </artifact>
2932
+ <artifact>
2933
+ <content>Title Two</content>
2934
+ </artifact>
2935
+ <artifact>
2936
+ <content>Title Three</content>
2937
+ </artifact>
2938
+ </title-group>
2939
+ </titles>
2940
+ ----
2941
+
2942
+ [source,yaml]
2943
+ ----
2944
+ ---
2945
+ titles:
2946
+ title-group:
2947
+ - artifact:
2948
+ content: Title One
2949
+ - artifact:
2950
+ content: Title Two
2951
+ - artifact:
2952
+ content: Title Three
2953
+ ----
2954
+
2955
+ [source,ruby]
2956
+ ----
2957
+ class Title < Lutaml::Model::Serializable
2958
+ attribute :content, :string
2959
+ end
2960
+
2961
+ class TitleCollection < Lutaml::Model::Collection
2962
+ instances :items, Title
2963
+
2964
+ xml do
2965
+ root "title-group"
2966
+ map_element "artifact", to: :items
2967
+ end
2968
+ end
2969
+
2970
+ class BibItem < Lutaml::Model::Serializable
2971
+ attribute :titles, TitleCollection
2972
+
2973
+ xml do
2974
+ root "bibitem"
2975
+ # This overrides the collection's root "title-group"
2976
+ map_element "titles", to: :titles
2977
+ end
2978
+ end
2979
+ ----
2980
+ ====
2981
+
2982
+
2983
+ === Keyed collections (key-value serialization formats only)
2984
+
2985
+ ==== General
2986
+
2987
+ In key-value serialization formats, a key can be used to uniquely identify each
2988
+ instance. This usage allows for enforcing uniqueness in the collection.
2989
+
2990
+ A collection that contains keyed objects as its instances is commonly called a
2991
+ "keyed collection". A keyed object in a serialization format is an object
2992
+ identified with a unique key.
2993
+
2994
+ NOTE: The concept of keyed collections does not typically apply to XML
2995
+ collections.
2996
+
2997
+ There are two kinds of keyed collections depending on the type of the keyed
2998
+ value:
2999
+
3000
+ "keyed value collection":: the value is a "primitive type"
3001
+
3002
+ "keyed object collection":: the value is a "model instance"
3003
+
3004
+ Regardless of the type of keyed collections, the instance in a collection is
3005
+ always a LutaML model instance.
3006
+
3007
+
3008
+ ==== `map_key` method
3009
+
3010
+ The `map_key` method specifies that the unique key is to be moved into an
3011
+ attribute belonging to the instance model.
3012
+
3013
+ Syntax:
3014
+
3015
+ [source,ruby]
3016
+ ----
3017
+ key_value do
3018
+ map_key to_instance: {instance-attribute-name}
3019
+ end
3020
+ ----
3021
+
3022
+ Where,
3023
+
3024
+ `to_instance`:: Refers to the attribute name in the instance that contains the key.
3025
+ `{key_attribute}`:: The attribute name in the instance that contains the key.
3026
+
3027
+
3028
+ ==== `map_value` method
3029
+
3030
+ The `map_value` method specifies that the value (the object referenced by the
3031
+ unique key) is to be moved into an attribute belonging to the instance model.
3032
+
3033
+ Syntax:
3034
+
3035
+ [source,ruby]
3036
+ ----
3037
+ key_value do
3038
+ # basic pattern
3039
+ map_value {operation}: [*argument]
3040
+
3041
+ # Mapping the value object to a full instance through `to_instance`
3042
+ map_value to_instance: {instance-attribute-name}
3043
+
3044
+ # Mapping the value object to an attribute as_instance
3045
+ map_value as_attribute: {instance-attribute-name}
3046
+ end
3047
+ ----
3048
+
3049
+ ==== Keyed value collections
3050
+
3051
+ A keyed value collection is a collection where the keyed item in the serialization
3052
+ format is a primitive type (e.g. string, integer, etc.).
3053
+
3054
+ The instance item inside the collection is a model instance that contains both
3055
+ the serialized key and serialized value both as attributes inside the model.
3056
+
3057
+ All three `map_key`, `map_value`, and `map_instances` methods need to be used to
3058
+ define how instances are mapped in a keyed value collection.
3059
+
3060
+ .Creating a keyed value collection
3061
+ [example]
3062
+ ====
3063
+ [source,ruby]
3064
+ ----
3065
+ class AuthorAvailability < Lutaml::Model::Serializable
3066
+ attribute :id, :string
3067
+ attribute :available, :boolean
3068
+ end
3069
+
3070
+ class AuthorCollection < Lutaml::Model::Collection
3071
+ instances :authors, AuthorAvailability
3072
+
3073
+ key_value do
3074
+ map_key to_instance: :id # This refers to 'authors[].id'
3075
+ map_value as_attribute: :available # This refers to 'authors[].available'
3076
+ map_instances to: :authors
3077
+ end
3078
+ end
3079
+ ----
3080
+
3081
+ [source,yaml]
3082
+ ----
3083
+ ---
3084
+ author_01: true
3085
+ author_02: false
3086
+ author_03: true
3087
+ ----
3088
+
3089
+ [source,ruby]
3090
+ ----
3091
+ authors = AuthorCollection.from_yaml(yaml_data)
3092
+ authors.first.id
3093
+ # => "author_01"
3094
+ authors.first.available
3095
+ # => true
3096
+ ----
3097
+ ====
3098
+
3099
+
3100
+ ==== Keyed object collections
3101
+
3102
+ A keyed object collection is a collection where the keyed item in the
3103
+ serialization format contains multiple attributes.
3104
+
3105
+ The instance item inside the collection is a model instance that contains the
3106
+ serialized key as one attribute, and the serialized value attributes are all
3107
+ attributes inside the model.
3108
+
3109
+ Both the `map_key` and `map_instances` are used to define how instances are
3110
+ mapped in a keyed object collection.
3111
+
3112
+ .Creating a keyed object collection
3113
+ [example]
3114
+ ====
3115
+ [source,ruby]
3116
+ ----
3117
+ class Author < Lutaml::Model::Serializable
3118
+ attribute :id, :string
3119
+ attribute :name, :string
3120
+ end
3121
+
3122
+ class AuthorCollection < Lutaml::Model::Collection
3123
+ instances :authors, Author
3124
+
3125
+ key_value do
3126
+ map_key to_instance: :id # This refers to 'authors[].id'
3127
+ map_instances to: :authors
3128
+ end
3129
+ end
3130
+ ----
3131
+
3132
+ [source,yaml]
3133
+ ----
3134
+ ---
3135
+ author_01:
3136
+ name: Author One
3137
+ author_02:
3138
+ name: Author Two
3139
+ author_03:
3140
+ name: Author Three
3141
+ ----
3142
+
3143
+ [source,ruby]
3144
+ ----
3145
+ authors = AuthorCollection.from_yaml(yaml_data)
3146
+ authors.first.id
3147
+ # => "author_01"
3148
+ authors.first.name
3149
+ # => "Author One"
3150
+ ----
3151
+ ====
3152
+
3153
+
3154
+
3155
+
3156
+ === Behavior
3157
+
3158
+ ==== Enumerable interface
3159
+
3160
+ Collections implement the Ruby `Enumerable` interface, providing standard
3161
+ collection operations.
3162
+
3163
+ Collections allow the following sample `Enumerable` methods:
3164
+
3165
+ * `each` - Iterate over collection items
3166
+ * `map` - Transform collection items
3167
+ * `select` - Filter collection items
3168
+ * `find` - Find items matching criteria
3169
+ * `reduce` - Aggregate collection items
3170
+
3171
+ .Usage of the collection Enumerable interface
3172
+ [example]
3173
+ ====
3174
+ [source,ruby]
3175
+ ----
3176
+ # Filter items
3177
+ filtered = collection.filter { |item| item.id == "1" }
3178
+
3179
+ # Reject items
3180
+ rejected = collection.reject { |item| item.id == "1" }
3181
+
3182
+ # Select items
3183
+ selected = collection.select { |item| item.id == "1" }
3184
+
3185
+ # Map items
3186
+ mapped = collection.map { |item| item.name }
3187
+
3188
+ # Count items
3189
+ count = collection.count
3190
+ ----
3191
+ ====
3192
+
3193
+
3194
+ // ==== Collection validation
3195
+
3196
+ // Collections can define validation rules for their elements.
3197
+
3198
+ // [example]
3199
+ // ====
3200
+ // [source,ruby]
3201
+ // ----
3202
+ // class PublicationCollection < Lutaml::Model::Collection
3203
+ // instances(:publications, Publication) do
3204
+ // validates :year, numericality: { greater_than: 1900 }
3205
+
3206
+ // validate :must_have_author
3207
+
3208
+ // def must_have_author(publications)
3209
+ // publications.each do |publication|
3210
+ // next unless publication.author.nil?
3211
+ // errors.add(:author, "`#{publication.title}` must have an author")
3212
+ // end
3213
+ // end
3214
+ // end
3215
+ // end
3216
+ // ----
3217
+ // ====
3218
+
3219
+ ==== Initialization
3220
+
3221
+ Collections can be initialized with an array of items or through individual item
3222
+ addition.
3223
+
3224
+ [example]
3225
+ ====
3226
+ [source,ruby]
3227
+ ----
3228
+ # Empty collection
3229
+ collection = ItemCollection.new
3230
+
3231
+ # From an array of items
3232
+ collection = ItemCollection.new([item1, item2, item3])
3233
+
3234
+ # From an array of hashes
3235
+ collection = ItemCollection.new([
3236
+ { id: "1", name: "Item 1" },
3237
+ { id: "2", name: "Item 2" }
3238
+ ])
3239
+
3240
+ # Adding items later
3241
+ collection << Item.new(id: "3", name: "Item 3")
3242
+ ----
3243
+ ====
3244
+
3245
+ ==== Ordering
3246
+
3247
+ TODO: This case needs to be fixed.
3248
+
3249
+ Collections that maintain a specific ordering of elements.
3250
+
3251
+ Syntax:
3252
+
3253
+ [source,ruby]
3254
+ ----
3255
+ class MyCollection < Lutaml::Model::Collection
3256
+ instances {instances-name}, ModelType
3257
+ ordered by: {attribute-of-instance}, order: {:asc | :desc}
3258
+ end
3259
+ ----
3260
+
3261
+ Where,
3262
+
3263
+ `{instances-name}`:: name of the instances accessor within the collection
3264
+ `ModelType`:: The model type of the collection elements.
3265
+
3266
+ `{attribute-of-instance-or-proc}`:: How model instances are to be ordered by. Values supported are:
3267
+ `{attribute-of-instance}`::: Attribute name of an instance to be ordered by.
3268
+ `{proc}`::: Proc that returns a value to order by (same as `sort_by`), given the instance as input.
3269
+
3270
+ `order`::: Order direction of the value:
3271
+ `:asc`:::: Ascending order (default).
3272
+ `:desc`:::: Descending order.
3273
+
3274
+ .Ordered collection applied to a root collection
3275
+ [example]
3276
+ ====
3277
+ Data:
3278
+
3279
+ [source,xml]
3280
+ ----
3281
+ <items>
3282
+ <item id="3" name="Item Three"/>
3283
+ <item id="1" name="Item One"/>
3284
+ <item id="2" name="Item Two"/>
3285
+ </items>
3286
+ ----
3287
+
3288
+ [source,yaml]
3289
+ ----
3290
+ ---
3291
+ - id: 3
3292
+ name: Item Three
3293
+ - id: 1
3294
+ name: Item One
3295
+ - id: 2
3296
+ name: Item Two
3297
+ ----
3298
+
3299
+ [source,ruby]
3300
+ ----
3301
+ class Item < Lutaml::Model::Serializable
3302
+ attribute :id, :string
3303
+ attribute :name, :string
3304
+
3305
+ xml do
3306
+ map_attribute "id", to: :id
3307
+ map_attribute "name", to: :name
3308
+ end
3309
+ end
3310
+
3311
+ class OrderedItemCollection < Lutaml::Model::Collection
3312
+ instances :items, Item
3313
+ ordered by: :id, order: :desc
3314
+
3315
+ xml do
3316
+ root "items"
3317
+ map_element "item", to: :items
3318
+ end
3319
+
3320
+ key_value do
3321
+ no_root
3322
+ map_instances to: :items
3323
+ end
3324
+ end
3325
+ ----
3326
+
3327
+ [source,ruby]
3328
+ ----
3329
+ > collection = OrderedItemCollection.from_xml(xml_data)
3330
+ > collection.map(&:id)
3331
+ > # ["3", "2", "1"]
3332
+
3333
+ > collection = OrderedItemCollection.from_yaml(yaml_data)
3334
+ > collection.map(&:id)
3335
+ > # ["3", "2", "1"]
3336
+ ----
3337
+ ====
3338
+
3339
+
3340
+
3341
+ // ==== Polymorphic collections
3342
+
3343
+ // Collections can contain instances of different model classes that share a common
3344
+ // base class.
3345
+
3346
+ // The polymorphic options for attributes are also applied here.
3347
+
3348
+ // [example]
3349
+ // ====
3350
+ // [source,ruby]
3351
+ // ----
3352
+ // class PolymorphicItemCollection < Lutaml::Model::Collection
3353
+ // instances :items, Item, polymorphic: true
3354
+
3355
+ // xml do
3356
+ // root "items"
3357
+ // map_element "item", to: :items
3358
+ // end
3359
+
3360
+ // key_value do
3361
+ // root "items"
3362
+ // map_instances to: :items
3363
+ // end
3364
+ // end
3365
+ // ----
3366
+ // ====
3367
+
3368
+
3369
+ == Serialization model mappings
3370
+
3371
+ === General
3372
+
3373
+ Lutaml::Model allows you to translate a data model into serialization models of
3374
+ various serialization formats.
3375
+
3376
+ Depending on the serialization format, different methods are supported for
3377
+ defining serialization and deserialization mappings.
3378
+
3379
+ A serialization model mapping is defined using a format-specific DSL block
3380
+ in this syntax:
3381
+
3382
+ [source,ruby]
3383
+ ----
3384
+ class Example < Lutaml::Model::Serializable
3385
+ {format-short-name} do <1>
3386
+ # ...
3387
+ end
3388
+ end
3389
+ ----
3390
+ <1> `{format-short-name}` is the serialization format short name.
1977
3391
 
1978
- == Serialization model mappings
3392
+ There are two kinds of serialization models:
1979
3393
 
1980
- === General
3394
+ * Represents a singular model (maps to a Lutaml::Model::Serializable)
3395
+ * Represents a group/collection of models (maps to Lutaml::Model::Collection)
1981
3396
 
1982
- Lutaml::Model allows you to translate a data model into serialization models of
1983
- various serialization formats including XML, Hash, JSON, YAML, and TOML.
3397
+ A collection contains instances of singular models, and therefore is always
3398
+ inextricably linked to an underlying serialization format for singular models.
3399
+ For instance, JSONL represents a collection (itself being invalid JSON) that
3400
+ uses JSON for singular models.
1984
3401
 
1985
- Depending on the serialization format, different methods are supported for
1986
- defining serialization and deserialization mappings.
3402
+ The supported serialization formats and their short names are defined as follows:
3403
+
3404
+ Model serialization formats::
3405
+
3406
+ `xml`::: XML
3407
+ `hsh`::: Hash
3408
+ +
3409
+ NOTE: Yes a 3-letter abbreviation for Hash!
3410
+
3411
+ `json`::: JSON
3412
+ `yaml`::: YAML
3413
+ `toml`::: TOML
3414
+ `key_value`::: Key-value format, a shorthand for all key-value formats (including
3415
+ JSON, YAML and TOML).
3416
+
3417
+ Collection serialization formats::
3418
+
3419
+ `jsonl`::: JSONL (JSON Lines)
3420
+ `yamls`::: YAML Stream (multi-document format)
1987
3421
 
1988
- Serialization model mappings are defined under the `xml`, `hsh`, `json`, `yaml`,
1989
- and `toml` blocks.
1990
3422
 
1991
3423
  .Using the `xml`, `hsh`, `json`, `yaml`, `toml` and `key_value` blocks to define serialization mappings
3424
+ [example]
3425
+ ====
1992
3426
  [source,ruby]
1993
3427
  ----
1994
3428
  class Example < Lutaml::Model::Serializable
@@ -2017,6 +3451,20 @@ class Example < Lutaml::Model::Serializable
2017
3451
  end
2018
3452
  end
2019
3453
  ----
3454
+ ====
3455
+
3456
+ .Using the `jsonl` block to define serialization mappings to a collection
3457
+ [example]
3458
+ ====
3459
+ [source,ruby]
3460
+ ----
3461
+ class Example < Lutaml::Model::Collection
3462
+ jsonl do
3463
+ # ...
3464
+ end
3465
+ end
3466
+ ----
3467
+ ====
2020
3468
 
2021
3469
 
2022
3470
  === XML
@@ -2988,6 +4436,7 @@ HERE
2988
4436
  ----
2989
4437
  ====
2990
4438
 
4439
+ NOTE: For importing model mappings inside a `sequence` block, refer to <<import-model-mappings-inside-sequence, Importing model mappings inside a `sequence`>>.
2991
4440
 
2992
4441
  [[xml-schema-location]]
2993
4442
  ==== Automatic support of `xsi:schemaLocation`
@@ -3453,11 +4902,20 @@ This XML snippet is in UTF-8.
3453
4902
 
3454
4903
  ==== General
3455
4904
 
3456
- Key-value data models like Hash, JSON, YAML, and TOML all share a similar structure
3457
- where data is stored as key-value pairs.
4905
+ Key-value data models share a similar structure where data is stored as
4906
+ key-value pairs.
3458
4907
 
3459
4908
  `Lutaml::Model` works with these formats in a similar way.
3460
4909
 
4910
+ Key-value data models supported are identified by their short name:
4911
+
4912
+ `hsh`:: Hash (Ruby `Hash` class)
4913
+ `json`:: JSON
4914
+ `yaml`:: YAML
4915
+ `toml`:: TOML
4916
+ `key_value`:: A way to configure key-value mappings for all supported key-value data models.
4917
+
4918
+
3461
4919
  ==== Mapping
3462
4920
 
3463
4921
  The `map` method is used to define key-value mappings.
@@ -3466,11 +4924,35 @@ Syntax:
3466
4924
 
3467
4925
  [source,ruby]
3468
4926
  ----
3469
- hsh | json | yaml | toml | key_value do
4927
+ {key_value_type_short} do <1>
3470
4928
  map 'key_value_model_attribute_name', to: :name_of_attribute
3471
4929
  end
3472
4930
  ----
4931
+ <1> `key_value_type_short` is the key-value data model's short name.
4932
+
4933
+ .Creating a key-value data model mapping for only the JSON format
4934
+ [example]
4935
+ ====
4936
+ [source,ruby]
4937
+ ----
4938
+ json do
4939
+ map :color, to: :color
4940
+ map :desc, to: :description
4941
+ end
4942
+ ----
4943
+ ====
3473
4944
 
4945
+ .Creating a key-value data model mapping for all key-value formats
4946
+ [example]
4947
+ ====
4948
+ [source,ruby]
4949
+ ----
4950
+ key_value do
4951
+ map :color, to: :color
4952
+ map :desc, to: :description
4953
+ end
4954
+ ----
4955
+ ====
3474
4956
 
3475
4957
  ==== Unified mapping
3476
4958
 
@@ -3494,19 +4976,11 @@ class CeramicModel < Lutaml::Model::Serializable
3494
4976
  attribute :glaze, :string
3495
4977
  attribute :description, :string
3496
4978
 
3497
- key_value do
3498
- map :color, to: color
4979
+ key_value do # or any other key-value data model
4980
+ map :color, to: :color
3499
4981
  map :glz, to: :glaze
3500
4982
  map :desc, to: :description
3501
4983
  end
3502
-
3503
- # Equivalent to the Hash, JSON, YAML, and TOML mappings.
3504
- #
3505
- # hsh and json and yaml and toml do
3506
- # map :id, to: color
3507
- # map :name, to: :full_name
3508
- # map :status, to: :current_status
3509
- # end
3510
4984
  end
3511
4985
  ----
3512
4986
 
@@ -3537,13 +5011,7 @@ desc: A ceramic with a navy blue color and clear glaze.
3537
5011
 
3538
5012
  ==== Specific format mappings
3539
5013
 
3540
- Specific key value formats can be mapping independently of other formats, including:
3541
-
3542
- * `hsh` for the Hash format
3543
- * `json` for the JSON format
3544
- * `yaml` for the YAML format
3545
- * `toml` for the TOML format
3546
-
5014
+ Specific key value formats can be mapping independently of other formats.
3547
5015
 
3548
5016
  .Using the `map` method to define key-value mappings per format
3549
5017
  [example]
@@ -4477,106 +5945,447 @@ object as the value of a designated attribute.
4477
5945
  ----
4478
5946
  ====
4479
5947
 
4480
- If a specified value path is not found, the corresponding attribute in the model
4481
- will be assigned a `nil` value.
5948
+ If a specified value path is not found, the corresponding attribute in the model
5949
+ will be assigned a `nil` value.
5950
+
5951
+ .Attribute values set to `nil` when the `path_to_value` is not found
5952
+ [example]
5953
+ ====
5954
+ In the following JSON content, the `path_to_value` of `[:extras, :sunroof]` and
5955
+ `[:extras, :drinks_cooler]` at the object `"gearbox"` would be set to `nil`.
5956
+
5957
+ [source,json]
5958
+ ----
5959
+ {
5960
+ "components": {
5961
+ "engine": {
5962
+ "manufacturer": "Ford",
5963
+ "extras": {
5964
+ "sunroof": true,
5965
+ "drinks_cooler": true
5966
+ }
5967
+ },
5968
+ "gearbox": {
5969
+ "manufacturer": "Toyota"
5970
+ }
5971
+ }
5972
+ }
5973
+ ----
5974
+ ====
5975
+
5976
+
5977
+ .Using the `child_mappings` option to extract values from a key-value data model
5978
+ [example]
5979
+ ====
5980
+ The following JSON contains 2 keys in schema named `foo` and `bar`.
5981
+
5982
+ [source,json]
5983
+ ----
5984
+ {
5985
+ "schemas": {
5986
+ "foo": { <1>
5987
+ "path": { <2>
5988
+ "link": "link one",
5989
+ "name": "one"
5990
+ }
5991
+ },
5992
+ "bar": { <1>
5993
+ "path": { <2>
5994
+ "link": "link two",
5995
+ "name": "two"
5996
+ }
5997
+ }
5998
+ }
5999
+ }
6000
+ ----
6001
+ <1> The keys `foo` and `bar` are to be mapped to the `id` attribute.
6002
+ <2> The nested `path.link` and `path.name` keys are used as the `link` and
6003
+ `name` attributes, respectively.
6004
+
6005
+ A model can be defined for this JSON as follows:
6006
+
6007
+ [source,ruby]
6008
+ ----
6009
+ class Schema < Lutaml::Model::Serializable
6010
+ attribute :id, :string
6011
+ attribute :link, :string
6012
+ attribute :name, :string
6013
+ end
6014
+
6015
+ class ChildMappingClass < Lutaml::Model::Serializable
6016
+ attribute :schemas, Schema, collection: true
6017
+
6018
+ json do
6019
+ map "schemas", to: :schemas,
6020
+ child_mappings: {
6021
+ id: :key,
6022
+ link: %i[path link],
6023
+ name: %i[path name],
6024
+ }
6025
+ end
6026
+ end
6027
+ ----
6028
+
6029
+ The output becomes:
6030
+
6031
+ [source,ruby]
6032
+ ----
6033
+ > ChildMappingClass.from_json(json)
6034
+ > #<ChildMappingClass:0x0000000104ac7240
6035
+ @schemas=
6036
+ [#<Schema:0x0000000104ac6e30 @id="foo", @link="link one", @name="one">,
6037
+ #<Schema:0x0000000104ac58f0 @id="bar", @link="link two", @name="two">]>
6038
+ > ChildMappingClass.new(schemas: [Schema.new(id: "foo", link: "link one", name: "one"), Schema.new(id: "bar", link: "link two", name: "two")]).to_json
6039
+ > #{"schemas"=>{"foo"=>{"path"=>{"link"=>"link one", "name"=>"one"}}, {"bar"=>{"path"=>{"link"=>"link two", "name"=>"two"}}}}}
6040
+ ----
6041
+
6042
+ In this example:
6043
+
6044
+ * The `key` of each schema (`foo` and `bar`) is mapped to the `id` attribute.
6045
+
6046
+ * The nested `path.link` and `path.name` keys are mapped to the `link` and
6047
+ `name` attributes, respectively.
6048
+ ====
6049
+
6050
+ === Collection data models
6051
+
6052
+ ==== General
6053
+
6054
+ Collection data models represent a group of models, mapping to an instance of
6055
+ Lutaml::Model::Collection.
6056
+
6057
+ Collection data models supported are identified by their short name:
6058
+
6059
+ `jsonl`:: JSONL (JSON Lines)
6060
+ `yamls`:: YAML Stream (multi-document format)
6061
+
6062
+
6063
+ ==== Mapping
6064
+
6065
+ As with collections in general, the `map` method is used to define collection
6066
+ mappings.
6067
+
6068
+ Syntax:
6069
+
6070
+ [source,ruby]
6071
+ ----
6072
+ class MySerializedCollection < Lutaml::Model::Collection
6073
+ instances {attribute}, ModelType
6074
+
6075
+ {collection_type_short} do
6076
+ map_instances to: {attribute}
6077
+ end
6078
+ end
6079
+ ----
6080
+
6081
+ Where,
6082
+
6083
+ `{collection_type_short}`:: The short name of the collection type (e.g. `jsonl`, `yamls`).
6084
+
6085
+ `{attribute}`:: The name of the attribute in the collection that will hold the
6086
+ collection data.
6087
+
6088
+ `ModelType`:: The type of the model that will be used in the collection.
6089
+
6090
+ A singular model may also utilize collection data models in the following manner.
6091
+
6092
+ Syntax:
6093
+
6094
+ [source,ruby]
6095
+ ----
6096
+ class MySerializedCollection < Lutaml::Model::Serializeable
6097
+ attribute {attribute}, ModelType, collection: true
6098
+
6099
+ {collection_type_short} do
6100
+ # Notice that there is no key_name i.e map <key_name>, to: <attribute_name>,
6101
+ # This is because in a collection there are no keys. Each object needs to be
6102
+ # mapped to the attribute.
6103
+ map to: {attribute}
6104
+ end
6105
+ end
6106
+ ----
6107
+
6108
+ Where,
6109
+
6110
+ `{collection_type_short}`:: The short name of the collection type (e.g. `jsonl`, `yamls`).
6111
+
6112
+ `{attribute}`:: The name of the attribute in the collection that will hold the
6113
+ collection data.
6114
+
6115
+ `ModelType`:: The type of the model that will be used in the collection.
6116
+
6117
+
6118
+ ==== JSONL
6119
+
6120
+ JSONL (short for JSON Lines) is a serialization format where each line
6121
+ represents a valid JSON object. The format is meant to be efficient for large
6122
+ datasets such as for streaming or batch processing.
6123
+
6124
+ It represents a collection of JSON objects encoded one object per line.
6125
+
6126
+ NOTE: The contents of JSONL itself is not valid JSON, but each line is a valid
6127
+ JSON.
6128
+
6129
+ Since JSONL contains JSON elements, the model specified with `instances` or
6130
+ `attribute` must support JSON.
6131
+
6132
+ Every line in a JSONL file is also a valid JSON object. If JSONL-specific
6133
+ mappings (through `jsonl`) are not defined in the model, the existing `json`
6134
+ mappings are used instead as a fallback for serialization and deserialization.
6135
+
6136
+
6137
+ .Parsing a JSONL collection using a collection
6138
+ [example]
6139
+ ====
6140
+ [source,ruby]
6141
+ ----
6142
+ class Person
6143
+ attribute :name, :string
6144
+ attribute :age, :integer
6145
+ attribute :id, :string
6146
+ end
6147
+
6148
+ class Directory < Lutaml::Model::Collection
6149
+ instances :persons, Person
6150
+
6151
+ jsonl do
6152
+ map_instances to: :persons
6153
+ end
6154
+ end
6155
+
6156
+ jsonl = <<~JSONL
6157
+ {"name":"John","age":30,"id":"abc-123"}
6158
+ {"name":"Jane","age":25,"id":"def-456"}
6159
+ JSONL
6160
+
6161
+ jsonl = Directory.from_jsonl(jsonl)
6162
+
6163
+ # => <Directory:0x00007fae4b0c9b10
6164
+ # @persons=[
6165
+ # <Person:0x00007fae4b0c9970 @name="John", @age=30, @id="abc-123">,
6166
+ # <Person:0x00007fae4b0c9838 @name="Jane", @age=25, @id="def-456">
6167
+ # ]>
6168
+ ----
6169
+ ====
6170
+
6171
+ .Parsing a JSONL collection using a singlular model
6172
+ [example]
6173
+ ====
6174
+ [source,ruby]
6175
+ ----
6176
+ class Person
6177
+ attribute :name, :string
6178
+ attribute :age, :integer
6179
+ attribute :id, :string
6180
+ end
6181
+
6182
+ class Directory < Lutaml::Model::Serializeable
6183
+ attribute :persons, Person, collection: true
6184
+
6185
+ jsonl do
6186
+ map_instances to: :persons
6187
+ end
6188
+ end
6189
+
6190
+ jsonl = <<~JSONL
6191
+ {"name":"John","age":30,"id":"abc-123"}
6192
+ {"name":"Jane","age":25,"id":"def-456"}
6193
+ JSONL
6194
+
6195
+ jsonl = Directory.from_jsonl(jsonl)
6196
+
6197
+ # => <Directory:0x00007fae4b0c9b10
6198
+ # @persons=[
6199
+ # <Person:0x00007fae4b0c9970 @name="John", @age=30, @id="abc-123">,
6200
+ # <Person:0x00007fae4b0c9838 @name="Jane", @age=25, @id="def-456">
6201
+ # ]>
6202
+ ----
6203
+ ====
6204
+
6205
+ .Parsing a JSONL collection relying on JSON mappings on instance model
6206
+ [example]
6207
+ ====
6208
+ [source,ruby]
6209
+ ----
6210
+ class Person
6211
+ attribute :name, :string
6212
+ attribute :age, :integer
6213
+ attribute :id, :string
6214
+
6215
+ json do
6216
+ map "full_name", to: :name
6217
+ map "age", to: :age
6218
+ map "id", to: :id
6219
+ end
6220
+ end
6221
+
6222
+ class Directory < Lutaml::Model::Collection
6223
+ instances :persons, Person
6224
+
6225
+ jsonl do
6226
+ map_instances to: :persons
6227
+ end
6228
+ end
6229
+
6230
+ jsonl = <<~JSONL
6231
+ {"full_name":"John Doe","age":30,"id":"abc-123"}
6232
+ {"full_name":"Jane Smith","age":25,"id":"def-456"}
6233
+ JSONL
6234
+
6235
+ jsonl = Directory.from_jsonl(jsonl)
6236
+ # => <Directory:0x00007fae4b0c9b10
6237
+ # @persons=[
6238
+ # <Person:0x00007fae4b0c9970 @name="John Doe", @age=30, @id="abc-123">,
6239
+ # <Person:0x00007fae4b0c9838 @name="Jane Smith", @age=25, @id="def-456">
6240
+ # ]>
6241
+ ----
6242
+ ====
6243
+
4482
6244
 
4483
- .Attribute values set to `nil` when the `path_to_value` is not found
4484
- [example]
4485
- ====
4486
- In the following JSON content, the `path_to_value` of `[:extras, :sunroof]` and
4487
- `[:extras, :drinks_cooler]` at the object `"gearbox"` would be set to `nil`.
6245
+ ==== YAML Stream
4488
6246
 
4489
- [source,json]
4490
- ----
4491
- {
4492
- "components": {
4493
- "engine": {
4494
- "manufacturer": "Ford",
4495
- "extras": {
4496
- "sunroof": true,
4497
- "drinks_cooler": true
4498
- }
4499
- },
4500
- "gearbox": {
4501
- "manufacturer": "Toyota"
4502
- }
4503
- }
4504
- }
4505
- ----
4506
- ====
6247
+ YAML Stream (short for YAML multi-document format) is a serialization format
6248
+ where each document is separated by a document separator (`---`). The format is
6249
+ meant to be efficient for large datasets such as for streaming or batch
6250
+ processing.
4507
6251
 
6252
+ It represents a collection of YAML documents encoded one document per stream.
4508
6253
 
4509
- .Using the `child_mappings` option to extract values from a key-value data model
6254
+ NOTE: The contents of YAML Stream is valid YAML, where each document is a valid
6255
+ YAML document separated by document separators.
6256
+
6257
+ Since YAML Stream contains YAML elements, the model specified with `instances`
6258
+ or `attribute` must support YAML.
6259
+
6260
+ Every document in a YAML Stream file is also a valid YAML document. If YAML
6261
+ Stream-specific mappings (through `yamls`) are not defined in the model, the
6262
+ existing `yaml` mappings are used instead as a fallback for serialization and
6263
+ deserialization.
6264
+
6265
+
6266
+ .Parsing a YAML Stream collection using a collection
4510
6267
  [example]
4511
6268
  ====
4512
- The following JSON contains 2 keys in schema named `foo` and `bar`.
4513
-
4514
- [source,json]
4515
- ----
4516
- {
4517
- "schemas": {
4518
- "foo": { <1>
4519
- "path": { <2>
4520
- "link": "link one",
4521
- "name": "one"
4522
- }
4523
- },
4524
- "bar": { <1>
4525
- "path": { <2>
4526
- "link": "link two",
4527
- "name": "two"
4528
- }
4529
- }
4530
- }
4531
- }
6269
+ [source,ruby]
4532
6270
  ----
4533
- <1> The keys `foo` and `bar` are to be mapped to the `id` attribute.
4534
- <2> The nested `path.link` and `path.name` keys are used as the `link` and
4535
- `name` attributes, respectively.
6271
+ class Person
6272
+ attribute :name, :string
6273
+ attribute :age, :integer
6274
+ attribute :id, :string
6275
+ end
4536
6276
 
4537
- A model can be defined for this JSON as follows:
6277
+ class Directory < Lutaml::Model::Collection
6278
+ instances :persons, Person
6279
+
6280
+ yamls do
6281
+ map_instances to: :persons
6282
+ end
6283
+ end
6284
+
6285
+ yamls = <<~YAMLS
6286
+ ---
6287
+ name: John
6288
+ age: 30
6289
+ id: abc-123
6290
+ ---
6291
+ name: Jane
6292
+ age: 25
6293
+ id: def-456
6294
+ YAMLS
6295
+
6296
+ yamls = Directory.from_yamls(yamls)
4538
6297
 
6298
+ # => <Directory:0x00007fae4b0c9b10
6299
+ # @persons=[
6300
+ # <Person:0x00007fae4b0c9970 @name="John", @age=30, @id="abc-123">,
6301
+ # <Person:0x00007fae4b0c9838 @name="Jane", @age=25, @id="def-456">
6302
+ # ]>
6303
+ ----
6304
+ ====
6305
+
6306
+ .Parsing a YAML Stream collection using a singlular model
6307
+ [example]
6308
+ ====
4539
6309
  [source,ruby]
4540
6310
  ----
4541
- class Schema < Lutaml::Model::Serializable
4542
- attribute :id, :string
4543
- attribute :link, :string
6311
+ class Person
4544
6312
  attribute :name, :string
6313
+ attribute :age, :integer
6314
+ attribute :id, :string
4545
6315
  end
4546
6316
 
4547
- class ChildMappingClass < Lutaml::Model::Serializable
4548
- attribute :schemas, Schema, collection: true
6317
+ class Directory < Lutaml::Model::Serializeable
6318
+ attribute :persons, Person, collection: true
4549
6319
 
4550
- json do
4551
- map "schemas", to: :schemas,
4552
- child_mappings: {
4553
- id: :key,
4554
- link: %i[path link],
4555
- name: %i[path name],
4556
- }
6320
+ yamls do
6321
+ map_instances to: :persons
4557
6322
  end
4558
6323
  end
4559
- ----
4560
6324
 
4561
- The output becomes:
6325
+ yamls = <<~YAMLS
6326
+ ---
6327
+ name: John
6328
+ age: 30
6329
+ id: abc-123
6330
+ ---
6331
+ name: Jane
6332
+ age: 25
6333
+ id: def-456
6334
+ YAMLS
4562
6335
 
4563
- [source,ruby]
6336
+ yamls = Directory.from_yamls(yamls)
6337
+
6338
+ # => <Directory:0x00007fae4b0c9b10
6339
+ # @persons=[
6340
+ # <Person:0x00007fae4b0c9970 @name="John", @age=30, @id="abc-123">,
6341
+ # <Person:0x00007fae4b0c9838 @name="Jane", @age=25, @id="def-456">
6342
+ # ]>
4564
6343
  ----
4565
- > ChildMappingClass.from_json(json)
4566
- > #<ChildMappingClass:0x0000000104ac7240
4567
- @schemas=
4568
- [#<Schema:0x0000000104ac6e30 @id="foo", @link="link one", @name="one">,
4569
- #<Schema:0x0000000104ac58f0 @id="bar", @link="link two", @name="two">]>
4570
- > ChildMappingClass.new(schemas: [Schema.new(id: "foo", link: "link one", name: "one"), Schema.new(id: "bar", link: "link two", name: "two")]).to_json
4571
- > #{"schemas"=>{"foo"=>{"path"=>{"link"=>"link one", "name"=>"one"}}, {"bar"=>{"path"=>{"link"=>"link two", "name"=>"two"}}}}}
6344
+ ====
6345
+
6346
+ .Parsing a YAML Stream collection relying on YAML mappings on instance model
6347
+ [example]
6348
+ ====
6349
+ [source,ruby]
4572
6350
  ----
6351
+ class Person
6352
+ attribute :name, :string
6353
+ attribute :age, :integer
6354
+ attribute :id, :string
4573
6355
 
4574
- In this example:
6356
+ yaml do
6357
+ map "full_name", to: :name
6358
+ map "age", to: :age
6359
+ map "id", to: :id
6360
+ end
6361
+ end
4575
6362
 
4576
- * The `key` of each schema (`foo` and `bar`) is mapped to the `id` attribute.
6363
+ class Directory < Lutaml::Model::Collection
6364
+ instances :persons, Person
4577
6365
 
4578
- * The nested `path.link` and `path.name` keys are mapped to the `link` and
4579
- `name` attributes, respectively.
6366
+ yamls do
6367
+ map_instances to: :persons
6368
+ end
6369
+ end
6370
+
6371
+ yamls = <<~YAMLS
6372
+ ---
6373
+ full_name: John Doe
6374
+ age: 30
6375
+ id: abc-123
6376
+ ---
6377
+ full_name: Jane Smith
6378
+ age: 25
6379
+ id: def-456
6380
+ YAMLS
6381
+
6382
+ yamls = Directory.from_yamls(yamls)
6383
+ # => <Directory:0x00007fae4b0c9b10
6384
+ # @persons=[
6385
+ # <Person:0x00007fae4b0c9970 @name="John Doe", @age=30, @id="abc-123">,
6386
+ # <Person:0x00007fae4b0c9838 @name="Jane Smith", @age=25, @id="def-456">
6387
+ # ]>
6388
+ ----
4580
6389
  ====
4581
6390
 
4582
6391
  === Format-independent mechanisms
@@ -6997,9 +8806,91 @@ puts SomeModel.new.to_yaml
6997
8806
 
6998
8807
 
6999
8808
 
7000
- == Importing data models
8809
+ == Schema generation and import
7001
8810
 
7002
- === General
8811
+ === Schema generation
8812
+
8813
+ Lutaml::Model provides functionality to generate schema definitions from LutaML models. This allows you to create schemas that can be used for validation or documentation purposes.
8814
+
8815
+ Currently, the following schema formats are supported:
8816
+
8817
+ * JSON Schema (https://json-schema.org/understanding-json-schema/[JSON Schema])
8818
+ * YAML Schema (https://yaml.org/spec/1.2/spec.html[YAML])
8819
+
8820
+ ==== JSON Schema generation
8821
+
8822
+ The `Lutaml::Model::Schema.to_json` method generates a JSON Schema from a LutaML model class. The generated schema includes:
8823
+
8824
+ * Properties based on model attributes
8825
+ * Validation constraints (patterns, enumerations, etc.)
8826
+ * Support for polymorphic types
8827
+ * Support for inheritance
8828
+ * Support for choice attributes
8829
+ * Collection constraints
8830
+
8831
+ Example:
8832
+
8833
+ [source,ruby]
8834
+ ----
8835
+ class Glaze < Lutaml::Model::Serializable
8836
+ attribute :color, :string
8837
+ attribute :finish, :string
8838
+ end
8839
+
8840
+ class Vase < Lutaml::Model::Serializable
8841
+ attribute :height, :float
8842
+ attribute :diameter, :float
8843
+ attribute :glaze, Glaze
8844
+ attribute :materials, :string, collection: true
8845
+ end
8846
+
8847
+ # Generate JSON schema
8848
+ schema = Lutaml::Model::Schema.to_json(
8849
+ Vase,
8850
+ id: "https://example.com/vase.schema.json",
8851
+ description: "A vase schema",
8852
+ pretty: true
8853
+ )
8854
+
8855
+ # Write to file
8856
+ File.write("vase.schema.json", schema)
8857
+ ----
8858
+
8859
+ The generated schema will include definitions for all nested models and their attributes.
8860
+
8861
+ ==== YAML Schema generation
8862
+
8863
+ The `Lutaml::Model::Schema.to_yaml` method generates a YAML Schema from a LutaML model class. The generated schema includes the same features as the JSON Schema generation.
8864
+
8865
+ Example:
8866
+
8867
+ [source,ruby]
8868
+ ----
8869
+ class Glaze < Lutaml::Model::Serializable
8870
+ attribute :color, :string
8871
+ attribute :finish, :string
8872
+ end
8873
+
8874
+ class Vase < Lutaml::Model::Serializable
8875
+ attribute :height, :float
8876
+ attribute :diameter, :float
8877
+ attribute :glaze, Glaze
8878
+ attribute :materials, :string, collection: true
8879
+ end
8880
+
8881
+ # Generate YAML schema
8882
+ schema = Lutaml::Model::Schema.to_yaml(
8883
+ Vase,
8884
+ id: "http://example.com/schemas/vase",
8885
+ description: "A vase schema",
8886
+ pretty: true
8887
+ )
8888
+
8889
+ # Write to file
8890
+ File.write("vase.schema.yaml", schema)
8891
+ ----
8892
+
8893
+ === Importing data models
7003
8894
 
7004
8895
  Lutaml::Model provides a way to import data models defined from various formats
7005
8896
  into the LutaML data modeling system.
@@ -7524,60 +9415,129 @@ puts output
7524
9415
  ----
7525
9416
  ====
7526
9417
 
7527
- == Adapters
9418
+
9419
+ == Serialization adapters
7528
9420
 
7529
9421
  === General
7530
9422
 
7531
- Lutaml::Model uses an adapter pattern to support multiple libraries for each
7532
- serialization format.
9423
+ The LutaML component that serializes a model into a serialization format is
9424
+ called an adapter. A serialization format may be supported by multiple adapters.
9425
+
9426
+ An adapter typically:
7533
9427
 
7534
- Lutaml::Model supports the following serialization formats:
9428
+ * supports a specific serialization format
9429
+ * provides a set of methods to serialize and deserialize models and collections of models
9430
+
9431
+ LutaML, out of the box, supports the following serialization formats:
7535
9432
 
7536
9433
  * XML (https://www.w3.org/TR/xmlschema-1/[W3C XML Schema (Second Edition)], XML 1.0)
7537
9434
  * YAML (https://yaml.org/[YAML version 1.2])
7538
9435
  * JSON (https://www.ecma-international.org/publications-and-standards/standards/ecma-404/[ECMA-404 The JSON Data Interchange Standard], unofficial link: https://www.json.org[JSON])
7539
9436
  * TOML (https://toml.io/en[TOML version 1.0])
7540
- * You can also create custom adapters for additional data formats. See link:docs/custom_adapters.adoc[Custom Adapters Guide] for detailed information.
7541
9437
 
7542
- You will need to specify the configuration for the adapter you want to use. The
7543
- easiest way is to copy and paste the following configuration into your code.
9438
+ The adapter interface is also used to support certain transformation of models
9439
+ into an "end format", which is not a serialization format. For example, the
9440
+ `Lutaml::Model::HashAdapter` is used to convert a model into a hash format
9441
+ that is not a serialization format.
9442
+
9443
+ Users can extend LutaML by creating custom adapters for other serialization
9444
+ formats or for other data formats. The
9445
+ link:docs/custom_adapters.adoc[Custom Adapters Guide] describes this process in
9446
+ detail.
9447
+
9448
+ For certain serialization formats, LutaML provides multiple adapters to support
9449
+ different serialization libraries. Please refer to their specific sections for
9450
+ more information.
9451
+
7544
9452
 
7545
- The configuration is as follows:
9453
+ === Configuration
9454
+
9455
+ ==== General
9456
+
9457
+ It is necessary to configure the adapter to be used for serialization and
9458
+ deserialization for a set of formats that the LutaML models will be transformed
9459
+ into.
9460
+
9461
+ There are two cases where you need to define such configuration:
9462
+
9463
+ * End-user usage of the LutaML models. This is the case where you are
9464
+ using LutaML models in your application and want to serialize them into a
9465
+ specific format. If you are a gem developer that relies on lutaml-model,
9466
+ this case does not apply to you, because the end-user of your gem should
9467
+ determine the adapter configuration.
9468
+
9469
+ * Testing purposes, e.g. RSpec. In order to run tests that involve verifying
9470
+ correctness of serialization, it is necessary to define adapter configuration.
9471
+
9472
+ There are two ways to specify a configuration:
9473
+
9474
+ * by providing a predefined symbol (preferred)
9475
+ * by providing the actual adapter classes
9476
+
9477
+ There is a default configuration for adapters for commonly used formats:
9478
+
9479
+ * YAML: `yaml_adapter_type` is set to `:standard_yaml`
9480
+ * JSON: `json_adapter_type` is set to `:standard_json`
9481
+ * Hash: `hash_adapter_type` is set to `:standard_hash`
9482
+ * XML: not defined
9483
+ * TOML: not defined
9484
+
9485
+
9486
+ ==== Configure adapters through symbol choices
9487
+
9488
+ The end-user or a gem developer can copy and paste the following configuration
9489
+ into an early loading file in their application or gem.
9490
+
9491
+ This configuration is preferred over the class choices because it is more
9492
+ concise and does not require any `require` code specific to the internals
9493
+ of the LutaML runtime implementation.
9494
+
9495
+ Syntax:
7546
9496
 
7547
9497
  [source,ruby]
7548
9498
  ----
7549
9499
  require 'lutaml/model'
7550
- require 'lutaml/model/xml_adapter/nokogiri_adapter'
7551
- require 'lutaml/model/json_adapter/standard_json_adapter'
7552
- require 'lutaml/model/toml_adapter/toml_rb_adapter'
7553
- require 'lutaml/model/yaml_adapter/standard_yaml_adapter'
7554
9500
 
7555
9501
  Lutaml::Model::Config.configure do |config|
7556
- config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
7557
- config.hash_adapter = Lutaml::Model::YamlAdapter::StandardHashAdapter
7558
- config.yaml_adapter = Lutaml::Model::YamlAdapter::StandardYamlAdapter
7559
- config.json_adapter = Lutaml::Model::JsonAdapter::StandardJsonAdapter
7560
- config.toml_adapter = Lutaml::Model::TomlAdapter::TomlRbAdapter
9502
+ config.xml_adapter_type = :nokogiri # can be one of [:nokogiri, :ox, :oga]
9503
+ config.hash_adapter_type = :standard_hash
9504
+ config.yaml_adapter_type = :standard_yaml
9505
+ config.json_adapter_type = :standard_json # can be one of [:standard_json, :multi_json]
9506
+ config.toml_adapter_type = :toml_rb # can be one of [:toml_rb, :tomlib]
7561
9507
  end
7562
9508
  ----
7563
9509
 
7564
- You can also provide the adapter type by using symbols like
7565
9510
 
9511
+ ==== Configure adapters through class choices
9512
+
9513
+ The end-uesr or a gem developer can copy and paste the following configuration
9514
+ into an early loading file in their application or gem.
9515
+
9516
+ Only the serialization formats used will require a configuration.
9517
+
9518
+ Syntax:
9519
+
9520
+ [example]
9521
+ ====
7566
9522
  [source,ruby]
7567
9523
  ----
7568
9524
  require 'lutaml/model'
9525
+ require 'lutaml/model/xml/nokogiri_adapter'
9526
+ require 'lutaml/model/hash_adapter/standard_adapter'
9527
+ require 'lutaml/model/json/standard_adapter'
9528
+ require 'lutaml/model/yaml/standard_adapter'
9529
+ require 'lutaml/model/toml/toml_rb_adapter'
7569
9530
 
7570
9531
  Lutaml::Model::Config.configure do |config|
7571
- config.xml_adapter_type = :nokogiri # can be one of [:nokogiri, :ox, :oga]
7572
- config.hash_adapter_type = :standard_hash
7573
- config.yaml_adapter_type = :standard_yaml
7574
- config.json_adapter_type = :standard_json # can be one of [:standard_json, :multi_json]
7575
- config.toml_adapter_type = :toml_rb # can be one of [:toml_rb, :tomlib]
9532
+ config.xml_adapter = Lutaml::Model::Xml::NokogiriAdapter
9533
+ config.hash_adapter = Lutaml::Model::HashAdapter::StandardAdapter
9534
+ config.yaml_adapter = Lutaml::Model::Yaml::StandardAdapter
9535
+ config.json_adapter = Lutaml::Model::Json::StandardAdapter
9536
+ config.toml_adapter = Lutaml::Model::Toml::TomlRbAdapter
7576
9537
  end
7577
9538
  ----
9539
+ ====
7578
9540
 
7579
- NOTE: By default `yaml_adapter_type` and `json_adapter_type` are set to
7580
- `:standard_yaml` and `:standard_json` respectively.
7581
9541
 
7582
9542
 
7583
9543
  === XML
@@ -7604,36 +9564,17 @@ Requires native extensions (i.e. compiled C code).
7604
9564
  Requires the `ox` gem.
7605
9565
 
7606
9566
 
7607
- .Using the Nokogiri XML adapter
7608
- [source,ruby]
7609
- ----
7610
- require 'lutaml/model'
7611
-
7612
- Lutaml::Model::Config.configure do |config|
7613
- require 'lutaml/model/xml_adapter/nokogiri_adapter'
7614
- config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
7615
- end
7616
- ----
7617
-
7618
- .Using the Oga XML adapter
7619
- [source,ruby]
7620
- ----
7621
- require 'lutaml/model'
7622
-
7623
- Lutaml::Model::Config.configure do |config|
7624
- require 'lutaml/model/xml_adapter/oga_adapter'
7625
- config.xml_adapter = Lutaml::Model::XmlAdapter::OgaAdapter
7626
- end
7627
- ----
7628
-
7629
- .Using the Ox XML adapter
9567
+ .Using an XML adapter
7630
9568
  [source,ruby]
7631
9569
  ----
7632
9570
  require 'lutaml/model'
7633
9571
 
7634
9572
  Lutaml::Model::Config.configure do |config|
7635
- require 'lutaml/model/xml_adapter/ox_adapter'
7636
- config.xml_adapter = Lutaml::Model::XmlAdapter::OxAdapter
9573
+ config.xml_adapter = :nokogiri
9574
+ # or
9575
+ config.xml_adapter = :oga
9576
+ # or
9577
+ config.xml_adapter = :ox
7637
9578
  end
7638
9579
  ----
7639
9580
 
@@ -7647,14 +9588,13 @@ YAML::
7647
9588
  The Psych YAML parser and emitter for Ruby.
7648
9589
  Included in the Ruby standard library.
7649
9590
 
7650
- .Using the YAML adapter
9591
+ .Using a YAML adapter
7651
9592
  [source,ruby]
7652
9593
  ----
7653
9594
  require 'lutaml/model'
7654
9595
 
7655
9596
  Lutaml::Model::Config.configure do |config|
7656
- require 'lutaml/model/yaml_adapter/standard_yaml_adapter'
7657
- config.yaml_adapter = Lutaml::Model::YamlAdapter::StandardYamlAdapter
9597
+ config.yaml_adapter = :standard_yaml
7658
9598
  end
7659
9599
  ----
7660
9600
 
@@ -7673,27 +9613,18 @@ MultiJson::
7673
9613
  A gem that provides a common interface to multiple JSON libraries.
7674
9614
  Requires the `multi_json` gem.
7675
9615
 
7676
- .Using the JSON adapter
9616
+ .Using a JSON adapter
7677
9617
  [source,ruby]
7678
9618
  ----
7679
9619
  require 'lutaml/model'
7680
9620
 
7681
9621
  Lutaml::Model::Config.configure do |config|
7682
- require 'lutaml/model/json_adapter/standard_json_adapter'
7683
- config.json_adapter = Lutaml::Model::JsonAdapter::StandardJsonAdapter
9622
+ config.json_adapter = :standard_json
9623
+ # or
9624
+ config.json_adapter = :multi_json
7684
9625
  end
7685
9626
  ----
7686
9627
 
7687
- .Using the MultiJson adapter
7688
- [source,ruby]
7689
- ----
7690
- require 'lutaml/model'
7691
-
7692
- Lutaml::Model::Config.configure do |config|
7693
- require 'lutaml/model/json_adapter/multi_json_adapter'
7694
- config.json_adapter = Lutaml::Model::JsonAdapter::MultiJsonAdapter
7695
- end
7696
- ----
7697
9628
 
7698
9629
  === TOML
7699
9630
 
@@ -7712,33 +9643,82 @@ additional features.
7712
9643
  Requires the `tomlib` gem.
7713
9644
 
7714
9645
 
7715
- .Using the Toml-rb adapter
9646
+ .Using a TOML adapter
7716
9647
  [source,ruby]
7717
9648
  ----
7718
9649
  require 'lutaml/model'
7719
9650
 
7720
9651
  Lutaml::Model::Config.configure do |config|
7721
- require 'lutaml/model/toml_adapter/toml_rb_adapter'
7722
- config.toml_adapter = Lutaml::Model::TomlAdapter::TomlRbAdapter
9652
+ config.toml_adapter = :toml_rb
9653
+ # or
9654
+ config.toml_adapter = :tomlib
7723
9655
  end
7724
9656
  ----
7725
9657
 
7726
- .Using the Tomlib adapter
7727
- [source,ruby]
7728
- ----
7729
- require 'lutaml/model'
7730
-
7731
- Lutaml::Model::Config.configure do |config|
7732
- config.toml_adapter = Lutaml::Model::TomlAdapter::TomlibAdapter
7733
- require 'lutaml/model/toml_adapter/tomlib_adapter'
7734
- end
7735
- ----
7736
9658
 
7737
9659
  [[custom-adapters]]
7738
- === Custom Adapters
9660
+ == Custom serialization adapters
9661
+
9662
+ Lutaml::Model provides a flexible system for creating custom adapters to handle
9663
+ different data formats.
9664
+
9665
+ Please refer to link:docs/custom_adapters.adoc[Custom adapters] for details
9666
+ and examples.
9667
+
9668
+
9669
+ [[schema-generation]]
9670
+ == Schema generation
9671
+
9672
+ Lutaml::Model provides functionality to generate schema definitions from LutaML models. This allows you to create schemas that can be used for validation or documentation purposes.
9673
+
9674
+ Currently, the following schema formats are supported:
9675
+
9676
+ * JSON Schema (https://json-schema.org/understanding-json-schema/[JSON Schema])
9677
+ * YAML Schema (https://yaml.org/spec/1.2/spec.html[YAML])
9678
+ * XSD (https://w3.org/TR/xmlschema-1/[XML Schema Definition Language])
9679
+ * RELAX NG (https://relaxng.org/[RELAX NG])
9680
+
9681
+ The schema generation supports advanced features such as:
7739
9682
 
7740
- Lutaml::Model provides a flexible system for creating custom adapters to handle different data formats.
7741
- For more information See link:docs/custom_adapters.adoc[Custom Adapters Guide]
9683
+ * Validation constraints (patterns, enumerations, ranges)
9684
+ * Choice attributes with min/max constraints
9685
+ * Polymorphic types with oneOf validation
9686
+ * Collection constraints with minItems/maxItems
9687
+
9688
+ Please refer to link:docs/schema_generation.adoc[Schema Generation] for details
9689
+ and examples.
9690
+
9691
+
9692
+ [[schema-import]]
9693
+ == Schema import
9694
+
9695
+ Lutaml::Model provides functionality to import schema definitions into LutaML
9696
+ models. This allows you to create models from existing schema definitions.
9697
+
9698
+ Currently, the following schema formats are supported:
9699
+
9700
+ * XSD (https://w3.org/TR/xmlschema-1/[XML Schema Definition Language])
9701
+ * JSON/YAML Schema
9702
+
9703
+ Please refer to link:docs/schema_import.adoc[Schema Import] for details
9704
+ and examples.
9705
+
9706
+
9707
+ [[custom-registers]]
9708
+ === Custom Registers
9709
+
9710
+ A *LutaML::Model* *Register* allows for dynamic modification and reconfiguration of model hierarchies without altering the original model definitions.
9711
+ For more information, refer to the link:docs/custom_registers.adoc[Custom Registers Guide].
9712
+
9713
+ NOTE: Before using the `Lutaml::Model::Register` instance, make sure to register it in `Lutaml::Model::GlobalRegister`.
9714
+
9715
+ NOTE: By default, a `default_register` with the id `:default` is created and registered in the GlobalRegister. This default register is also set in `Lutaml::Model::Config.default_register` as the default value.
9716
+
9717
+ The default register can be set at the configuration level using the following syntax:
9718
+
9719
+ ```ruby
9720
+ Lutaml::Model::Config.default_register = :default # the register id goes here.
9721
+ ```
7742
9722
 
7743
9723
  == Comparison with Shale
7744
9724
 
@@ -7853,6 +9833,11 @@ data models.
7853
9833
  | No.
7854
9834
  | Lutaml::Model supports attribute extraction from key-value data models.
7855
9835
 
9836
+ | Register
9837
+ | Yes. Supports three types of registers(<<custom-register, read more details>>) with different types of functionalities.
9838
+ | Supports `register` functionality for *Shale::Type* classes only.
9839
+ | `Lutaml::Model` registers both `Registrable` classes and `Lutaml::Model::Type` classes, offering a more comprehensive registration system.
9840
+
7856
9841
  |===
7857
9842
 
7858
9843