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
@@ -0,0 +1,364 @@
1
+ = Schema import
2
+
3
+ This document describes how to import schemas into LutaML models.
4
+
5
+ == Schema import
6
+
7
+ === Overview
8
+
9
+ Lutaml::Model provides functionality to import schema definitions into LutaML
10
+ models. This allows you to create models from existing schema definitions.
11
+
12
+ The following figure illustrates the process of importing an XML Schema model to
13
+ create corresponding LutaML models.
14
+
15
+ .Importing serialization schemas to create LutaML models (XML example)
16
+ [source]
17
+ ----
18
+ ╔════════════════════════════╗ ╔═══════════════════════╗
19
+ ║ Serialization Models ║ ║ Core Model ║
20
+ ╚════════════════════════════╝ ╚═══════════════════════╝
21
+
22
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
23
+ ┆ XML Schema (XSD/RNG/RNC) ┆ ┆ Model ┆
24
+ ┆ │ ┆ ┌────────────────┐ ┆ │ ┆
25
+ ┆ ┌──────┴──────┐ ┆ │ │ ┆ ┌────────┴──┐ ┆
26
+ ┆ │ │ ┆ │ Schema │ ┆ │ │ ┆
27
+ ┆ Models Value Types ┆──►│ Importing │──►┆ Models Value Types ┆
28
+ ┆ │ │ ┆ │ │ ┆ │ │ ┆
29
+ ┆ │ │ ┆ └────────────────┘ ┆ │ │ ┆
30
+ ┆ ┌────┴────┐ ┌─┴─┐ ┆ │ ┆ │ ┌──────┴──┐ ┆
31
+ ┆ │ │ │ │ ┆ │ ┆ │ │ │ ┆
32
+ ┆ Element Value xs:string ┆ │ ┆ │ String Integer ┆
33
+ ┆ Attribute Type xs:date ┆ │ ┆ │ Date Float ┆
34
+ ┆ Union Complex xs:boolean ┆ │ ┆ │ Time Boolean ┆
35
+ ┆ Sequence Choice xs:anyURI ┆ │ ┆ │ ┆
36
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ │ ┆ └──────┐ ┆
37
+ │ ┆ │ ┆
38
+ │ ┆ Contains ┆
39
+ │ ┆ more Models ┆
40
+ │ ┆ (recursive) ┆
41
+ │ ┆ ┆
42
+ │ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
43
+ │ ┌────────────────┐
44
+ │ │ │
45
+ │ │ Model │
46
+ └──────────► │ Transformation │
47
+ │ & │
48
+ │ Mapping Rules │
49
+ │ │
50
+ └────────────────┘
51
+ ----
52
+
53
+
54
+ Currently, the following schema formats are supported for import:
55
+
56
+ * XSD (https://w3.org/TR/xmlschema-1/[XML Schema Definition Language])
57
+
58
+ === XML Schema (XSD) import
59
+
60
+ W3C XSD is a schema language designed to define the structure of XML documents,
61
+ alongside other XML schema languages like DTD, RELAX NG, and Schematron.
62
+
63
+ Lutaml::Model supports the import of XSD schema files to define information
64
+ models that can be used to parse and generate XML documents.
65
+
66
+ Specifically, the `Lutaml::Model::Schema#from_xml` method loads XML Schema files
67
+ (XSD, `*.xsd`) and generates Ruby files (`*.rb`) that inherit from
68
+ `Lutaml::Model::Serializable` that are saved to disk.
69
+
70
+ ==== Syntax
71
+
72
+ [source,ruby]
73
+ ----
74
+ Lutaml::Model::Schema.from_xml(
75
+ xsd_schema, <1>
76
+ options: options <2>
77
+ )
78
+ ----
79
+ <1> The `xsd_schema` is the XML Schema string to be converted to model files.
80
+ <2> The `options` hash is an optional argument.
81
+
82
+ `options`:: Optional hash containing potentially the following key-values.
83
+
84
+ `output_dir`::: The directory where the model files will be saved. If not
85
+ provided, a default directory named `lutaml_models_<timestamp>` is created.
86
+ +
87
+ [example]
88
+ `"path/to/directory"`
89
+
90
+ `create_files`::: A `boolean` argument (`false` by default) to create files
91
+ directly in the specified directory as defined by the `output_dir` option.
92
+ +
93
+ [example]
94
+ `create_files: (true | false)`
95
+
96
+ `load_classes`::: A `boolean` argument (`false` by default) to load generated
97
+ classes before returning them.
98
+ +
99
+ [example]
100
+ `load_classes: (true | false)`
101
+
102
+ `namespace`::: The namespace of the schema. This will be added in the
103
+ `Lutaml::Model::Serializable` file's `xml do` block.
104
+ +
105
+ [example]
106
+ `http://example.com/namespace`
107
+
108
+ `prefix`::: The prefix of the namespace provided in the `namespace` option.
109
+ +
110
+ [example]
111
+ `example-prefix`
112
+
113
+ `location`::: The URL or path of the directory containing all the files of the
114
+ schema. For more information, refer to the
115
+ link:https://www.w3.org/TR/xmlschema-1/#include[XML Schema specification].
116
+ +
117
+ [example]
118
+ `"http://example.com/example.xsd"`
119
+ +
120
+ [example]
121
+ `"path/to/schema/directory"`
122
+
123
+ NOTE: If both `create_files` and `load_classes` are provided, the `create_files`
124
+ argument will take priority and generate files without loading them!
125
+
126
+ ==== Generated model structure
127
+
128
+ The generated LutaML models consists of two different kind of Ruby classes
129
+ depending on the XSD schema:
130
+
131
+ XSD "SimpleTypes":: converted into classes that inherit from
132
+ `Lutaml::Model::Type::Value`, which define the data types with restrictions and
133
+ other validations of these values.
134
+
135
+ XSD "ComplexTypes":: converted into classes that inherit from
136
+ `Lutaml::Model::Serializable` that model according to the defined structure.
137
+
138
+ Lutaml::Model uses the https://github.com/lutaml/lutaml-xsd[`lutaml-xsd` gem] to
139
+ automatically resolve the `include` and `import` elements, enabling
140
+ *Lutaml-Model* to generate the corresponding model files.
141
+
142
+ This auto-resolving feature allows seamless integration of these files into your
143
+ models without the need for manual resolution of includes and imports.
144
+
145
+ ==== Example
146
+
147
+ [example]
148
+ ====
149
+ [source,ruby]
150
+ ----
151
+ xsd_schema = <<~XSD
152
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
153
+ <!-- XML schema definition here -->
154
+ <xs:element name="User">
155
+ <xs:complexType>
156
+ <xs:sequence>
157
+ <xs:element name="id" type="xs:positiveInteger"/>
158
+ <xs:element name="age" type="xs:positiveInteger"/>
159
+ <xs:element name="token" type="xs:token"/>
160
+ </xs:sequence>
161
+ </xs:complexType>
162
+ </xs:element>
163
+ </xs:schema>
164
+ XSD
165
+
166
+ options = {
167
+ # These are all optional:
168
+ output_dir: 'path/to/directory',
169
+ namespace: 'http://example.com/namespace',
170
+ prefix: "example-prefix",
171
+ location: "http://example.com/example.xsd",
172
+ # or
173
+ # location: "path/to/schema/directory"
174
+ create_files: true, # Default: false
175
+ # OR
176
+ load_classes: true, # Default: false
177
+ }
178
+
179
+ # generates the files in the output_dir | default_dir
180
+ Lutaml::Model::Schema.from_xml(xsd_schema, options: options)
181
+ ----
182
+ ====
183
+
184
+ ==== Working with generated models
185
+
186
+ You can use the models directly if you set `load_classes: true`:
187
+
188
+ [example]
189
+ ====
190
+ [source,ruby]
191
+ ----
192
+ # Generate and load the models
193
+ Lutaml::Model::Schema.from_xml(xsd_schema, options: {load_classes: true})
194
+
195
+ # Create a new User instance
196
+ user = User.new(id: 1112, age: 29, token: "u9dId901dp13f")
197
+
198
+ # Serialize to XML
199
+ xml = user.to_xml
200
+ # => "<User>\n <id>1112</id>\n <age>29</age>\n <token>u9dId901dp13f</token>\n</User>"
201
+
202
+ # Parse from XML
203
+ parsed_user = User.from_xml(xml)
204
+ parsed_user.id # => 1112
205
+ parsed_user.age # => 29
206
+ parsed_user.token # => "u9dId901dp13f"
207
+ ----
208
+ ====
209
+
210
+ Alternatively, you could directly load the generated Ruby files into your application by
211
+ requiring them:
212
+
213
+ [example]
214
+ ====
215
+ [source,ruby]
216
+ ----
217
+ Lutaml::Model::Schema.from_xml(xsd_schema, options: {output_dir: 'path/to/directory', create_files: true})
218
+ require_relative 'path/to/directory/*.rb'
219
+ ----
220
+ ====
221
+
222
+ === JSON/YAML Schema import
223
+
224
+ Lutaml::Model supports importing JSON Schema definitions to generate Ruby model classes. This enables you to create Ruby models that match your JSON Schema, supporting schema-driven development and interoperability.
225
+
226
+ ==== Overview
227
+
228
+ The `Lutaml::Model::Schema::JsonSchema.generate_model_classes` method takes a JSON Schema (as a Ruby hash) and generates Ruby class definitions for each schema in the `$defs` section.
229
+
230
+ - Each generated class inherits from `Lutaml::Model::Serializable`.
231
+ - Attributes are created based on the schema's properties.
232
+ - The output is a hash mapping definition names to Ruby class code (as strings).
233
+
234
+ ==== Usage
235
+
236
+ [source,ruby]
237
+ ----
238
+ require 'lutaml/model/schema/json_schema'
239
+ require 'json'
240
+
241
+ # Load your JSON Schema (as a Ruby hash)
242
+ schema = JSON.parse(File.read("your_schema.json"))
243
+
244
+ # Generate Ruby model class definitions as strings
245
+ model_classes = Lutaml::Model::Schema::JsonSchema.generate_model_classes(schema)
246
+
247
+ # model_classes is a hash mapping definition names to Ruby class code
248
+ puts model_classes["YourDefinitionName"]
249
+ ----
250
+
251
+ ==== Example
252
+
253
+ Given a JSON Schema with a `$defs` section:
254
+
255
+ [source,json]
256
+ ----
257
+ {
258
+ "$defs": {
259
+ "Person": {
260
+ "type": "object",
261
+ "properties": {
262
+ "name": { "type": "string" },
263
+ "age": { "type": "integer" }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ ----
269
+
270
+ The generated Ruby class will look like:
271
+
272
+ [source,ruby]
273
+ ----
274
+ class Person < Lutaml::Model::Serializable
275
+ attribute "name", :string
276
+ attribute "age", :integer
277
+ end
278
+ ----
279
+
280
+ ==== Polymorphic Classes and `oneOf` Support
281
+
282
+ Polymorphism allows you to define a common interface for multiple classes, enabling them to be used interchangeably. In JSON Schema, polymorphism is often represented using the `oneOf` keyword, which specifies that a value must validate against exactly one of the given schemas.
283
+
284
+ For example:
285
+
286
+ [source,json]
287
+ ----
288
+ {
289
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
290
+ "$ref": "#/$defs/PolymorphicModel",
291
+ "$defs": {
292
+ "PolymorphicModel": {
293
+ "type": "object",
294
+ "additionalProperties": false,
295
+ "properties": {
296
+ "shape": {
297
+ "type": ["object", "null"],
298
+ "oneOf": [
299
+ { "$ref": "#/$defs/Circle" },
300
+ { "$ref": "#/$defs/Square" },
301
+ { "$ref": "#/$defs/Shape" }
302
+ ]
303
+ }
304
+ }
305
+ },
306
+ "Circle": {
307
+ "type": "object",
308
+ "additionalProperties": false,
309
+ "properties": {
310
+ "area": { "type": ["number", "null"] },
311
+ "radius": { "type": ["number", "null"] }
312
+ }
313
+ },
314
+ "Square": {
315
+ "type": "object",
316
+ "additionalProperties": false,
317
+ "properties": {
318
+ "area": { "type": ["number", "null"] },
319
+ "side": { "type": ["number", "null"] }
320
+ }
321
+ },
322
+ "Shape": {
323
+ "type": "object",
324
+ "additionalProperties": false,
325
+ "properties": {
326
+ "area": { "type": ["number", "null"] }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ ----
332
+
333
+ ===== Detecting the Parent Class
334
+
335
+ When using `oneOf`, you can infer the parent class by identifying the common attributes shared by all referenced schemas. In the example above, all referenced types (`Circle`, `Square`, `Shape`) have an `area` property, so a `Shape` parent class can be defined with this attribute. The subclasses (`Circle`, `Square`) then add their specific attributes.
336
+
337
+ This pattern allows Lutaml::Model to:
338
+
339
+ - Detect the parent class by finding the intersection of attributes in all `oneOf` schemas.
340
+ - Generate a base class with the shared attributes.
341
+ - Generate subclasses for each specific schema, inheriting from the base class and adding unique attributes.
342
+
343
+ ===== Example Ruby Mapping
344
+
345
+ [source,ruby]
346
+ ----
347
+ class Shape < Lutaml::Model::Serializable
348
+ attribute :area, :float
349
+ end
350
+
351
+ class Circle < Shape
352
+ attribute :radius, :float
353
+ end
354
+
355
+ class Square < Shape
356
+ attribute :side, :float
357
+ end
358
+
359
+ class PolymorphicModel < Lutaml::Model::Serializable
360
+ attribute :shape, :Shape, polymorphic: [Circle, Square]
361
+ end
362
+ ----
363
+
364
+ This approach enables polymorphic deserialization and validation, matching the intent of the JSON
data/flake.lock ADDED
@@ -0,0 +1,114 @@
1
+ {
2
+ "nodes": {
3
+ "devshell": {
4
+ "inputs": {
5
+ "nixpkgs": "nixpkgs"
6
+ },
7
+ "locked": {
8
+ "lastModified": 1735644329,
9
+ "narHash": "sha256-tO3HrHriyLvipc4xr+Ewtdlo7wM1OjXNjlWRgmM7peY=",
10
+ "owner": "numtide",
11
+ "repo": "devshell",
12
+ "rev": "f7795ede5b02664b57035b3b757876703e2c3eac",
13
+ "type": "github"
14
+ },
15
+ "original": {
16
+ "owner": "numtide",
17
+ "ref": "main",
18
+ "repo": "devshell",
19
+ "type": "github"
20
+ }
21
+ },
22
+ "flake-compat": {
23
+ "flake": false,
24
+ "locked": {
25
+ "lastModified": 1733328505,
26
+ "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
27
+ "owner": "edolstra",
28
+ "repo": "flake-compat",
29
+ "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
30
+ "type": "github"
31
+ },
32
+ "original": {
33
+ "owner": "edolstra",
34
+ "repo": "flake-compat",
35
+ "type": "github"
36
+ }
37
+ },
38
+ "flake-utils": {
39
+ "inputs": {
40
+ "systems": "systems"
41
+ },
42
+ "locked": {
43
+ "lastModified": 1731533236,
44
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
45
+ "owner": "numtide",
46
+ "repo": "flake-utils",
47
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
48
+ "type": "github"
49
+ },
50
+ "original": {
51
+ "owner": "numtide",
52
+ "repo": "flake-utils",
53
+ "type": "github"
54
+ }
55
+ },
56
+ "nixpkgs": {
57
+ "locked": {
58
+ "lastModified": 1722073938,
59
+ "narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=",
60
+ "owner": "NixOS",
61
+ "repo": "nixpkgs",
62
+ "rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae",
63
+ "type": "github"
64
+ },
65
+ "original": {
66
+ "owner": "NixOS",
67
+ "ref": "nixpkgs-unstable",
68
+ "repo": "nixpkgs",
69
+ "type": "github"
70
+ }
71
+ },
72
+ "nixpkgs_2": {
73
+ "locked": {
74
+ "lastModified": 1739959802,
75
+ "narHash": "sha256-WEWM1ylf1XMC355xzf9aUFVUB9SrRexayeTXk8LA7SU=",
76
+ "owner": "nixos",
77
+ "repo": "nixpkgs",
78
+ "rev": "d254fd973c0ce7c7c8ae2a4251d9d801368508e3",
79
+ "type": "github"
80
+ },
81
+ "original": {
82
+ "owner": "nixos",
83
+ "ref": "master",
84
+ "repo": "nixpkgs",
85
+ "type": "github"
86
+ }
87
+ },
88
+ "root": {
89
+ "inputs": {
90
+ "devshell": "devshell",
91
+ "flake-compat": "flake-compat",
92
+ "flake-utils": "flake-utils",
93
+ "nixpkgs": "nixpkgs_2"
94
+ }
95
+ },
96
+ "systems": {
97
+ "locked": {
98
+ "lastModified": 1681028828,
99
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
100
+ "owner": "nix-systems",
101
+ "repo": "default",
102
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
103
+ "type": "github"
104
+ },
105
+ "original": {
106
+ "owner": "nix-systems",
107
+ "repo": "default",
108
+ "type": "github"
109
+ }
110
+ }
111
+ },
112
+ "root": "root",
113
+ "version": 7
114
+ }
data/flake.nix ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ description = "Ruby Dev Env";
3
+ inputs = {
4
+ nixpkgs.url = "github:nixos/nixpkgs/master";
5
+ flake-utils.url = "github:numtide/flake-utils";
6
+ devshell.url = "github:numtide/devshell/main";
7
+ flake-compat = {
8
+ url = "github:edolstra/flake-compat";
9
+ flake = false;
10
+ };
11
+ };
12
+ outputs =
13
+ { self
14
+ , nixpkgs
15
+ , flake-utils
16
+ , devshell
17
+ , flake-compat
18
+ , ...
19
+ }:
20
+ flake-utils.lib.eachDefaultSystem (system:
21
+ let
22
+ cwd = builtins.toString ./.;
23
+ overlays = map (x: x.overlays.default) [
24
+ devshell
25
+ ];
26
+ pkgs = import nixpkgs { inherit system overlays; };
27
+ in
28
+ rec {
29
+
30
+ # nix develop
31
+ devShell = pkgs.devshell.mkShell {
32
+ env = [
33
+ ];
34
+ commands = [
35
+ {
36
+ name = "irb";
37
+ command = "bundle exec irb \"$@\"";
38
+ help = "Run console IRB (has completion menu)";
39
+ category = "Ruby";
40
+ }
41
+ {
42
+ name = "console";
43
+ command = "bundle exec irb \"$@\"";
44
+ help = "Run console IRB (has completion menu)";
45
+ category = "Ruby";
46
+ }
47
+ {
48
+ name = "pry";
49
+ command = "bundle exec pry \"$@\"";
50
+ help = "Run pry";
51
+ category = "Ruby";
52
+ }
53
+ {
54
+ name = "release";
55
+ command = "bundle exec rake release \"$@\"";
56
+ help = "Run rake release, which adds a tag and pushes to RubyGems";
57
+ category = "Ruby";
58
+ }
59
+ {
60
+ name = "rubocop";
61
+ command = "bundle exec rubocop \"$@\"";
62
+ help = "Run rubocop";
63
+ category = "Ruby";
64
+ }
65
+ {
66
+ name = "lint";
67
+ command = "bundle exec rubocop \"$@\"";
68
+ help = "Run rubocop";
69
+ category = "Ruby";
70
+ }
71
+ {
72
+ name = "rspec";
73
+ command = "bundle exec rspec \"$@\"";
74
+ help = "Run test suite";
75
+ category = "Ruby";
76
+ }
77
+ {
78
+ name = "update-flakes";
79
+ command = "make update-flakes \"$@\"";
80
+ help = "Update all flakes";
81
+ category = "Nix";
82
+ }
83
+ ];
84
+ packages = with pkgs; [
85
+ bash
86
+ curl
87
+ fd
88
+ # fzf
89
+ gnused
90
+ jq
91
+ nodejs
92
+ # rubocop
93
+ # ruby
94
+ # rubyfmt # Broken
95
+ rubyPackages.ruby-lsp
96
+ rubyPackages.solargraph
97
+ rubyPackages.sorbet-runtime
98
+ ripgrep
99
+ wget
100
+ ];
101
+ };
102
+ });
103
+ }