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,898 @@
1
+ = Schema generation
2
+
3
+ This document describes how to generate schemas from LutaML models.
4
+
5
+ == Schema generation
6
+
7
+ === Overview
8
+
9
+ Lutaml::Model provides functionality to generate schema definitions from LutaML
10
+ models. This allows you to create schemas that can be used for validation or
11
+ documentation purposes.
12
+
13
+ The following figure illustrates the process of generating schemas from LutaML models.
14
+ Once the LutaML models are defined, they can be transformed into various schema formats
15
+ that can be used for validation, documentation, or other purposes.
16
+
17
+ .Generating serialization schemas from LutaML models
18
+ [source]
19
+ ----
20
+ ╔═══════════════════════╗ ╔════════════════════════════╗
21
+ ║ Core Model ║ ║ Serialization Models ║
22
+ ╚═══════════════════════╝ ╚════════════════════════════╝
23
+
24
+ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
25
+ ┆ Model ┆ ┆ JSON/YAML/XML Schema ┆
26
+ ┆ │ ┆ ┌────────────────┐ ┆ │ ┆
27
+ ┆ ┌────────┴──┐ ┆ │ │ ┆ ┌──────┴──────┐ ┆
28
+ ┆ │ │ ┆ │ Schema │ ┆ │ │ ┆
29
+ ┆ Models Value Types ┆──►│ Generation │──►┆ Elements Validation ┆
30
+ ┆ │ │ ┆ │ │ ┆ │ │ ┆
31
+ ┆ │ │ ┆ └────────────────┘ ┆ │ │ ┆
32
+ ┆ │ ┌──────┴──┐ ┆ │ ┆ ┌────┴────┐ ┌───┴───┐ ┆
33
+ ┆ │ │ │ ┆ │ ┆ │ │ │ │ ┆
34
+ ┆ │ String Integer ┆ │ ┆ Properties Patterns Enums ┆
35
+ ┆ │ Date Float ┆ │ ┆ Definitions References ┆
36
+ ┆ │ Time Boolean ┆ │ ┆ Types Min/Max ┆
37
+ ┆ │ ┆ │ ┆ OneOf ┆
38
+ ┆ └──────┐ ┆ │ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯
39
+ ┆ │ ┆ │
40
+ ┆ Contains ┆ │ ┌────────────────┐
41
+ ┆ more Models ┆ │ │ │
42
+ ┆ (recursive) ┆ └───────────►│ Schema │
43
+ ┆ ┆ │ Validation │
44
+ ┆ ┆ │ │
45
+ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ └────────────────┘
46
+ ----
47
+
48
+ Currently, the following schema formats are supported:
49
+
50
+ * JSON Schema (https://json-schema.org/understanding-json-schema/[JSON Schema])
51
+ * YAML Schema (https://yaml.org/spec/1.2/spec.html[YAML])
52
+ * XSD (https://w3.org/TR/xmlschema-1/[XML Schema Definition Language])
53
+ * RELAX NG (https://relaxng.org/[RELAX NG])
54
+
55
+
56
+ === JSON Schema generation
57
+
58
+ The `Lutaml::Model::Schema.to_json` method generates a JSON Schema from a LutaML
59
+ model class. The generated schema includes:
60
+
61
+ * Properties based on model attributes
62
+ * Validation constraints (patterns, enumerations, etc.)
63
+ * Support for polymorphic types
64
+ * Support for inheritance
65
+ * Support for choice attributes
66
+ * Collection constraints
67
+
68
+ ==== Basic example
69
+
70
+ [example]
71
+ ====
72
+ [source,ruby]
73
+ ----
74
+ class Glaze < Lutaml::Model::Serializable
75
+ attribute :color, Lutaml::Model::Type::String
76
+ attribute :finish, Lutaml::Model::Type::String
77
+ end
78
+
79
+ class Vase < Lutaml::Model::Serializable
80
+ attribute :height, Lutaml::Model::Type::Float
81
+ attribute :diameter, Lutaml::Model::Type::Float
82
+ attribute :glaze, Glaze
83
+ attribute :materials, Lutaml::Model::Type::String, collection: true
84
+ end
85
+
86
+ # Generate JSON schema
87
+ schema = Lutaml::Model::Schema.to_json(
88
+ Vase,
89
+ id: "https://example.com/vase.schema.json",
90
+ description: "A vase schema",
91
+ pretty: true
92
+ )
93
+
94
+ # Write to file
95
+ File.write("vase.schema.json", schema)
96
+ ----
97
+ ====
98
+
99
+ The generated schema will include definitions for all nested models and their
100
+ attributes. The output JSON schema would look like:
101
+
102
+ [source,json]
103
+ ----
104
+ {
105
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
106
+ "$id": "https://example.com/vase.schema.json",
107
+ "description": "A vase schema",
108
+ "$ref": "#/$defs/Vase",
109
+ "$defs": {
110
+ "Vase": {
111
+ "type": "object",
112
+ "additionalProperties": false,
113
+ "properties": {
114
+ "height": {
115
+ "type": ["number", "null"]
116
+ },
117
+ "diameter": {
118
+ "type": ["number", "null"]
119
+ },
120
+ "glaze": {
121
+ "$ref": "#/$defs/Glaze"
122
+ },
123
+ "materials": {
124
+ "type": "array",
125
+ "items": {
126
+ "type": "string"
127
+ }
128
+ }
129
+ }
130
+ },
131
+ "Glaze": {
132
+ "type": "object",
133
+ "additionalProperties": false,
134
+ "properties": {
135
+ "color": {
136
+ "type": ["string", "null"]
137
+ },
138
+ "finish": {
139
+ "type": ["string", "null"]
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ ----
146
+
147
+ ==== Advanced examples
148
+
149
+ ===== Models with validation constraints
150
+
151
+ [example]
152
+ ====
153
+ [source,ruby]
154
+ ----
155
+ class ValidationModel < Lutaml::Model::Serializable
156
+ attribute :name, Lutaml::Model::Type::String, values: %w[Alice Bob Charlie]
157
+ attribute :email, Lutaml::Model::Type::String, pattern: /.*?\S+@.+\.\S+/
158
+ attribute :age, Lutaml::Model::Type::Integer, collection: 1..3
159
+ attribute :score, Lutaml::Model::Type::Float, default: 0.0
160
+ end
161
+
162
+ # Generate JSON schema
163
+ schema = Lutaml::Model::Schema.to_json(ValidationModel, pretty: true)
164
+ ----
165
+ ====
166
+
167
+ The generated schema will include validation constraints:
168
+
169
+ [source,json]
170
+ ----
171
+ {
172
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
173
+ "$ref": "#/$defs/ValidationModel",
174
+ "$defs": {
175
+ "ValidationModel": {
176
+ "type": "object",
177
+ "additionalProperties": false,
178
+ "properties": {
179
+ "name": {
180
+ "type": ["string", "null"],
181
+ "enum": ["Alice", "Bob", "Charlie"]
182
+ },
183
+ "email": {
184
+ "type": ["string", "null"],
185
+ "pattern": ".*?\\S+@.+\\.\\S+"
186
+ },
187
+ "age": {
188
+ "type": "array",
189
+ "items": {
190
+ "type": "integer"
191
+ },
192
+ "minItems": 1,
193
+ "maxItems": 3
194
+ },
195
+ "score": {
196
+ "type": ["number", "null"],
197
+ "default": 0.0
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ ----
204
+
205
+ ===== Models with choice attributes
206
+
207
+ [example]
208
+ ====
209
+ [source,ruby]
210
+ ----
211
+ class ChoiceModel < Lutaml::Model::Serializable
212
+ attribute :name, Lutaml::Model::Type::String
213
+ attribute :email, Lutaml::Model::Type::String
214
+ attribute :phone, Lutaml::Model::Type::String
215
+
216
+ choice(min: 1, max: 2) do
217
+ attribute :email, Lutaml::Model::Type::String
218
+ attribute :phone, Lutaml::Model::Type::String
219
+ end
220
+ end
221
+
222
+ # Generate JSON schema
223
+ schema = Lutaml::Model::Schema.to_json(ChoiceModel, pretty: true)
224
+ ----
225
+ ====
226
+
227
+ The generated schema will include choice constraints:
228
+
229
+ [source,json]
230
+ ----
231
+ {
232
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
233
+ "$ref": "#/$defs/ChoiceModel",
234
+ "$defs": {
235
+ "ChoiceModel": {
236
+ "type": "object",
237
+ "additionalProperties": false,
238
+ "properties": {
239
+ "name": {
240
+ "type": ["string", "null"]
241
+ },
242
+ "email": {
243
+ "type": ["string", "null"]
244
+ },
245
+ "phone": {
246
+ "type": ["string", "null"]
247
+ }
248
+ },
249
+ "oneOf": [
250
+ {
251
+ "type": "object",
252
+ "properties": {
253
+ "email": {
254
+ "type": ["string", "null"]
255
+ },
256
+ "phone": {
257
+ "type": ["string", "null"]
258
+ }
259
+ }
260
+ }
261
+ ]
262
+ }
263
+ }
264
+ }
265
+ ----
266
+
267
+ ===== Models with polymorphic types
268
+
269
+ [example]
270
+ ====
271
+ [source,ruby]
272
+ ----
273
+ class Shape < Lutaml::Model::Serializable
274
+ attribute :area, :float
275
+ end
276
+
277
+ class Circle < Shape
278
+ attribute :radius, Lutaml::Model::Type::Float
279
+ end
280
+
281
+ class Square < Shape
282
+ attribute :side, Lutaml::Model::Type::Float
283
+ end
284
+
285
+ class PolymorphicModel < Lutaml::Model::Serializable
286
+ attribute :shape, Shape, polymorphic: [Circle, Square]
287
+ end
288
+
289
+ # Generate JSON schema
290
+ schema = Lutaml::Model::Schema.to_json(PolymorphicModel, pretty: true)
291
+ ----
292
+ ====
293
+
294
+ The generated schema will include polymorphic type constraints:
295
+
296
+ [source,json]
297
+ ----
298
+ {
299
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
300
+ "$ref": "#/$defs/PolymorphicModel",
301
+ "$defs": {
302
+ "PolymorphicModel": {
303
+ "type": "object",
304
+ "additionalProperties": false,
305
+ "properties": {
306
+ "shape": {
307
+ "type": ["object", "null"],
308
+ "oneOf": [
309
+ {
310
+ "$ref": "#/$defs/Circle"
311
+ },
312
+ {
313
+ "$ref": "#/$defs/Square"
314
+ },
315
+ {
316
+ "$ref": "#/$defs/Shape"
317
+ }
318
+ ]
319
+ }
320
+ }
321
+ },
322
+ "Circle": {
323
+ "type": "object",
324
+ "additionalProperties": false,
325
+ "properties": {
326
+ "area": {
327
+ "type": ["number", "null"]
328
+ },
329
+ "radius": {
330
+ "type": ["number", "null"]
331
+ }
332
+ }
333
+ },
334
+ "Square": {
335
+ "type": "object",
336
+ "additionalProperties": false,
337
+ "properties": {
338
+ "area": {
339
+ "type": ["number", "null"]
340
+ },
341
+ "side": {
342
+ "type": ["number", "null"]
343
+ }
344
+ }
345
+ },
346
+ "Shape": {
347
+ "type": "object",
348
+ "additionalProperties": false,
349
+ "properties": {
350
+ "area": {
351
+ "type": ["number", "null"]
352
+ }
353
+ }
354
+ }
355
+ }
356
+ }
357
+ ----
358
+
359
+ === YAML Schema generation
360
+
361
+ The `Lutaml::Model::Schema.to_yaml` method generates a YAML Schema from a LutaML
362
+ model class. The generated schema includes the same features as the JSON Schema
363
+ generation.
364
+
365
+ ==== Basic example
366
+
367
+ [example]
368
+ ====
369
+ [source,ruby]
370
+ ----
371
+ class Glaze < Lutaml::Model::Serializable
372
+ attribute :color, Lutaml::Model::Type::String
373
+ attribute :finish, Lutaml::Model::Type::String
374
+ end
375
+
376
+ class Vase < Lutaml::Model::Serializable
377
+ attribute :height, Lutaml::Model::Type::Float
378
+ attribute :diameter, Lutaml::Model::Type::Float
379
+ attribute :glaze, Glaze
380
+ attribute :materials, Lutaml::Model::Type::String, collection: true
381
+ end
382
+
383
+ # Generate YAML schema
384
+ schema = Lutaml::Model::Schema.to_yaml(
385
+ Vase,
386
+ id: "http://stsci.edu/schemas/yaml-schema/draft-01",
387
+ description: "A vase schema",
388
+ pretty: true
389
+ )
390
+
391
+ # Write to file
392
+ File.write("vase.schema.yaml", schema)
393
+ ----
394
+ ====
395
+
396
+ The generated YAML schema would look like:
397
+
398
+ [source,yaml]
399
+ ----
400
+ %YAML 1.1
401
+ ---
402
+ "$schema": https://json-schema.org/draft/2020-12/schema
403
+ "$id": http://stsci.edu/schemas/yaml-schema/draft-01
404
+ description: A vase schema
405
+ "$ref": "#/$defs/Vase"
406
+ "$defs":
407
+ Vase:
408
+ type: object
409
+ additionalProperties: false
410
+ properties:
411
+ height:
412
+ type:
413
+ - number
414
+ - 'null'
415
+ diameter:
416
+ type:
417
+ - number
418
+ - 'null'
419
+ glaze:
420
+ "$ref": "#/$defs/Glaze"
421
+ materials:
422
+ type: array
423
+ items:
424
+ type: string
425
+ Glaze:
426
+ type: object
427
+ additionalProperties: false
428
+ properties:
429
+ color:
430
+ type:
431
+ - string
432
+ - 'null'
433
+ finish:
434
+ type:
435
+ - string
436
+ - 'null'
437
+ ----
438
+
439
+ ==== Advanced examples
440
+
441
+ ===== Models with validation constraints
442
+
443
+ [example]
444
+ ====
445
+ [source,ruby]
446
+ ----
447
+ class ValidationModel < Lutaml::Model::Serializable
448
+ attribute :name, Lutaml::Model::Type::String, values: %w[Alice Bob Charlie]
449
+ attribute :email, Lutaml::Model::Type::String, pattern: /.*?\S+@.+\.\S+/
450
+ attribute :age, Lutaml::Model::Type::Integer, collection: 1..3
451
+ attribute :score, Lutaml::Model::Type::Float, default: 0.0
452
+ end
453
+
454
+ # Generate YAML schema
455
+ schema = Lutaml::Model::Schema.to_yaml(ValidationModel)
456
+ ----
457
+ ====
458
+
459
+ The generated schema will include validation constraints:
460
+
461
+ [source,yaml]
462
+ ----
463
+ %YAML 1.1
464
+ ---
465
+ "$schema": https://json-schema.org/draft/2020-12/schema
466
+ "$ref": "#/$defs/ValidationModel"
467
+ "$defs":
468
+ ValidationModel:
469
+ type: object
470
+ additionalProperties: false
471
+ properties:
472
+ name:
473
+ type:
474
+ - string
475
+ - 'null'
476
+ enum:
477
+ - Alice
478
+ - Bob
479
+ - Charlie
480
+ email:
481
+ type:
482
+ - string
483
+ - 'null'
484
+ pattern: ".*?\\S+@.+\\.\\S+"
485
+ age:
486
+ type: array
487
+ items:
488
+ type: integer
489
+ minItems: 1
490
+ maxItems: 3
491
+ score:
492
+ type:
493
+ - number
494
+ - 'null'
495
+ default: 0.0
496
+ ----
497
+
498
+ ===== Models with polymorphic types
499
+
500
+ [example]
501
+ ====
502
+ [source,ruby]
503
+ ----
504
+ class Shape < Lutaml::Model::Serializable
505
+ attribute :area, :float
506
+ end
507
+
508
+ class Circle < Shape
509
+ attribute :radius, Lutaml::Model::Type::Float
510
+ end
511
+
512
+ class Square < Shape
513
+ attribute :side, Lutaml::Model::Type::Float
514
+ end
515
+
516
+ class PolymorphicModel < Lutaml::Model::Serializable
517
+ attribute :shape, Shape, polymorphic: [Circle, Square]
518
+ end
519
+
520
+ # Generate YAML schema
521
+ schema = Lutaml::Model::Schema.to_yaml(PolymorphicModel)
522
+ ----
523
+ ====
524
+
525
+ The generated schema will include polymorphic type constraints:
526
+
527
+ [source,yaml]
528
+ ----
529
+ %YAML 1.1
530
+ ---
531
+ "$schema": https://json-schema.org/draft/2020-12/schema
532
+ "$ref": "#/$defs/PolymorphicModel"
533
+ "$defs":
534
+ PolymorphicModel:
535
+ type: object
536
+ additionalProperties: false
537
+ properties:
538
+ shape:
539
+ type:
540
+ - object
541
+ - 'null'
542
+ oneOf:
543
+ - "$ref": "#/$defs/Circle"
544
+ - "$ref": "#/$defs/Square"
545
+ - "$ref": "#/$defs/Shape"
546
+ Shape:
547
+ type: object
548
+ additionalProperties: false
549
+ properties:
550
+ area:
551
+ type:
552
+ - number
553
+ - 'null'
554
+ Circle:
555
+ type: object
556
+ additionalProperties: false
557
+ properties:
558
+ area:
559
+ type:
560
+ - number
561
+ - 'null'
562
+ radius:
563
+ type:
564
+ - number
565
+ - 'null'
566
+ Square:
567
+ type: object
568
+ additionalProperties: false
569
+ properties:
570
+ area:
571
+ type:
572
+ - number
573
+ - 'null'
574
+ side:
575
+ type:
576
+ - number
577
+ - 'null'
578
+ ----
579
+
580
+ === XSD Schema generation
581
+
582
+ The `Lutaml::Model::Schema.to_xsd` method generates an XML Schema (XSD) from a LutaML
583
+ model class. The generated schema includes:
584
+
585
+ * Element definitions based on model attributes
586
+ * Complex types for nested models
587
+ * Support for collections
588
+ * XML namespace support
589
+ * Validation constraints
590
+
591
+ ==== Basic example
592
+
593
+ [example]
594
+ ====
595
+ [source,ruby]
596
+ ----
597
+ class Glaze < Lutaml::Model::Serializable
598
+ attribute :color, Lutaml::Model::Type::String
599
+ attribute :finish, Lutaml::Model::Type::String
600
+ end
601
+
602
+ class Vase < Lutaml::Model::Serializable
603
+ attribute :height, Lutaml::Model::Type::Float
604
+ attribute :diameter, Lutaml::Model::Type::Float
605
+ attribute :glaze, Glaze
606
+ attribute :materials, Lutaml::Model::Type::String, collection: true
607
+ end
608
+
609
+ # Generate XSD schema
610
+ schema = Lutaml::Model::Schema.to_xsd(
611
+ Vase,
612
+ namespace: "http://example.com/vase",
613
+ prefix: "vase",
614
+ pretty: true
615
+ )
616
+
617
+ # Write to file
618
+ File.write("vase.xsd", schema)
619
+ ----
620
+ ====
621
+
622
+ The generated XSD schema would look like:
623
+
624
+ [source,xml]
625
+ ----
626
+ <?xml version="1.0" encoding="UTF-8"?>
627
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
628
+ xmlns:vase="http://example.com/vase"
629
+ targetNamespace="http://example.com/vase"
630
+ elementFormDefault="qualified">
631
+ <xs:element name="Vase">
632
+ <xs:complexType>
633
+ <xs:sequence>
634
+ <xs:element name="height" type="xs:float"/>
635
+ <xs:element name="diameter" type="xs:float"/>
636
+ <xs:element name="glaze" type="vase:Glaze"/>
637
+ <xs:element name="materials" minOccurs="0" maxOccurs="unbounded" type="xs:string"/>
638
+ </xs:sequence>
639
+ </xs:complexType>
640
+ </xs:element>
641
+
642
+ <xs:complexType name="Glaze">
643
+ <xs:sequence>
644
+ <xs:element name="color" type="xs:string"/>
645
+ <xs:element name="finish" type="xs:string"/>
646
+ </xs:sequence>
647
+ </xs:complexType>
648
+ </xs:schema>
649
+ ----
650
+
651
+ ==== Advanced examples
652
+
653
+ ===== Models with validation constraints
654
+
655
+ [example]
656
+ ====
657
+ [source,ruby]
658
+ ----
659
+ class ValidationModel < Lutaml::Model::Serializable
660
+ attribute :name, Lutaml::Model::Type::String, values: %w[Alice Bob Charlie]
661
+ attribute :email, Lutaml::Model::Type::String, pattern: /.*?\S+@.+\.\S+/
662
+ attribute :age, Lutaml::Model::Type::Integer, collection: 1..3
663
+ attribute :score, Lutaml::Model::Type::Float, default: 0.0
664
+ end
665
+
666
+ # Generate XSD schema
667
+ schema = Lutaml::Model::Schema.to_xsd(ValidationModel, pretty: true)
668
+ ----
669
+ ====
670
+
671
+ The generated schema will include validation constraints:
672
+
673
+ [source,xml]
674
+ ----
675
+ <?xml version="1.0" encoding="UTF-8"?>
676
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
677
+ <xs:element name="ValidationModel">
678
+ <xs:complexType>
679
+ <xs:sequence>
680
+ <xs:element name="name">
681
+ <xs:simpleType>
682
+ <xs:restriction base="xs:string">
683
+ <xs:enumeration value="Alice"/>
684
+ <xs:enumeration value="Bob"/>
685
+ <xs:enumeration value="Charlie"/>
686
+ </xs:restriction>
687
+ </xs:simpleType>
688
+ </xs:element>
689
+ <xs:element name="email">
690
+ <xs:simpleType>
691
+ <xs:restriction base="xs:string">
692
+ <xs:pattern value=".*?\S+@.+\.\S+"/>
693
+ </xs:restriction>
694
+ </xs:simpleType>
695
+ </xs:element>
696
+ <xs:element name="age" minOccurs="1" maxOccurs="3" type="xs:integer"/>
697
+ <xs:element name="score" type="xs:float" default="0.0"/>
698
+ </xs:sequence>
699
+ </xs:complexType>
700
+ </xs:element>
701
+ </xs:schema>
702
+ ----
703
+
704
+ ===== Models with polymorphic types
705
+
706
+ [example]
707
+ ====
708
+ [source,ruby]
709
+ ----
710
+ class Shape < Lutaml::Model::Serializable
711
+ attribute :area, :float
712
+ end
713
+
714
+ class Circle < Shape
715
+ attribute :radius, Lutaml::Model::Type::Float
716
+ end
717
+
718
+ class Square < Shape
719
+ attribute :side, Lutaml::Model::Type::Float
720
+ end
721
+
722
+ class PolymorphicModel < Lutaml::Model::Serializable
723
+ attribute :shape, Shape, polymorphic: [Circle, Square]
724
+ end
725
+
726
+ # Generate XSD schema
727
+ schema = Lutaml::Model::Schema.to_xsd(PolymorphicModel, pretty: true)
728
+ ----
729
+ ====
730
+
731
+ The generated schema will include polymorphic type support:
732
+
733
+ [source,xml]
734
+ ----
735
+ <?xml version="1.0" encoding="UTF-8"?>
736
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
737
+ <xs:element name="PolymorphicModel">
738
+ <xs:complexType>
739
+ <xs:sequence>
740
+ <xs:element name="shape">
741
+ <xs:complexType>
742
+ <xs:choice>
743
+ <xs:element name="Circle">
744
+ <xs:complexType>
745
+ <xs:sequence>
746
+ <xs:element name="area" type="xs:float"/>
747
+ <xs:element name="radius" type="xs:float"/>
748
+ </xs:sequence>
749
+ </xs:complexType>
750
+ </xs:element>
751
+ <xs:element name="Square">
752
+ <xs:complexType>
753
+ <xs:sequence>
754
+ <xs:element name="area" type="xs:float"/>
755
+ <xs:element name="side" type="xs:float"/>
756
+ </xs:sequence>
757
+ </xs:complexType>
758
+ </xs:element>
759
+ <xs:element name="Shape">
760
+ <xs:complexType>
761
+ <xs:sequence>
762
+ <xs:element name="area" type="xs:float"/>
763
+ </xs:sequence>
764
+ </xs:complexType>
765
+ </xs:element>
766
+ </xs:choice>
767
+ </xs:complexType>
768
+ </xs:element>
769
+ </xs:sequence>
770
+ </xs:complexType>
771
+ </xs:element>
772
+ </xs:schema>
773
+ ----
774
+
775
+ === RELAX NG Schema generation
776
+
777
+ The `Lutaml::Model::Schema::RelaxngSchema.generate` method generates a RELAX NG schema from a LutaML
778
+ model class. The generated schema includes:
779
+
780
+ * Element definitions based on model attributes
781
+ * Named patterns for nested models
782
+ * Support for collections
783
+
784
+ ==== Basic example
785
+
786
+ [example]
787
+ ====
788
+ [source,ruby]
789
+ ----
790
+ class Glaze < Lutaml::Model::Serializable
791
+ attribute :color, Lutaml::Model::Type::String
792
+ attribute :finish, Lutaml::Model::Type::String
793
+ end
794
+
795
+ class Vase < Lutaml::Model::Serializable
796
+ attribute :height, Lutaml::Model::Type::Float
797
+ attribute :diameter, Lutaml::Model::Type::Float
798
+ attribute :glaze, Glaze
799
+ attribute :materials, Lutaml::Model::Type::String, collection: true
800
+ end
801
+
802
+ # Generate RELAX NG schema
803
+ schema = Lutaml::Model::Schema::RelaxngSchema.generate(Vase, pretty: true)
804
+
805
+ # Write to file
806
+ File.write("vase.rng", schema)
807
+ ----
808
+ ====
809
+
810
+ The generated RELAX NG schema would look like:
811
+
812
+ [source,xml]
813
+ ----
814
+ <?xml version="1.0" encoding="UTF-8"?>
815
+ <grammar xmlns="http://relaxng.org/ns/structure/1.0">
816
+ <start>
817
+ <ref name="Vase"/>
818
+ </start>
819
+ <define name="Vase">
820
+ <element name="Vase">
821
+ <element name="height">
822
+ <data type="float"/>
823
+ </element>
824
+ <element name="diameter">
825
+ <data type="float"/>
826
+ </element>
827
+ <ref name="Glaze"/>
828
+ <zeroOrMore>
829
+ <element name="materials">
830
+ <data type="string"/>
831
+ </element>
832
+ </zeroOrMore>
833
+ </element>
834
+ </define>
835
+ <define name="Glaze">
836
+ <element name="Glaze">
837
+ <element name="color">
838
+ <data type="string"/>
839
+ </element>
840
+ <element name="finish">
841
+ <data type="string"/>
842
+ </element>
843
+ </element>
844
+ </define>
845
+ </grammar>
846
+ ----
847
+
848
+ ==== Advanced examples
849
+
850
+ ===== Models with validation constraints
851
+
852
+ [example]
853
+ ====
854
+ [source,ruby]
855
+ ----
856
+ class ValidationModel < Lutaml::Model::Serializable
857
+ attribute :name, Lutaml::Model::Type::String, values: %w[Alice Bob Charlie]
858
+ attribute :email, Lutaml::Model::Type::String, pattern: /.*?\S+@.+\.\S+/
859
+ attribute :age, Lutaml::Model::Type::Integer, collection: 1..3
860
+ end
861
+
862
+ # Generate RELAX NG schema
863
+ schema = Lutaml::Model::Schema::RelaxngSchema.generate(ValidationModel, pretty: true)
864
+ ----
865
+ ====
866
+
867
+ The generated schema will include validation constraints:
868
+
869
+ [source,xml]
870
+ ----
871
+ <?xml version="1.0" encoding="UTF-8"?>
872
+ <grammar xmlns="http://relaxng.org/ns/structure/1.0">
873
+ <start>
874
+ <ref name="ValidationModel"/>
875
+ </start>
876
+ <define name="ValidationModel">
877
+ <element name="ValidationModel">
878
+ <element name="name">
879
+ <choice>
880
+ <value>Alice</value>
881
+ <value>Bob</value>
882
+ <value>Charlie</value>
883
+ </choice>
884
+ </element>
885
+ <element name="email">
886
+ <data type="string">
887
+ <param name="pattern">.*?\S+@.+\.\S+</param>
888
+ </data>
889
+ </element>
890
+ <oneOrMore>
891
+ <element name="age">
892
+ <data type="integer"/>
893
+ </element>
894
+ </oneOrMore>
895
+ </element>
896
+ </define>
897
+ </grammar>
898
+ ----