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,383 @@
1
+ require "lutaml/model/schema"
2
+
3
+ RSpec.describe Lutaml::Model::Schema::JsonSchema do
4
+ describe ".generate_model_classes" do
5
+ context "with basic model schema" do
6
+ let(:schema) do
7
+ {
8
+ "$schema" => "https://json-schema.org/draft/2020-12/schema",
9
+ "$id" => "https://example.com/vase.schema.json",
10
+ "description" => "A vase schema",
11
+ "$ref" => "#/$defs/JsonSchemaSpec_Vase",
12
+ "$defs" => {
13
+ "JsonSchemaSpec_Vase" => {
14
+ "type" => "object",
15
+ "additionalProperties" => false,
16
+ "properties" => {
17
+ "height" => { "type" => ["number", "null"] },
18
+ "diameter" => { "type" => ["number", "null"] },
19
+ "glaze" => { "$ref" => "#/$defs/JsonSchemaSpec_Glaze" },
20
+ "materials" => {
21
+ "type" => "array",
22
+ "items" => { "type" => "string" },
23
+ },
24
+ },
25
+ },
26
+ "JsonSchemaSpec_Glaze" => {
27
+ "type" => "object",
28
+ "additionalProperties" => false,
29
+ "properties" => {
30
+ "color" => { "type" => ["string", "null"] },
31
+ "finish" => { "type" => ["string", "null"] },
32
+ },
33
+ },
34
+ },
35
+ }
36
+ end
37
+
38
+ let(:glaze_class) do
39
+ <<~RUBY
40
+ require "lutaml/model"
41
+
42
+ module JsonSchemaSpec
43
+ class Glaze < Lutaml::Model::Serializable
44
+ attribute :color, :string
45
+ attribute :finish, :string
46
+ end
47
+ end
48
+ RUBY
49
+ end
50
+
51
+ let(:vase_class) do
52
+ <<~RUBY
53
+ require "lutaml/model"
54
+
55
+ module JsonSchemaSpec
56
+ class Vase < Lutaml::Model::Serializable
57
+ attribute :height, :float
58
+ attribute :diameter, :float
59
+ attribute :glaze, JsonSchemaSpec::Glaze
60
+ attribute :materials, :string, collection: true
61
+ end
62
+ end
63
+ RUBY
64
+ end
65
+
66
+ let(:expected_classes) do
67
+ {
68
+ "JsonSchemaSpec_Glaze" => glaze_class,
69
+ "JsonSchemaSpec_Vase" => vase_class,
70
+ }
71
+ end
72
+
73
+ it "generates Ruby model classes from schema" do
74
+ generated = described_class.generate_model_classes(schema)
75
+
76
+ expect(generated["JsonSchemaSpec_Vase"].strip).to eq(vase_class.strip)
77
+ expect(generated["JsonSchemaSpec_Glaze"].strip).to eq(glaze_class.strip)
78
+ end
79
+ end
80
+
81
+ context "with choice validation schema" do
82
+ let(:schema) do
83
+ {
84
+ "$schema" => "https://json-schema.org/draft/2020-12/schema",
85
+ "$ref" => "#/$defs/JsonSchemaSpec_ChoiceModel",
86
+ "$defs" => {
87
+ "JsonSchemaSpec_ChoiceModel" => {
88
+ "type" => "object",
89
+ "additionalProperties" => false,
90
+ "properties" => {
91
+ "name" => { "type" => ["string", "null"] },
92
+ "email" => { "type" => ["string", "null"] },
93
+ "phone" => { "type" => ["string", "null"] },
94
+ },
95
+ "oneOf" => [
96
+ {
97
+ "type" => "object",
98
+ "properties" => {
99
+ "email" => { "type" => ["string", "null"] },
100
+ "phone" => { "type" => ["string", "null"] },
101
+ },
102
+ },
103
+ ],
104
+ },
105
+ },
106
+ }
107
+ end
108
+
109
+ let(:expected_classes) do
110
+ <<~RUBY
111
+ require "lutaml/model"
112
+
113
+ module JsonSchemaSpec
114
+ class ChoiceModel < Lutaml::Model::Serializable
115
+ attribute :name, :string
116
+
117
+ choice do
118
+ attribute :email, :string
119
+ attribute :phone, :string
120
+ end
121
+ end
122
+ end
123
+ RUBY
124
+ end
125
+
126
+ it "generates Ruby model classes with choice constraints from schema" do
127
+ generated = described_class.generate_model_classes(schema)
128
+ expect(generated["JsonSchemaSpec_ChoiceModel"].strip).to eq(expected_classes.strip)
129
+ end
130
+ end
131
+
132
+ context "with validation constraints schema" do
133
+ let(:schema) do
134
+ {
135
+ "$schema" => "https://json-schema.org/draft/2020-12/schema",
136
+ "$ref" => "#/$defs/JsonSchemaSpec_ValidationModel",
137
+ "$defs" => {
138
+ "JsonSchemaSpec_ValidationModel" => {
139
+ "type" => "object",
140
+ "additionalProperties" => false,
141
+ "properties" => {
142
+ "name" => {
143
+ "type" => ["string", "null"],
144
+ "enum" => ["Alice", "Bob", "Charlie"],
145
+ },
146
+ "email" => {
147
+ "type" => ["string", "null"],
148
+ "pattern" => ".*?\\S+@.+\\.\\S+",
149
+ },
150
+ "age" => {
151
+ "type" => "array",
152
+ "items" => { "type" => "integer" },
153
+ "minItems" => 1,
154
+ "maxItems" => 3,
155
+ },
156
+ "score" => {
157
+ "type" => ["number", "null"],
158
+ "default" => 0.0,
159
+ },
160
+ },
161
+ },
162
+ },
163
+ }
164
+ end
165
+
166
+ let(:expected_classes) do
167
+ <<~RUBY
168
+ require "lutaml/model"
169
+
170
+ module JsonSchemaSpec
171
+ class ValidationModel < Lutaml::Model::Serializable
172
+ attribute :name, :string, values: ["Alice", "Bob", "Charlie"]
173
+ attribute :email, :string, pattern: /.*?\\S+@.+\\.\\S+/
174
+ attribute :age, :integer, collection: 1..3
175
+ attribute :score, :float, default: 0.0
176
+ end
177
+ end
178
+ RUBY
179
+ end
180
+
181
+ it "generates Ruby model classes with validation constraints from schema" do
182
+ generated = described_class.generate_model_classes(schema)
183
+ expect(generated["JsonSchemaSpec_ValidationModel"].strip).to eq(expected_classes.strip)
184
+ end
185
+ end
186
+
187
+ context "with polymorphic types schema" do
188
+ let(:schema) do
189
+ {
190
+ "$schema" => "https://json-schema.org/draft/2020-12/schema",
191
+ "$ref" => "#/$defs/JsonSchemaSpec_PolymorphicModel",
192
+ "$defs" => {
193
+ "JsonSchemaSpec_PolymorphicModel" => {
194
+ "type" => "object",
195
+ "additionalProperties" => false,
196
+ "properties" => {
197
+ "shape" => {
198
+ "type" => ["object", "null"],
199
+ "oneOf" => [
200
+ { "$ref" => "#/$defs/JsonSchemaSpec_Circle" },
201
+ { "$ref" => "#/$defs/JsonSchemaSpec_Square" },
202
+ { "$ref" => "#/$defs/JsonSchemaSpec_Shape" },
203
+ ],
204
+ },
205
+ },
206
+ },
207
+ "JsonSchemaSpec_Circle" => {
208
+ "type" => "object",
209
+ "additionalProperties" => false,
210
+ "properties" => {
211
+ "area" => { "type" => ["number", "null"] },
212
+ "radius" => { "type" => ["number", "null"] },
213
+ },
214
+ },
215
+ "JsonSchemaSpec_Square" => {
216
+ "type" => "object",
217
+ "additionalProperties" => false,
218
+ "properties" => {
219
+ "area" => { "type" => ["number", "null"] },
220
+ "side" => { "type" => ["number", "null"] },
221
+ },
222
+ },
223
+ "JsonSchemaSpec_Shape" => {
224
+ "type" => "object",
225
+ "additionalProperties" => false,
226
+ "properties" => {
227
+ "area" => { "type" => ["number", "null"] },
228
+ },
229
+ },
230
+ },
231
+ }
232
+ end
233
+
234
+ let(:expected_classes) do
235
+ {
236
+ "JsonSchemaSpec_Shape" => <<~RUBY.strip,
237
+ require "lutaml/model"
238
+
239
+ module JsonSchemaSpec
240
+ class Shape < Lutaml::Model::Serializable
241
+ attribute :area, :float
242
+ end
243
+ end
244
+ RUBY
245
+
246
+ "JsonSchemaSpec_Circle" => <<~RUBY.strip,
247
+ require "lutaml/model"
248
+
249
+ module JsonSchemaSpec
250
+ class Circle < JsonSchemaSpec::Shape
251
+ attribute :radius, :float
252
+ end
253
+ end
254
+ RUBY
255
+
256
+ "JsonSchemaSpec_Square" => <<~RUBY.strip,
257
+ require "lutaml/model"
258
+
259
+ module JsonSchemaSpec
260
+ class Square < JsonSchemaSpec::Shape
261
+ attribute :side, :float
262
+ end
263
+ end
264
+ RUBY
265
+
266
+ "JsonSchemaSpec_PolymorphicModel" => <<~RUBY.strip,
267
+ require "lutaml/model"
268
+
269
+ module JsonSchemaSpec
270
+ class PolymorphicModel < Lutaml::Model::Serializable
271
+ attribute :shape, JsonSchemaSpec::Shape, polymorphic: [JsonSchemaSpec::Circle, JsonSchemaSpec::Square]
272
+ end
273
+ end
274
+ RUBY
275
+ }
276
+ end
277
+
278
+ it "generates Ruby model classes with polymorphic types from schema" do
279
+ generated = described_class.generate_model_classes(schema)
280
+ expect(generated.transform_values(&:strip)).to eq(expected_classes)
281
+ end
282
+ end
283
+
284
+ context "with deeply nested classes schema" do
285
+ let(:schema) do
286
+ {
287
+ "$schema" => "https://json-schema.org/draft/2020-12/schema",
288
+ "$ref" => "#/$defs/JsonSchemaSpec_Container",
289
+ "$defs" => {
290
+ "JsonSchemaSpec_Container" => {
291
+ "type" => "object",
292
+ "additionalProperties" => false,
293
+ "properties" => {
294
+ "id" => { "type" => ["string", "null"] },
295
+ "box" => { "$ref" => "#/$defs/JsonSchemaSpec_Box" },
296
+ },
297
+ },
298
+ "JsonSchemaSpec_Box" => {
299
+ "type" => "object",
300
+ "additionalProperties" => false,
301
+ "properties" => {
302
+ "size" => { "type" => ["string", "null"] },
303
+ "items" => {
304
+ "type" => "array",
305
+ "items" => { "$ref" => "#/$defs/JsonSchemaSpec_Item" },
306
+ },
307
+ },
308
+ },
309
+ "JsonSchemaSpec_Item" => {
310
+ "type" => "object",
311
+ "additionalProperties" => false,
312
+ "properties" => {
313
+ "name" => { "type" => ["string", "null"] },
314
+ "detail" => { "$ref" => "#/$defs/JsonSchemaSpec_Detail" },
315
+ },
316
+ },
317
+ "JsonSchemaSpec_Detail" => {
318
+ "type" => "object",
319
+ "additionalProperties" => false,
320
+ "properties" => {
321
+ "weight" => { "type" => ["number", "null"] },
322
+ "color" => { "type" => ["string", "null"] },
323
+ },
324
+ },
325
+ },
326
+ }
327
+ end
328
+
329
+ let(:expected_classes) do
330
+ {
331
+ "JsonSchemaSpec_Detail" => <<~RUBY.strip,
332
+ require "lutaml/model"
333
+
334
+ module JsonSchemaSpec
335
+ class Detail < Lutaml::Model::Serializable
336
+ attribute :weight, :float
337
+ attribute :color, :string
338
+ end
339
+ end
340
+ RUBY
341
+
342
+ "JsonSchemaSpec_Item" => <<~RUBY.strip,
343
+ require "lutaml/model"
344
+
345
+ module JsonSchemaSpec
346
+ class Item < Lutaml::Model::Serializable
347
+ attribute :name, :string
348
+ attribute :detail, JsonSchemaSpec::Detail
349
+ end
350
+ end
351
+ RUBY
352
+
353
+ "JsonSchemaSpec_Box" => <<~RUBY.strip,
354
+ require "lutaml/model"
355
+
356
+ module JsonSchemaSpec
357
+ class Box < Lutaml::Model::Serializable
358
+ attribute :size, :string
359
+ attribute :items, JsonSchemaSpec::Item, collection: true
360
+ end
361
+ end
362
+ RUBY
363
+
364
+ "JsonSchemaSpec_Container" => <<~RUBY.strip,
365
+ require "lutaml/model"
366
+
367
+ module JsonSchemaSpec
368
+ class Container < Lutaml::Model::Serializable
369
+ attribute :id, :string
370
+ attribute :box, JsonSchemaSpec::Box
371
+ end
372
+ end
373
+ RUBY
374
+ }
375
+ end
376
+
377
+ it "generates Ruby model classes for deeply nested classes from schema" do
378
+ generated = described_class.generate_model_classes(schema)
379
+ expect(generated.transform_values(&:strip)).to eq(expected_classes)
380
+ end
381
+ end
382
+ end
383
+ end
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lutaml::Model::Schema::XmlCompiler::AttributeGroup do
4
+ let(:attribute_group) { described_class.new(name: "foo", ref: nil) }
5
+ let(:dummy_class) do
6
+ Class.new do
7
+ def to_attributes(indent); end
8
+ def to_xml_mapping(indent); end
9
+ def required_files; end
10
+ end
11
+ end
12
+
13
+ describe "#initialize" do
14
+ it "sets name, ref, and empty instances" do
15
+ ag = described_class.new(name: "bar", ref: "baz")
16
+ expect(ag.name).to eq("bar")
17
+ expect(ag.ref).to eq("baz")
18
+ expect(ag.instances).to eq([])
19
+ end
20
+ end
21
+
22
+ describe "#<<" do
23
+ it "adds instances to the list" do
24
+ instance = instance_double(dummy_class, to_attributes: "attr", to_xml_mapping: "xml", required_files: "files")
25
+ expect { attribute_group << instance }.to change { attribute_group.instances.size }.by(1)
26
+ expect(attribute_group.instances).to include(instance)
27
+ end
28
+
29
+ it "ignores nil instances" do
30
+ expect { attribute_group << nil }.not_to(change { attribute_group.instances.size })
31
+ end
32
+ end
33
+
34
+ describe "#to_attributes" do
35
+ it "returns attributes from resolved_instances" do
36
+ instance = instance_double(dummy_class, to_attributes: "attr")
37
+ attribute_group.instances = [instance]
38
+ expect(attribute_group.to_attributes(" ").join).to include("attr")
39
+ end
40
+ end
41
+
42
+ describe "#to_xml_mapping" do
43
+ it "returns xml mappings from resolved_instances" do
44
+ instance = instance_double(dummy_class, to_xml_mapping: "xml")
45
+ attribute_group.instances = [instance]
46
+ expect(attribute_group.to_xml_mapping(" ").join).to include("xml")
47
+ end
48
+ end
49
+
50
+ describe "#required_files" do
51
+ it "collects required_files from resolved_instances" do
52
+ instance = instance_double(dummy_class, required_files: "files")
53
+ attribute_group.instances = [instance]
54
+ expect(attribute_group.required_files.join).to include("files")
55
+ end
56
+ end
57
+
58
+ describe "private methods" do
59
+ it "resolved_instances returns @instances if ref is not present" do
60
+ attribute_group.instances = [1, 2]
61
+ attribute_group.ref = nil
62
+ expect(attribute_group.send(:resolved_instances)).to eq([1, 2])
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,63 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lutaml::Model::Schema::XmlCompiler::Attribute do
4
+ let(:attribute) { described_class.new(name: "foo", ref: "bar:Ref") }
5
+
6
+ describe "#initialize" do
7
+ it "sets name and ref" do
8
+ a = described_class.new(name: "foo", ref: "bar")
9
+ expect(a.name).to eq("foo")
10
+ expect(a.ref).to eq("bar")
11
+ end
12
+ end
13
+
14
+ describe "#to_attributes" do
15
+ it "returns attribute line if not skippable" do
16
+ allow(attribute).to receive_messages(skippable?: false, resolved_name: "foo", resolved_type: "string")
17
+ expect(attribute.to_attributes(" ")).to include("attribute :foo, :string")
18
+ end
19
+
20
+ it "returns nil if skippable" do
21
+ allow(attribute).to receive(:skippable?).and_return(true)
22
+ expect(attribute.to_attributes(" ")).to be_nil
23
+ end
24
+ end
25
+
26
+ describe "#to_xml_mapping" do
27
+ it "returns map_attribute line if not skippable" do
28
+ allow(attribute).to receive_messages(skippable?: false, resolved_name: "foo")
29
+ expect(attribute.to_xml_mapping(" ")).to include("map_attribute :foo, to: :foo")
30
+ end
31
+
32
+ it "returns nil if skippable" do
33
+ allow(attribute).to receive(:skippable?).and_return(true)
34
+ expect(attribute.to_xml_mapping(" ")).to be_nil
35
+ end
36
+ end
37
+
38
+ describe "#required_files" do
39
+ it "returns require 'bigdecimal' for decimal type" do
40
+ allow(attribute).to receive_messages(skippable?: false, resolved_type: "decimal")
41
+ expect(attribute.required_files).to eq("require \"bigdecimal\"")
42
+ end
43
+
44
+ it "returns require_relative for non-skippable type" do
45
+ allow(attribute).to receive_messages(skippable?: false, resolved_type: "foo")
46
+ allow(Lutaml::Model::Schema::XmlCompiler::SimpleType).to receive(:skippable?).and_return(false)
47
+ expect(attribute.required_files).to eq("require_relative \"foo\"")
48
+ end
49
+
50
+ it "returns nil for skippable type" do
51
+ allow(attribute).to receive(:skippable?).and_return(true)
52
+ expect(attribute.required_files).to be_nil
53
+ end
54
+ end
55
+
56
+ describe "private methods" do
57
+ it "last_of_split returns last part after colon" do
58
+ expect(attribute.send(:last_of_split, "foo:Bar")).to eq("Bar")
59
+ expect(attribute.send(:last_of_split, "Bar")).to eq("Bar")
60
+ expect(attribute.send(:last_of_split, nil)).to be_nil
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,71 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lutaml::Model::Schema::XmlCompiler::Choice do
4
+ let(:choice) { described_class.new }
5
+ let(:dummy_class) do
6
+ Class.new do
7
+ def to_attributes(indent); end
8
+ def to_xml_mapping(indent); end
9
+ def required_files; end
10
+ end
11
+ end
12
+
13
+ describe "#initialize" do
14
+ it "starts with empty instances" do
15
+ expect(choice.instances).to eq([])
16
+ end
17
+ end
18
+
19
+ describe "#<<" do
20
+ it "adds instances to the list" do
21
+ instance = instance_double(dummy_class)
22
+ expect { choice << instance }.to change { choice.instances.size }.by(1)
23
+ expect(choice.instances).to include(instance)
24
+ end
25
+
26
+ it "ignores nil instances" do
27
+ expect { choice << nil }.not_to(change { choice.instances.size })
28
+ end
29
+ end
30
+
31
+ describe "#to_attributes" do
32
+ it "renders the choice block with min/max options" do
33
+ instance = instance_double(dummy_class, to_attributes: " attribute :foo, :string\n")
34
+ choice << instance
35
+ choice.min_occurs = 1
36
+ choice.max_occurs = 2
37
+ expect(choice.to_attributes(" ")).to include("choice(")
38
+ expect(choice.to_attributes(" ")).to include("attribute :foo, :string")
39
+ end
40
+ end
41
+
42
+ describe "#to_xml_mapping" do
43
+ it "returns joined xml mappings from instances" do
44
+ instance = instance_double(dummy_class, to_xml_mapping: " map_element :foo, to: :foo\n")
45
+ choice << instance
46
+ expect(choice.to_xml_mapping(" ")).to include("map_element :foo, to: :foo")
47
+ end
48
+ end
49
+
50
+ describe "#required_files" do
51
+ it "collects required_files from all instances" do
52
+ instance = instance_double(dummy_class, required_files: "require 'foo'")
53
+ choice << instance
54
+ expect(choice.required_files).to include("require 'foo'")
55
+ end
56
+ end
57
+
58
+ describe "private methods" do
59
+ it "min_option returns correct min" do
60
+ choice.min_occurs = 3
61
+ expect(choice.send(:min_option)).to include("min: 3")
62
+ end
63
+
64
+ it "max_option returns correct max" do
65
+ choice.max_occurs = "unbounded"
66
+ expect(choice.send(:max_option)).to include("Float::INFINITY")
67
+ choice.max_occurs = 5
68
+ expect(choice.send(:max_option)).to include("max: 5")
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lutaml::Model::Schema::XmlCompiler::ComplexContentRestriction do
4
+ let(:restriction) { described_class.new(base: "Base", instances: []) }
5
+ let(:dummy_class) do
6
+ Class.new do
7
+ def to_attributes(indent); end
8
+ def to_xml_mapping(indent); end
9
+ def required_files; end
10
+ end
11
+ end
12
+
13
+ describe "#initialize" do
14
+ it "sets base and instances" do
15
+ r = described_class.new(base: "foo", instances: [1, 2])
16
+ expect(r.base).to eq("foo")
17
+ expect(r.instances).to eq([1, 2])
18
+ end
19
+ end
20
+
21
+ describe "#<<" do
22
+ it "adds instances to the list" do
23
+ expect { restriction << 1 }.to change { restriction.instances.size }.by(1)
24
+ expect(restriction.instances).to include(1)
25
+ end
26
+
27
+ it "ignores nil instances" do
28
+ expect { restriction << nil }.not_to(change { restriction.instances.size })
29
+ end
30
+ end
31
+
32
+ describe "#to_attributes" do
33
+ it "returns attributes from all instances" do
34
+ instance = instance_double(dummy_class, to_attributes: "attr")
35
+ restriction.instances = [instance]
36
+ expect(restriction.to_attributes(" ")).to include("attr")
37
+ end
38
+ end
39
+
40
+ describe "#to_xml_mapping" do
41
+ it "returns xml mappings from all instances" do
42
+ instance = instance_double(dummy_class, to_xml_mapping: "xml")
43
+ restriction.instances = [instance]
44
+ expect(restriction.to_xml_mapping(" ")).to include("xml")
45
+ end
46
+ end
47
+
48
+ describe "#required_files" do
49
+ it "collects required_files from all instances" do
50
+ instance = instance_double(dummy_class, required_files: "files")
51
+ restriction.instances = [instance]
52
+ expect(restriction.required_files).to include("files")
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lutaml::Model::Schema::XmlCompiler::ComplexContent do
4
+ let(:dummy_class) do
5
+ Class.new do
6
+ def to_attributes(indent); end
7
+ def to_xml_mapping(indent); end
8
+ def required_files; end
9
+ end
10
+ end
11
+ let(:restriction) { instance_double(dummy_class, to_attributes: "attr", to_xml_mapping: "xml", required_files: "files") }
12
+ let(:complex_content) { described_class.new(restriction) }
13
+
14
+ describe "#initialize" do
15
+ it "sets restriction" do
16
+ expect(complex_content.restriction).to eq(restriction)
17
+ end
18
+ end
19
+
20
+ describe "#to_attributes" do
21
+ it "delegates to restriction" do
22
+ expect(complex_content.to_attributes(" ")).to eq("attr")
23
+ end
24
+ end
25
+
26
+ describe "#to_xml_mapping" do
27
+ it "delegates to restriction" do
28
+ expect(complex_content.to_xml_mapping(" ")).to eq("xml")
29
+ end
30
+ end
31
+
32
+ describe "#required_files" do
33
+ it "delegates to restriction" do
34
+ expect(complex_content.required_files).to eq("files")
35
+ end
36
+ end
37
+ end