lutaml-model 0.8.31 → 0.8.32
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/README.adoc +25 -23
- data/docs/_pages/breaking-changes.adoc +58 -0
- data/docs/_pages/collections.adoc +25 -24
- data/docs/_pages/validation.adoc +48 -1
- data/lib/compat/opal/lutaml_model_boot.rb +1 -0
- data/lib/lutaml/key_value/transform.rb +7 -1
- data/lib/lutaml/model/attribute.rb +67 -24
- data/lib/lutaml/model/collection.rb +5 -2
- data/lib/lutaml/model/collection_handler.rb +45 -2
- data/lib/lutaml/model/serialize/enum_handling.rb +5 -1
- data/lib/lutaml/model/validation.rb +49 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/format.rb +2 -0
- data/lib/lutaml/xml/model_transform.rb +33 -9
- data/lib/lutaml/xml/nested_collection_attribute.rb +95 -0
- data/lib/lutaml/xml/schema/xsd_schema.rb +396 -128
- data/lib/lutaml/xml/serialization/instance_methods.rb +2 -2
- data/lib/lutaml/xml/transformation/element_builder.rb +55 -8
- data/spec/lutaml/model/attribute_collection_spec.rb +156 -0
- data/spec/lutaml/model/attribute_spec.rb +1 -1
- data/spec/lutaml/model/cardinality_strict_spec.rb +614 -0
- data/spec/lutaml/model/cdata_spec.rb +1 -1
- data/spec/lutaml/model/collection_register_spec.rb +119 -0
- data/spec/lutaml/model/collection_single_value_coercion_spec.rb +222 -0
- data/spec/lutaml/model/root_mappings_spec.rb +10 -7
- data/spec/lutaml/model/schema/xsd_schema_spec.rb +785 -25
- data/spec/lutaml/model/serializable_validation_spec.rb +293 -0
- data/spec/lutaml/model/value_policy_spec.rb +6 -3
- data/spec/lutaml/model/xsd_form_default_patterns_spec.rb +12 -12
- data/spec/lutaml/model/xsd_patterns_spec.rb +29 -29
- data/spec/lutaml/model/xsd_type_validation_spec.rb +1 -1
- data/spec/lutaml/turtle/transform_spec.rb +2 -2
- metadata +5 -1
|
@@ -88,4 +88,297 @@ RSpec.describe Lutaml::Model::Serializable do
|
|
|
88
88
|
expect { valid_instance.validate! }.not_to raise_error
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
|
+
|
|
92
|
+
# Regression: valid_pattern! must short-circuit non-string values instead of
|
|
93
|
+
# feeding nil / UninitializedClass to Regexp#match? (which raises TypeError).
|
|
94
|
+
describe "pattern with absent or nil values" do
|
|
95
|
+
it "does not raise when an optional patterned string is omitted" do
|
|
96
|
+
instance = TestSerializable.new(name: "Alice", age: [30])
|
|
97
|
+
|
|
98
|
+
errors = nil
|
|
99
|
+
expect { errors = instance.validate }.not_to raise_error
|
|
100
|
+
expect(errors).to be_none do |e|
|
|
101
|
+
e.is_a?(Lutaml::Model::PatternNotMatchedError)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "skips nil elements in a patterned collection" do
|
|
106
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
107
|
+
attribute :codes, :string, collection: true, pattern: /\A[A-Z]+\z/
|
|
108
|
+
key_value { map "codes", to: :codes }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
expect(klass.new(codes: ["ABC", nil, "DEF"]).validate).to be_empty
|
|
112
|
+
expect(klass.new(codes: ["ABC", "bad"]).validate).not_to be_empty
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# valid_collection! must return truthy on success so validate_value!'s &&
|
|
116
|
+
# chain still reaches pattern validation for a bounded-range collection.
|
|
117
|
+
it "still runs pattern validation after a valid bounded-range collection" do
|
|
118
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
119
|
+
attribute :codes, :string, collection: 1..2, pattern: /\A[A-Z]+\z/
|
|
120
|
+
key_value { map "codes", to: :codes }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
expect(klass.new(codes: ["ABC"]).validate).to be_empty
|
|
124
|
+
expect(klass.new(codes: ["bad"]).validate)
|
|
125
|
+
.to include(an_instance_of(Lutaml::Model::PatternNotMatchedError))
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Regression: enum (values:) on a collection must validate each element,
|
|
130
|
+
# not compare the whole coerced collection against the allowed set.
|
|
131
|
+
describe "enum on collection attributes" do
|
|
132
|
+
let(:klass) do
|
|
133
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
134
|
+
attribute :tags, :string, collection: true, values: %w[a b c]
|
|
135
|
+
key_value { map "tags", to: :tags }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "accepts a single valid value coerced into the collection" do
|
|
140
|
+
expect(klass.new(tags: ["a"]).validate).to be_empty
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "accepts multiple valid values" do
|
|
144
|
+
expect(klass.new(tags: %w[a b]).validate).to be_empty
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "rejects a collection containing a disallowed element" do
|
|
148
|
+
expect(klass.new(tags: %w[a z]).validate)
|
|
149
|
+
.to include(an_instance_of(Lutaml::Model::InvalidValueError))
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Regression: a collection of nested models must recurse into every element
|
|
154
|
+
# so a grandchild's own validation errors surface on the parent.
|
|
155
|
+
describe "nested model collection validation" do
|
|
156
|
+
let(:child_class) do
|
|
157
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
158
|
+
attribute :status, :string, values: %w[ok fine]
|
|
159
|
+
key_value { map "status", to: :status }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
let(:parent_class) do
|
|
164
|
+
child = child_class
|
|
165
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
166
|
+
attribute :items, child, collection: true
|
|
167
|
+
key_value { map "items", to: :items }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "surfaces an invalid grandchild in a model collection" do
|
|
172
|
+
parent = parent_class.new(
|
|
173
|
+
items: [child_class.new(status: "ok"), child_class.new(status: "BAD")],
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
expect(parent.validate)
|
|
177
|
+
.to include(an_instance_of(Lutaml::Model::InvalidValueError))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it "returns no errors when every model element is valid" do
|
|
181
|
+
parent = parent_class.new(
|
|
182
|
+
items: [child_class.new(status: "ok"), child_class.new(status: "fine")],
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
expect(parent.validate).to be_empty
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Regression: a Collection is itself a model, so a nested one must be
|
|
190
|
+
# validated as one — iterating its elements alone skips collection-level
|
|
191
|
+
# rules such as validates_min_count.
|
|
192
|
+
describe "nested collection-level validation" do
|
|
193
|
+
let(:item_class) do
|
|
194
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
195
|
+
attribute :name, :string
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
let(:collection_class) do
|
|
200
|
+
item = item_class
|
|
201
|
+
Class.new(Lutaml::Model::Collection) do
|
|
202
|
+
instances :items, item
|
|
203
|
+
validates_min_count 2
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
let(:holder_class) do
|
|
208
|
+
collection = collection_class
|
|
209
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
210
|
+
attribute :items, collection
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "surfaces a nested collection's collection-level rule violation" do
|
|
215
|
+
holder = holder_class.new(
|
|
216
|
+
items: collection_class.new([item_class.new(name: "a")]),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
expect(holder.validate)
|
|
220
|
+
.to include(an_instance_of(Lutaml::Model::ValidationFailedError))
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "returns no errors when the nested collection satisfies its rules" do
|
|
224
|
+
holder = holder_class.new(
|
|
225
|
+
items: collection_class.new(
|
|
226
|
+
[item_class.new(name: "a"), item_class.new(name: "b")],
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
expect(holder.validate).to be_empty
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Regression: #validate is a public override point, so recursing into a child
|
|
235
|
+
# must not hand it arguments its own signature does not declare.
|
|
236
|
+
describe "nested model overriding #validate with zero arity" do
|
|
237
|
+
let(:child_class) do
|
|
238
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
239
|
+
attribute :status, :string
|
|
240
|
+
|
|
241
|
+
def validate
|
|
242
|
+
super << "child checked #{status}"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
let(:parent_class) do
|
|
248
|
+
child = child_class
|
|
249
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
250
|
+
attribute :item, child
|
|
251
|
+
attribute :items, child, collection: true
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "surfaces errors from a zero-arity override on a single child" do
|
|
256
|
+
parent = parent_class.new(item: child_class.new(status: "one"))
|
|
257
|
+
|
|
258
|
+
expect(parent.validate).to include("child checked one")
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it "surfaces errors from a zero-arity override on every element" do
|
|
262
|
+
parent = parent_class.new(
|
|
263
|
+
items: [child_class.new(status: "a"), child_class.new(status: "b")],
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
expect(parent.validate)
|
|
267
|
+
.to include("child checked a", "child checked b")
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
describe "models that reference themselves" do
|
|
272
|
+
let(:node_class) do
|
|
273
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
274
|
+
def self.name = "ValidationCycleSpec::Node"
|
|
275
|
+
attribute :name, :string, values: %w[ok]
|
|
276
|
+
attribute :parent, self
|
|
277
|
+
attribute :children, self, collection: true
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it "validates a self-reference held in a collection" do
|
|
282
|
+
node = node_class.new(name: "ok")
|
|
283
|
+
node.children = [node]
|
|
284
|
+
|
|
285
|
+
expect { node.validate }.not_to raise_error
|
|
286
|
+
expect(node.validate).to be_empty
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "validates a self-reference held in a single attribute" do
|
|
290
|
+
node = node_class.new(name: "ok")
|
|
291
|
+
node.parent = node
|
|
292
|
+
|
|
293
|
+
expect { node.validate }.not_to raise_error
|
|
294
|
+
expect(node.validate).to be_empty
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "reports a cycled model's own errors exactly once" do
|
|
298
|
+
node = node_class.new(name: "bad")
|
|
299
|
+
node.children = [node]
|
|
300
|
+
|
|
301
|
+
expect(node.validate).to contain_exactly(
|
|
302
|
+
an_instance_of(Lutaml::Model::InvalidValueError),
|
|
303
|
+
)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "still validates the same child twice when it is not a cycle" do
|
|
307
|
+
child = node_class.new(name: "bad")
|
|
308
|
+
parent = node_class.new(name: "ok", children: [child, child])
|
|
309
|
+
|
|
310
|
+
expect(parent.validate.size).to eq(2)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "raises from validate! on a cycled model with a real error" do
|
|
314
|
+
node = node_class.new(name: "bad")
|
|
315
|
+
node.children = [node]
|
|
316
|
+
|
|
317
|
+
expect { node.validate! }.to raise_error(Lutaml::Model::ValidationError)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# The guard tracks visited models in thread-local state, so it has to clean
|
|
321
|
+
# up on the way out even when validate! raises.
|
|
322
|
+
it "leaves no thread-local state behind when validate! raises" do
|
|
323
|
+
node = node_class.new(name: "bad")
|
|
324
|
+
node.children = [node]
|
|
325
|
+
|
|
326
|
+
begin
|
|
327
|
+
node.validate!
|
|
328
|
+
rescue Lutaml::Model::ValidationError
|
|
329
|
+
nil
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
expect(Thread.current[Lutaml::Model::Validation::VALIDATING_KEY])
|
|
333
|
+
.to be_nil
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# A downstream #validate override runs above the guard, so the cycle has
|
|
337
|
+
# to be cut before the override is entered, not inside it.
|
|
338
|
+
it "runs a downstream validate override once on a cycle" do
|
|
339
|
+
klass = Class.new(Lutaml::Model::Serializable) do
|
|
340
|
+
def self.name = "ValidationCycleSpec::Overrider"
|
|
341
|
+
attribute :children, self, collection: true
|
|
342
|
+
|
|
343
|
+
def validate
|
|
344
|
+
super << "override ran"
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
node = klass.new
|
|
348
|
+
node.children = [node]
|
|
349
|
+
|
|
350
|
+
expect(node.validate.count { |e| e == "override ran" }).to eq(1)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it "keeps the guard isolated between threads" do
|
|
354
|
+
node = node_class.new(name: "ok")
|
|
355
|
+
node.children = [node]
|
|
356
|
+
|
|
357
|
+
results = Array.new(4) { Thread.new { node.validate } }.map(&:value)
|
|
358
|
+
|
|
359
|
+
expect(results).to all(be_empty)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
describe "collections that reference themselves" do
|
|
364
|
+
let(:collection_class) do
|
|
365
|
+
Class.new(Lutaml::Model::Collection) do
|
|
366
|
+
def self.name = "ValidationCycleSpec::Items"
|
|
367
|
+
instances :items, TestSerializable
|
|
368
|
+
|
|
369
|
+
validate_collection do |collection, errors, _ctx|
|
|
370
|
+
errors.add(:collection, "ran #{collection.count}")
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# The rules used to run once inside the guarded pipeline and again from the
|
|
376
|
+
# outer call, because #validate was overridden above the guard.
|
|
377
|
+
it "runs its collection-level rules exactly once" do
|
|
378
|
+
collection = collection_class.new([])
|
|
379
|
+
|
|
380
|
+
ran = collection.validate.select { |e| e.to_s.include?("ran ") }
|
|
381
|
+
expect(ran.size).to eq(1)
|
|
382
|
+
end
|
|
383
|
+
end
|
|
91
384
|
end
|
|
@@ -67,9 +67,12 @@ RSpec.describe "ValuePolicy shaping of non-collection attributes" do
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
describe "built-in scalar types" do
|
|
70
|
-
it "keep the collection: true guidance
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
it "keep the collection: true guidance as a validate-time report" do
|
|
71
|
+
doc = nil
|
|
72
|
+
expect { doc = ValuePolicySpec::StringsDoc.from_yaml("v: [a]") }
|
|
73
|
+
.not_to raise_error
|
|
74
|
+
expect(doc.validate)
|
|
75
|
+
.to include(an_instance_of(Lutaml::Model::CollectionTrueMissingError))
|
|
73
76
|
end
|
|
74
77
|
end
|
|
75
78
|
|
|
@@ -66,7 +66,7 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
66
66
|
xsd = Lutaml::Model::Schema.to_xsd(model_class)
|
|
67
67
|
|
|
68
68
|
# Per XSD spec, form attribute should NOT be emitted when it matches the default
|
|
69
|
-
expect(xsd).to include('<element name="premium" type="xs:string"/>')
|
|
69
|
+
expect(xsd).to include('<xs:element name="premium" type="xs:string"/>')
|
|
70
70
|
expect(xsd).not_to include('form="unqualified"')
|
|
71
71
|
end
|
|
72
72
|
|
|
@@ -125,7 +125,7 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
125
125
|
|
|
126
126
|
# Per XSD spec, form="unqualified" MUST be emitted when it differs from elementFormDefault
|
|
127
127
|
expect(xsd).to include('form="unqualified"')
|
|
128
|
-
expect(xsd).to include('<element name="element" type="xs:string" form="unqualified"/>')
|
|
128
|
+
expect(xsd).to include('<xs:element name="element" type="xs:string" form="unqualified"/>')
|
|
129
129
|
end
|
|
130
130
|
|
|
131
131
|
it "serializes element without namespace prefix (as declared)" do
|
|
@@ -174,8 +174,8 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
174
174
|
xsd = Lutaml::Model::Schema.to_xsd(model_class)
|
|
175
175
|
|
|
176
176
|
# Per XSD spec, form attribute should NOT be emitted when it matches elementFormDefault
|
|
177
|
-
expect(xsd).to include('<element name="name" type="xs:string"/>')
|
|
178
|
-
expect(xsd).to include('<element name="value" type="xs:string"/>')
|
|
177
|
+
expect(xsd).to include('<xs:element name="name" type="xs:string"/>')
|
|
178
|
+
expect(xsd).to include('<xs:element name="value" type="xs:string"/>')
|
|
179
179
|
expect(xsd).not_to include('form="qualified"')
|
|
180
180
|
end
|
|
181
181
|
|
|
@@ -364,7 +364,7 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
364
364
|
xsd = Lutaml::Model::Schema.to_xsd(model_class)
|
|
365
365
|
|
|
366
366
|
# Per XSD spec, form attribute should NOT be emitted when it matches the default
|
|
367
|
-
expect(xsd).to include('<attribute name="id" type="xs:string"/>')
|
|
367
|
+
expect(xsd).to include('<xs:attribute name="id" type="xs:string"/>')
|
|
368
368
|
expect(xsd).not_to include('form="unqualified"')
|
|
369
369
|
end
|
|
370
370
|
|
|
@@ -414,7 +414,7 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
414
414
|
|
|
415
415
|
# Per XSD spec, form="unqualified" MUST be emitted when it differs from attributeFormDefault
|
|
416
416
|
expect(xsd).to include('form="unqualified"')
|
|
417
|
-
expect(xsd).to include('<attribute name="id" type="xs:string" form="unqualified"/>')
|
|
417
|
+
expect(xsd).to include('<xs:attribute name="id" type="xs:string" form="unqualified"/>')
|
|
418
418
|
end
|
|
419
419
|
|
|
420
420
|
# NOTE: The following test documents a known issue where form: :unqualified
|
|
@@ -479,8 +479,8 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
479
479
|
xsd = Lutaml::Model::Schema.to_xsd(model_class)
|
|
480
480
|
|
|
481
481
|
# Per XSD spec, form attribute should NOT be emitted when it matches attributeFormDefault
|
|
482
|
-
expect(xsd).to include('<attribute name="id" type="xs:string"/>')
|
|
483
|
-
expect(xsd).to include('<attribute name="type" type="xs:string"/>')
|
|
482
|
+
expect(xsd).to include('<xs:attribute name="id" type="xs:string"/>')
|
|
483
|
+
expect(xsd).to include('<xs:attribute name="type" type="xs:string"/>')
|
|
484
484
|
expect(xsd).not_to include('form="qualified"')
|
|
485
485
|
end
|
|
486
486
|
|
|
@@ -535,13 +535,13 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
535
535
|
it "emits form='unqualified' on element that differs from default" do
|
|
536
536
|
xsd = Lutaml::Model::Schema.to_xsd(model_class)
|
|
537
537
|
|
|
538
|
-
expect(xsd).to include('<element name="element_content" type="xs:string" form="unqualified"/>')
|
|
538
|
+
expect(xsd).to include('<xs:element name="element_content" type="xs:string" form="unqualified"/>')
|
|
539
539
|
end
|
|
540
540
|
|
|
541
541
|
it "emits form='unqualified' on attribute that differs from default" do
|
|
542
542
|
xsd = Lutaml::Model::Schema.to_xsd(model_class)
|
|
543
543
|
|
|
544
|
-
expect(xsd).to include('<attribute name="id" type="xs:string" form="unqualified"/>')
|
|
544
|
+
expect(xsd).to include('<xs:attribute name="id" type="xs:string" form="unqualified"/>')
|
|
545
545
|
end
|
|
546
546
|
|
|
547
547
|
it "serializes element without namespace prefix" do
|
|
@@ -596,7 +596,7 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
596
596
|
expect(xsd).to include('elementFormDefault="qualified"')
|
|
597
597
|
|
|
598
598
|
# Elements should be declared WITHOUT form attribute (matches default)
|
|
599
|
-
expect(xsd).to include('<element name="premium" type="xs:string"/>')
|
|
599
|
+
expect(xsd).to include('<xs:element name="premium" type="xs:string"/>')
|
|
600
600
|
|
|
601
601
|
# Instance should use prefixed elements when prefix: true is specified
|
|
602
602
|
instance = model_class.new(premium: "100")
|
|
@@ -644,7 +644,7 @@ RSpec.describe "XSD Form Default Patterns" do
|
|
|
644
644
|
expect(xsd).to include('attributeFormDefault="qualified"')
|
|
645
645
|
|
|
646
646
|
# Attributes should be declared WITHOUT form attribute (matches default)
|
|
647
|
-
expect(xsd).to include('<attribute name="id" type="xs:string"/>')
|
|
647
|
+
expect(xsd).to include('<xs:attribute name="id" type="xs:string"/>')
|
|
648
648
|
|
|
649
649
|
# Instance should use prefixed attributes
|
|
650
650
|
instance = model_class.new(id: "abc")
|
|
@@ -20,11 +20,11 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
20
20
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
21
21
|
|
|
22
22
|
# Should generate inline complexType
|
|
23
|
-
expect(xsd).to include('<element name="product">')
|
|
24
|
-
expect(xsd).to include("<complexType>")
|
|
25
|
-
expect(xsd).not_to include("<complexType name=")
|
|
26
|
-
expect(xsd).to include('<element name="name" type="xs:string"/>')
|
|
27
|
-
expect(xsd).to include('<element name="value" type="xs:integer"/>')
|
|
23
|
+
expect(xsd).to include('<xs:element name="product">')
|
|
24
|
+
expect(xsd).to include("<xs:complexType>")
|
|
25
|
+
expect(xsd).not_to include("<xs:complexType name=")
|
|
26
|
+
expect(xsd).to include('<xs:element name="name" type="xs:string"/>')
|
|
27
|
+
expect(xsd).to include('<xs:element name="value" type="xs:integer"/>')
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
it "generates inline complexType for nested model without type_name" do
|
|
@@ -41,10 +41,10 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
41
41
|
|
|
42
42
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
43
43
|
|
|
44
|
-
expect(xsd).to include('<element name="item">')
|
|
45
|
-
expect(xsd).to include("<complexType>")
|
|
46
|
-
expect(xsd).not_to include("<complexType name=")
|
|
47
|
-
expect(xsd).to include('<attribute name="id" type="xs:string"/>')
|
|
44
|
+
expect(xsd).to include('<xs:element name="item">')
|
|
45
|
+
expect(xsd).to include("<xs:complexType>")
|
|
46
|
+
expect(xsd).not_to include("<xs:complexType name=")
|
|
47
|
+
expect(xsd).to include('<xs:attribute name="id" type="xs:string"/>')
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -64,11 +64,11 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
64
64
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
65
65
|
|
|
66
66
|
# Should generate named complexType only (no element)
|
|
67
|
-
expect(xsd).to include('<complexType name="ProductType">')
|
|
68
|
-
expect(xsd).to include('<element name="name" type="xs:string"/>')
|
|
69
|
-
expect(xsd).to include('<element name="value" type="xs:integer"/>')
|
|
67
|
+
expect(xsd).to include('<xs:complexType name="ProductType">')
|
|
68
|
+
expect(xsd).to include('<xs:element name="name" type="xs:string"/>')
|
|
69
|
+
expect(xsd).to include('<xs:element name="value" type="xs:integer"/>')
|
|
70
70
|
# Should NOT generate standalone element declaration
|
|
71
|
-
expect(xsd).not_to include('<element name="product"')
|
|
71
|
+
expect(xsd).not_to include('<xs:element name="product"')
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
it "type-only model can be referenced by other elements" do
|
|
@@ -99,8 +99,8 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
99
99
|
xsd = Lutaml::Model::Schema.to_xsd(person_klass)
|
|
100
100
|
|
|
101
101
|
# Should reference AddressType
|
|
102
|
-
expect(xsd).to include('<element name="address" type="AddressType"/>')
|
|
103
|
-
expect(xsd).to include('<complexType name="AddressType">')
|
|
102
|
+
expect(xsd).to include('<xs:element name="address" type="AddressType"/>')
|
|
103
|
+
expect(xsd).to include('<xs:complexType name="AddressType">')
|
|
104
104
|
end
|
|
105
105
|
end
|
|
106
106
|
|
|
@@ -121,10 +121,10 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
121
121
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
122
122
|
|
|
123
123
|
# Should generate both element and named type
|
|
124
|
-
expect(xsd).to include('<element name="product" type="ProductType"/>')
|
|
125
|
-
expect(xsd).to include('<complexType name="ProductType">')
|
|
126
|
-
expect(xsd).to include('<element name="name" type="xs:string"/>')
|
|
127
|
-
expect(xsd).to include('<element name="value" type="xs:integer"/>')
|
|
124
|
+
expect(xsd).to include('<xs:element name="product" type="ProductType"/>')
|
|
125
|
+
expect(xsd).to include('<xs:complexType name="ProductType">')
|
|
126
|
+
expect(xsd).to include('<xs:element name="name" type="xs:string"/>')
|
|
127
|
+
expect(xsd).to include('<xs:element name="value" type="xs:integer"/>')
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
it "allows reuse of named type by other elements" do
|
|
@@ -143,9 +143,9 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
143
143
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
144
144
|
|
|
145
145
|
# Element references the named type
|
|
146
|
-
expect(xsd).to include('<element name="catalog" type="CatalogType"/>')
|
|
146
|
+
expect(xsd).to include('<xs:element name="catalog" type="CatalogType"/>')
|
|
147
147
|
# Type definition exists separately
|
|
148
|
-
expect(xsd).to include('<complexType name="CatalogType">')
|
|
148
|
+
expect(xsd).to include('<xs:complexType name="CatalogType">')
|
|
149
149
|
end
|
|
150
150
|
end
|
|
151
151
|
|
|
@@ -192,8 +192,8 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
192
192
|
|
|
193
193
|
# Both should generate identical XSD
|
|
194
194
|
expect(xsd1).to eq(xsd2)
|
|
195
|
-
expect(xsd1).to include('<element name="test" type="TestType"/>')
|
|
196
|
-
expect(xsd1).to include('<complexType name="TestType">')
|
|
195
|
+
expect(xsd1).to include('<xs:element name="test" type="TestType"/>')
|
|
196
|
+
expect(xsd1).to include('<xs:complexType name="TestType">')
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
it "xsd_type does NOT auto-set type-only (NO MAGIC)" do
|
|
@@ -232,8 +232,8 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
232
232
|
expect(mapping).not_to be_no_root
|
|
233
233
|
|
|
234
234
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
235
|
-
expect(xsd).to include('<element name="simple">')
|
|
236
|
-
expect(xsd).not_to include('<element name="simple" type=')
|
|
235
|
+
expect(xsd).to include('<xs:element name="simple">')
|
|
236
|
+
expect(xsd).not_to include('<xs:element name="simple" type=')
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
it "Pattern 2: type_name only -> named reusable" do
|
|
@@ -250,9 +250,9 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
250
250
|
expect(mapping.type_name_value).to eq("SimpleType")
|
|
251
251
|
|
|
252
252
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
253
|
-
expect(xsd).to include('<complexType name="SimpleType">')
|
|
253
|
+
expect(xsd).to include('<xs:complexType name="SimpleType">')
|
|
254
254
|
# Should not have standalone element declaration (only child elements in sequence are OK)
|
|
255
|
-
expect(xsd).not_to include('<element name="SimpleType"')
|
|
255
|
+
expect(xsd).not_to include('<xs:element name="SimpleType"')
|
|
256
256
|
end
|
|
257
257
|
|
|
258
258
|
it "Pattern 3: both -> element with named type" do
|
|
@@ -270,8 +270,8 @@ RSpec.describe "XSD Three Pattern Architecture" do
|
|
|
270
270
|
expect(mapping.type_name_value).to eq("SimpleType")
|
|
271
271
|
|
|
272
272
|
xsd = Lutaml::Model::Schema.to_xsd(klass)
|
|
273
|
-
expect(xsd).to include('<element name="simple" type="SimpleType"/>')
|
|
274
|
-
expect(xsd).to include('<complexType name="SimpleType">')
|
|
273
|
+
expect(xsd).to include('<xs:element name="simple" type="SimpleType"/>')
|
|
274
|
+
expect(xsd).to include('<xs:complexType name="SimpleType">')
|
|
275
275
|
end
|
|
276
276
|
end
|
|
277
277
|
end
|
|
@@ -433,7 +433,7 @@ RSpec.describe "XSD Type Validation" do
|
|
|
433
433
|
end
|
|
434
434
|
|
|
435
435
|
xsd = described_class.generate(model)
|
|
436
|
-
expect(xsd).to
|
|
436
|
+
expect(xsd).to include('<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"')
|
|
437
437
|
expect(xsd).to include('type="xs:string"')
|
|
438
438
|
expect(xsd).to include('type="xs:integer"')
|
|
439
439
|
end
|
|
@@ -375,7 +375,7 @@ RSpec.describe Lutaml::Turtle::Transform do
|
|
|
375
375
|
instance = UriRefModel.new(name: "test", related: ["skos:other"])
|
|
376
376
|
turtle = instance.to_turtle
|
|
377
377
|
restored = UriRefModel.from_turtle(turtle)
|
|
378
|
-
expect(restored.related).to eq("skos:other")
|
|
378
|
+
expect(restored.related).to eq(["skos:other"])
|
|
379
379
|
end
|
|
380
380
|
|
|
381
381
|
it "handles full URI values without prefix" do
|
|
@@ -390,7 +390,7 @@ RSpec.describe Lutaml::Turtle::Transform do
|
|
|
390
390
|
related: ["http://example.org/foo"])
|
|
391
391
|
turtle = instance.to_turtle
|
|
392
392
|
restored = UriRefModel.from_turtle(turtle)
|
|
393
|
-
expect(restored.related).to eq("http://example.org/foo")
|
|
393
|
+
expect(restored.related).to eq(["http://example.org/foo"])
|
|
394
394
|
end
|
|
395
395
|
end
|
|
396
396
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.32
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -780,6 +780,7 @@ files:
|
|
|
780
780
|
- lib/lutaml/xml/namespace_scope_config.rb
|
|
781
781
|
- lib/lutaml/xml/namespace_type_resolver.rb
|
|
782
782
|
- lib/lutaml/xml/namespace_usage.rb
|
|
783
|
+
- lib/lutaml/xml/nested_collection_attribute.rb
|
|
783
784
|
- lib/lutaml/xml/nokogiri.rb
|
|
784
785
|
- lib/lutaml/xml/nokogiri/element.rb
|
|
785
786
|
- lib/lutaml/xml/oga.rb
|
|
@@ -1673,12 +1674,15 @@ files:
|
|
|
1673
1674
|
- spec/lutaml/model/attribute_spec.rb
|
|
1674
1675
|
- spec/lutaml/model/boot_manifest_spec.rb
|
|
1675
1676
|
- spec/lutaml/model/cached_type_resolver_spec.rb
|
|
1677
|
+
- spec/lutaml/model/cardinality_strict_spec.rb
|
|
1676
1678
|
- spec/lutaml/model/cdata_spec.rb
|
|
1677
1679
|
- spec/lutaml/model/choice_restrict_spec.rb
|
|
1678
1680
|
- spec/lutaml/model/choice_spec.rb
|
|
1679
1681
|
- spec/lutaml/model/cli_spec.rb
|
|
1680
1682
|
- spec/lutaml/model/collection_index_spec.rb
|
|
1681
1683
|
- spec/lutaml/model/collection_mutation_spec.rb
|
|
1684
|
+
- spec/lutaml/model/collection_register_spec.rb
|
|
1685
|
+
- spec/lutaml/model/collection_single_value_coercion_spec.rb
|
|
1682
1686
|
- spec/lutaml/model/collection_spec.rb
|
|
1683
1687
|
- spec/lutaml/model/collection_state_nesting_spec.rb
|
|
1684
1688
|
- spec/lutaml/model/collection_validation_spec.rb
|