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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +25 -23
  3. data/docs/_pages/breaking-changes.adoc +58 -0
  4. data/docs/_pages/collections.adoc +25 -24
  5. data/docs/_pages/validation.adoc +48 -1
  6. data/lib/compat/opal/lutaml_model_boot.rb +1 -0
  7. data/lib/lutaml/key_value/transform.rb +7 -1
  8. data/lib/lutaml/model/attribute.rb +67 -24
  9. data/lib/lutaml/model/collection.rb +5 -2
  10. data/lib/lutaml/model/collection_handler.rb +45 -2
  11. data/lib/lutaml/model/serialize/enum_handling.rb +5 -1
  12. data/lib/lutaml/model/validation.rb +49 -4
  13. data/lib/lutaml/model/version.rb +1 -1
  14. data/lib/lutaml/xml/format.rb +2 -0
  15. data/lib/lutaml/xml/model_transform.rb +33 -9
  16. data/lib/lutaml/xml/nested_collection_attribute.rb +95 -0
  17. data/lib/lutaml/xml/schema/xsd_schema.rb +396 -128
  18. data/lib/lutaml/xml/serialization/instance_methods.rb +2 -2
  19. data/lib/lutaml/xml/transformation/element_builder.rb +55 -8
  20. data/spec/lutaml/model/attribute_collection_spec.rb +156 -0
  21. data/spec/lutaml/model/attribute_spec.rb +1 -1
  22. data/spec/lutaml/model/cardinality_strict_spec.rb +614 -0
  23. data/spec/lutaml/model/cdata_spec.rb +1 -1
  24. data/spec/lutaml/model/collection_register_spec.rb +119 -0
  25. data/spec/lutaml/model/collection_single_value_coercion_spec.rb +222 -0
  26. data/spec/lutaml/model/root_mappings_spec.rb +10 -7
  27. data/spec/lutaml/model/schema/xsd_schema_spec.rb +785 -25
  28. data/spec/lutaml/model/serializable_validation_spec.rb +293 -0
  29. data/spec/lutaml/model/value_policy_spec.rb +6 -3
  30. data/spec/lutaml/model/xsd_form_default_patterns_spec.rb +12 -12
  31. data/spec/lutaml/model/xsd_patterns_spec.rb +29 -29
  32. data/spec/lutaml/model/xsd_type_validation_spec.rb +1 -1
  33. data/spec/lutaml/turtle/transform_spec.rb +2 -2
  34. 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 error" do
71
- expect { ValuePolicySpec::StringsDoc.from_yaml("v: [a]") }
72
- .to raise_error(Lutaml::Model::CollectionTrueMissingError)
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 match('<schema xmlns="http://www.w3.org/2001/XMLSchema">')
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.31
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