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.
- checksums.yaml +4 -4
- data/.envrc +1 -0
- data/.github/workflows/release.yml +3 -0
- data/.gitignore +6 -1
- data/.irbrc +1 -0
- data/.pryrc +1 -0
- data/.rubocop_todo.yml +25 -52
- data/README.adoc +2177 -192
- data/docs/custom_registers.adoc +228 -0
- data/docs/schema_generation.adoc +898 -0
- data/docs/schema_import.adoc +364 -0
- data/flake.lock +114 -0
- data/flake.nix +103 -0
- data/lib/lutaml/model/attribute.rb +230 -94
- data/lib/lutaml/model/choice.rb +30 -0
- data/lib/lutaml/model/collection.rb +194 -0
- data/lib/lutaml/model/comparable_model.rb +3 -3
- data/lib/lutaml/model/config.rb +26 -3
- data/lib/lutaml/model/constants.rb +2 -0
- data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
- data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
- data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
- data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
- data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
- data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
- data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
- data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
- data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
- data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
- data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
- data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
- data/lib/lutaml/model/error.rb +10 -0
- data/lib/lutaml/model/errors.rb +36 -0
- data/lib/lutaml/model/format_registry.rb +5 -2
- data/lib/lutaml/model/global_register.rb +41 -0
- data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
- data/lib/lutaml/model/jsonl/document.rb +14 -0
- data/lib/lutaml/model/jsonl/mapping.rb +19 -0
- data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
- data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
- data/lib/lutaml/model/jsonl/transform.rb +19 -0
- data/lib/lutaml/model/jsonl.rb +21 -0
- data/lib/lutaml/model/key_value_document.rb +3 -2
- data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
- data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
- data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
- data/lib/lutaml/model/register.rb +105 -0
- data/lib/lutaml/model/registrable.rb +6 -0
- data/lib/lutaml/model/schema/base_schema.rb +64 -0
- data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
- data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
- data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
- data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
- data/lib/lutaml/model/schema/generator/definition.rb +53 -0
- data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
- data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
- data/lib/lutaml/model/schema/generator/property.rb +110 -0
- data/lib/lutaml/model/schema/generator/ref.rb +24 -0
- data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
- data/lib/lutaml/model/schema/json_schema.rb +42 -49
- data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
- data/lib/lutaml/model/schema/renderer.rb +36 -0
- data/lib/lutaml/model/schema/shared_methods.rb +24 -0
- data/lib/lutaml/model/schema/templates/model.erb +9 -0
- data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
- data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
- data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
- data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
- data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
- data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
- data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
- data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
- data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
- data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
- data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
- data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
- data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
- data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
- data/lib/lutaml/model/schema.rb +1 -0
- data/lib/lutaml/model/sequence.rb +60 -30
- data/lib/lutaml/model/serialize.rb +175 -53
- data/lib/lutaml/model/services/base.rb +11 -0
- data/lib/lutaml/model/services/logger.rb +2 -2
- data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
- data/lib/lutaml/model/services/type/validator/number.rb +25 -0
- data/lib/lutaml/model/services/type/validator/string.rb +52 -0
- data/lib/lutaml/model/services/type/validator.rb +43 -0
- data/lib/lutaml/model/services/validator.rb +145 -0
- data/lib/lutaml/model/services.rb +3 -0
- data/lib/lutaml/model/transform/key_value_transform.rb +60 -50
- data/lib/lutaml/model/transform/xml_transform.rb +46 -57
- data/lib/lutaml/model/transform.rb +22 -8
- data/lib/lutaml/model/type/boolean.rb +1 -1
- data/lib/lutaml/model/type/date.rb +1 -1
- data/lib/lutaml/model/type/date_time.rb +1 -1
- data/lib/lutaml/model/type/decimal.rb +11 -9
- data/lib/lutaml/model/type/float.rb +2 -1
- data/lib/lutaml/model/type/integer.rb +24 -21
- data/lib/lutaml/model/type/string.rb +4 -2
- data/lib/lutaml/model/type/time.rb +1 -1
- data/lib/lutaml/model/type/time_without_date.rb +1 -1
- data/lib/lutaml/model/type/value.rb +5 -1
- data/lib/lutaml/model/type.rb +5 -2
- data/lib/lutaml/model/utils.rb +30 -8
- data/lib/lutaml/model/validation.rb +6 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml/document.rb +37 -19
- data/lib/lutaml/model/xml/mapping.rb +74 -13
- data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
- data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
- data/lib/lutaml/model/xml/oga/element.rb +4 -1
- data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
- data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
- data/lib/lutaml/model/xml/xml_element.rb +3 -28
- data/lib/lutaml/model/xml_adapter/element.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
- data/lib/lutaml/model/yamls/document.rb +14 -0
- data/lib/lutaml/model/yamls/mapping.rb +19 -0
- data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
- data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
- data/lib/lutaml/model/yamls/transform.rb +19 -0
- data/lib/lutaml/model/yamls.rb +21 -0
- data/lib/lutaml/model.rb +7 -31
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
- data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
- data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
- data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
- data/spec/fixtures/xml/product_catalog.xsd +151 -0
- data/spec/fixtures/xml/specifications_schema.xsd +38 -0
- data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
- data/spec/lutaml/model/attribute_spec.rb +41 -44
- data/spec/lutaml/model/choice_spec.rb +44 -0
- data/spec/lutaml/model/custom_collection_spec.rb +830 -0
- data/spec/lutaml/model/custom_model_spec.rb +15 -3
- data/spec/lutaml/model/defaults_spec.rb +5 -1
- data/spec/lutaml/model/global_register_spec.rb +108 -0
- data/spec/lutaml/model/group_spec.rb +9 -3
- data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
- data/spec/lutaml/model/jsonl_spec.rb +229 -0
- data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
- data/spec/lutaml/model/register/key_value_spec.rb +275 -0
- data/spec/lutaml/model/register/xml_spec.rb +187 -0
- data/spec/lutaml/model/register_spec.rb +147 -0
- data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
- data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
- data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
- data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
- data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
- data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
- data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
- data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
- data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
- data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
- data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
- data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
- data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
- data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
- data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
- data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
- data/spec/lutaml/model/sequence_spec.rb +36 -0
- data/spec/lutaml/model/serializable_spec.rb +31 -0
- data/spec/lutaml/model/type_spec.rb +8 -4
- data/spec/lutaml/model/utils_spec.rb +3 -3
- data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
- data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
- data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
- data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
- data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
- data/spec/lutaml/model/yamls_spec.rb +294 -0
- data/spec/spec_helper.rb +1 -0
- metadata +105 -9
- data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
- /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
- /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
- /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
- /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
- /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
|
+
}
|