lutaml-model 0.6.7 → 0.7.2

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos-todo.json +7 -0
  3. data/.github/workflows/dependent-repos.json +17 -9
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +51 -65
  6. data/Gemfile +4 -1
  7. data/README.adoc +5083 -2612
  8. data/RELEASE_NOTES.adoc +346 -0
  9. data/docs/custom_adapters.adoc +144 -0
  10. data/lib/lutaml/model/attribute.rb +101 -16
  11. data/lib/lutaml/model/choice.rb +7 -0
  12. data/lib/lutaml/model/comparable_model.rb +48 -9
  13. data/lib/lutaml/model/config.rb +48 -42
  14. data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
  15. data/lib/lutaml/model/error/polymorphic_error.rb +14 -0
  16. data/lib/lutaml/model/error.rb +1 -0
  17. data/lib/lutaml/model/format_registry.rb +41 -0
  18. data/lib/lutaml/model/hash/document.rb +11 -0
  19. data/lib/lutaml/model/hash/mapping.rb +19 -0
  20. data/lib/lutaml/model/hash/mapping_rule.rb +9 -0
  21. data/lib/lutaml/model/hash/standard_adapter.rb +17 -0
  22. data/lib/lutaml/model/hash/transform.rb +8 -0
  23. data/lib/lutaml/model/hash.rb +21 -0
  24. data/lib/lutaml/model/json/document.rb +11 -0
  25. data/lib/lutaml/model/json/mapping.rb +19 -0
  26. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  27. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  28. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  29. data/lib/lutaml/model/json/transform.rb +8 -0
  30. data/lib/lutaml/model/json.rb +21 -0
  31. data/lib/lutaml/model/key_value_document.rb +27 -0
  32. data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +64 -16
  33. data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
  34. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  35. data/lib/lutaml/model/mapping/mapping_rule.rb +300 -0
  36. data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
  37. data/lib/lutaml/model/sequence.rb +2 -2
  38. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  39. data/lib/lutaml/model/serialize.rb +219 -444
  40. data/lib/lutaml/model/services/logger.rb +54 -0
  41. data/lib/lutaml/model/services/transformer.rb +48 -0
  42. data/lib/lutaml/model/services.rb +2 -0
  43. data/lib/lutaml/model/toml/document.rb +11 -0
  44. data/lib/lutaml/model/toml/mapping.rb +27 -0
  45. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  47. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  48. data/lib/lutaml/model/toml/transform.rb +8 -0
  49. data/lib/lutaml/model/toml.rb +30 -0
  50. data/lib/lutaml/model/transform/key_value_transform.rb +291 -0
  51. data/lib/lutaml/model/transform/xml_transform.rb +239 -0
  52. data/lib/lutaml/model/transform.rb +78 -0
  53. data/lib/lutaml/model/type/date.rb +1 -1
  54. data/lib/lutaml/model/type/date_time.rb +2 -2
  55. data/lib/lutaml/model/type/hash.rb +1 -1
  56. data/lib/lutaml/model/type/time.rb +2 -2
  57. data/lib/lutaml/model/type/time_without_date.rb +2 -2
  58. data/lib/lutaml/model/type/value.rb +6 -9
  59. data/lib/lutaml/model/uninitialized_class.rb +64 -0
  60. data/lib/lutaml/model/utils.rb +44 -0
  61. data/lib/lutaml/model/validation.rb +1 -0
  62. data/lib/lutaml/model/version.rb +1 -1
  63. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  64. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  65. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  66. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +41 -21
  67. data/lib/lutaml/model/xml/element.rb +32 -0
  68. data/lib/lutaml/model/xml/mapping.rb +410 -0
  69. data/lib/lutaml/model/xml/mapping_rule.rb +141 -0
  70. data/lib/lutaml/model/xml/nokogiri_adapter.rb +232 -0
  71. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  72. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +3 -1
  73. data/lib/lutaml/model/xml/oga_adapter.rb +171 -0
  74. data/lib/lutaml/model/xml/ox_adapter.rb +215 -0
  75. data/lib/lutaml/model/xml/transform.rb +8 -0
  76. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  77. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +23 -10
  78. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  79. data/lib/lutaml/model/xml.rb +31 -0
  80. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  81. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  82. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  83. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  84. data/lib/lutaml/model/yaml/document.rb +10 -0
  85. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  86. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  87. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  88. data/lib/lutaml/model/yaml/transform.rb +8 -0
  89. data/lib/lutaml/model/yaml.rb +21 -0
  90. data/lib/lutaml/model.rb +40 -4
  91. data/lutaml-model.gemspec +0 -4
  92. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +7 -7
  93. data/spec/fixtures/person.rb +5 -5
  94. data/spec/lutaml/model/attribute_spec.rb +37 -1
  95. data/spec/lutaml/model/cdata_spec.rb +9 -9
  96. data/spec/lutaml/model/collection_spec.rb +50 -2
  97. data/spec/lutaml/model/comparable_model_spec.rb +92 -27
  98. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  99. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  100. data/spec/lutaml/model/defaults_spec.rb +1 -1
  101. data/spec/lutaml/model/enum_spec.rb +1 -1
  102. data/spec/lutaml/model/group_spec.rb +333 -20
  103. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  104. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  105. data/spec/lutaml/model/key_value_mapping_spec.rb +65 -3
  106. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  107. data/spec/lutaml/model/multiple_mapping_spec.rb +5 -5
  108. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  109. data/spec/lutaml/model/polymorphic_spec.rb +526 -0
  110. data/spec/lutaml/model/render_empty_spec.rb +194 -0
  111. data/spec/lutaml/model/render_nil_spec.rb +206 -22
  112. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  113. data/spec/lutaml/model/schema/xml_compiler_spec.rb +6 -6
  114. data/spec/lutaml/model/serializable_spec.rb +179 -103
  115. data/spec/lutaml/model/simple_model_spec.rb +9 -9
  116. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  117. data/spec/lutaml/model/toml_spec.rb +51 -0
  118. data/spec/lutaml/model/transformation_spec.rb +72 -15
  119. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  120. data/spec/lutaml/model/value_map_spec.rb +240 -0
  121. data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
  122. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  123. data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
  124. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  125. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  126. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  127. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  128. data/spec/lutaml/model/xml_adapter_spec.rb +6 -6
  129. data/spec/lutaml/model/xml_mapping_rule_spec.rb +105 -5
  130. data/spec/lutaml/model/xml_mapping_spec.rb +70 -16
  131. data/spec/lutaml/model/xml_spec.rb +63 -0
  132. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  133. data/spec/sample_model_spec.rb +3 -3
  134. data/spec/spec_helper.rb +3 -3
  135. metadata +76 -59
  136. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  137. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  138. data/lib/lutaml/model/loggable.rb +0 -15
  139. data/lib/lutaml/model/mapping_rule.rb +0 -109
  140. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  141. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  142. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  143. data/lib/lutaml/model/toml_adapter.rb +0 -6
  144. data/lib/lutaml/model/xml_mapping.rb +0 -307
  145. data/lib/lutaml/model/xml_mapping_rule.rb +0 -122
  146. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  147. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  148. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -0,0 +1,526 @@
1
+ require "spec_helper"
2
+
3
+ module PolymorphicSpec
4
+ module Base
5
+ class Reference < Lutaml::Model::Serializable
6
+ attribute :_class, :string, polymorphic_class: true
7
+ attribute :name, :string
8
+
9
+ xml do
10
+ map_attribute "reference-type", to: :_class, polymorphic_map: {
11
+ "document-ref" => "PolymorphicSpec::Base::DocumentReference",
12
+ "anchor-ref" => "PolymorphicSpec::Base::AnchorReference",
13
+ }
14
+ map_element "name", to: :name
15
+ end
16
+
17
+ key_value do
18
+ map "_class", to: :_class, polymorphic_map: {
19
+ "Document" => "PolymorphicSpec::Base::DocumentReference",
20
+ "Anchor" => "PolymorphicSpec::Base::AnchorReference",
21
+ }
22
+ map "name", to: :name
23
+ end
24
+ end
25
+
26
+ class DocumentReference < Reference
27
+ attribute :document_id, :string
28
+
29
+ xml do
30
+ map_element "document_id", to: :document_id
31
+ end
32
+
33
+ key_value do
34
+ map "document_id", to: :document_id
35
+ end
36
+ end
37
+
38
+ class AnchorReference < Reference
39
+ attribute :anchor_id, :string
40
+
41
+ xml do
42
+ map_element "anchor_id", to: :anchor_id
43
+ end
44
+
45
+ key_value do
46
+ map "anchor_id", to: :anchor_id
47
+ end
48
+ end
49
+
50
+ module InvalidClasses
51
+ class AnchorReference < Lutaml::Model::Serializable
52
+ attribute :anchor_id, :string
53
+
54
+ xml do
55
+ map_element "anchor_id", to: :anchor_id
56
+ end
57
+
58
+ key_value do
59
+ map "anchor_id", to: :anchor_id
60
+ end
61
+ end
62
+
63
+ class DocumentReference < Reference
64
+ attribute :document_id, :string
65
+
66
+ xml do
67
+ map_element "document_id", to: :document_id
68
+ end
69
+
70
+ key_value do
71
+ map "document_id", to: :document_id
72
+ end
73
+ end
74
+ end
75
+
76
+ class ReferenceSet < Lutaml::Model::Serializable
77
+ attribute :references, Reference, collection: true, polymorphic: [
78
+ DocumentReference,
79
+ AnchorReference,
80
+ ]
81
+ end
82
+
83
+ class SimpleReferenceSet < Lutaml::Model::Serializable
84
+ attribute :references, Reference, collection: true, polymorphic: true
85
+
86
+ xml do
87
+ root "ReferenceSet"
88
+ map_element "references", to: :references, polymorphic: {
89
+ attribute: "reference-type",
90
+ class_map: {
91
+ "document-ref" => "PolymorphicSpec::Base::DocumentReference",
92
+ "anchor-ref" => "PolymorphicSpec::Base::AnchorReference",
93
+ },
94
+ }
95
+ end
96
+
97
+ key_value do
98
+ map "references", to: :references, polymorphic: {
99
+ attribute: "_class",
100
+ class_map: {
101
+ "Document" => "PolymorphicSpec::Base::DocumentReference",
102
+ "Anchor" => "PolymorphicSpec::Base::AnchorReference",
103
+ },
104
+ }
105
+ end
106
+ end
107
+ end
108
+
109
+ module Child
110
+ # Case: If we have no access to the base class and we need to
111
+ # define polymorphism in the sub-classes.
112
+ class Reference < Lutaml::Model::Serializable
113
+ attribute :name, :string
114
+
115
+ xml do
116
+ map_element "name", to: :name
117
+ end
118
+
119
+ key_value do
120
+ map "name", to: :name
121
+ end
122
+ end
123
+
124
+ class DocumentReference < Reference
125
+ attribute :_class, :string
126
+ attribute :document_id, :string
127
+
128
+ xml do
129
+ map_element "document_id", to: :document_id
130
+ map_attribute "_class", to: :_class
131
+ end
132
+
133
+ key_value do
134
+ map "document_id", to: :document_id
135
+ map "_class", to: :_class
136
+ end
137
+ end
138
+
139
+ class AnchorReference < Reference
140
+ attribute :_class, :string
141
+ attribute :anchor_id, :string
142
+
143
+ xml do
144
+ map_element "anchor_id", to: :anchor_id
145
+ map_attribute "_class", to: :_class
146
+ end
147
+
148
+ key_value do
149
+ map "anchor_id", to: :anchor_id
150
+ map "_class", to: :_class
151
+ end
152
+ end
153
+
154
+ class ReferenceSet < Lutaml::Model::Serializable
155
+ attribute :references, Reference, collection: true, polymorphic: [
156
+ DocumentReference,
157
+ AnchorReference,
158
+ ]
159
+
160
+ xml do
161
+ root "ReferenceSet"
162
+
163
+ map_element "references", to: :references, polymorphic: {
164
+ # This refers to the attribute in the polymorphic model, you need
165
+ # to specify the attribute name (which is specified in the sub-classed model).
166
+ attribute: "_class",
167
+ class_map: {
168
+ "document-ref" => "PolymorphicSpec::Child::DocumentReference",
169
+ "anchor-ref" => "PolymorphicSpec::Child::AnchorReference",
170
+ },
171
+ }
172
+ end
173
+
174
+ key_value do
175
+ map "references", to: :references, polymorphic: {
176
+ attribute: "_class",
177
+ class_map: {
178
+ "Document" => "PolymorphicSpec::Child::DocumentReference",
179
+ "Anchor" => "PolymorphicSpec::Child::AnchorReference",
180
+ },
181
+ }
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ RSpec.describe "Polymorphic" do
188
+ context "when defined in base class" do
189
+ context "when key_value formats" do
190
+ let(:reference_set) do
191
+ PolymorphicSpec::Base::ReferenceSet.new(
192
+ references: [
193
+ PolymorphicSpec::Base::DocumentReference.new(
194
+ _class: "Document",
195
+ document_id: "book:tbtd",
196
+ name: "The Tibetan Book of the Dead",
197
+ ),
198
+ PolymorphicSpec::Base::AnchorReference.new(
199
+ _class: "Anchor",
200
+ anchor_id: "book:tbtd:anchor-1",
201
+ name: "Chapter 1",
202
+ ),
203
+ ],
204
+ )
205
+ end
206
+
207
+ let(:yaml) do
208
+ <<~YAML
209
+ ---
210
+ references:
211
+ - _class: Document
212
+ name: The Tibetan Book of the Dead
213
+ document_id: book:tbtd
214
+ - _class: Anchor
215
+ name: Chapter 1
216
+ anchor_id: book:tbtd:anchor-1
217
+ YAML
218
+ end
219
+
220
+ let(:parsed_yaml) do
221
+ PolymorphicSpec::Base::ReferenceSet.from_yaml(yaml)
222
+ end
223
+
224
+ it "deserializes correctly" do
225
+ expect(parsed_yaml).to eq(reference_set)
226
+ end
227
+
228
+ it "serializes correctly" do
229
+ expect(parsed_yaml.to_yaml).to eq(yaml)
230
+ end
231
+ end
232
+
233
+ context "when XML format" do
234
+ let(:reference_set) do
235
+ PolymorphicSpec::Base::ReferenceSet.new(
236
+ references: [
237
+ PolymorphicSpec::Base::DocumentReference.new(
238
+ _class: "document-ref",
239
+ document_id: "book:tbtd",
240
+ name: "The Tibetan Book of the Dead",
241
+ ),
242
+ PolymorphicSpec::Base::AnchorReference.new(
243
+ _class: "anchor-ref",
244
+ anchor_id: "book:tbtd:anchor-1",
245
+ name: "Chapter 1",
246
+ ),
247
+ ],
248
+ )
249
+ end
250
+
251
+ let(:xml) do
252
+ <<~XML
253
+ <ReferenceSet>
254
+ <references reference-type="document-ref">
255
+ <name>The Tibetan Book of the Dead</name>
256
+ <document_id>book:tbtd</document_id>
257
+ </references>
258
+ <references reference-type="anchor-ref">
259
+ <name>Chapter 1</name>
260
+ <anchor_id>book:tbtd:anchor-1</anchor_id>
261
+ </references>
262
+ </ReferenceSet>
263
+ XML
264
+ end
265
+
266
+ let(:parsed_xml) do
267
+ PolymorphicSpec::Base::ReferenceSet.from_xml(xml)
268
+ end
269
+
270
+ it "deserializes correctly" do
271
+ expect(parsed_xml).to eq(reference_set)
272
+ end
273
+
274
+ it "serializes correctly" do
275
+ expect(parsed_xml.to_xml.strip).to be_equivalent_to(xml.strip)
276
+ end
277
+ end
278
+ end
279
+
280
+ context "when using polymorphic: true option" do
281
+ context "when key_value formats" do
282
+ let(:reference_set) do
283
+ PolymorphicSpec::Base::SimpleReferenceSet.new(
284
+ references: [
285
+ PolymorphicSpec::Base::DocumentReference.new(
286
+ _class: "Document",
287
+ document_id: "book:tbtd",
288
+ name: "The Tibetan Book of the Dead",
289
+ ),
290
+ PolymorphicSpec::Base::AnchorReference.new(
291
+ _class: "Anchor",
292
+ anchor_id: "book:tbtd:anchor-1",
293
+ name: "Chapter 1",
294
+ ),
295
+ ],
296
+ )
297
+ end
298
+
299
+ let(:invalid_reference_set) do
300
+ PolymorphicSpec::Base::SimpleReferenceSet.new(
301
+ references: [
302
+ PolymorphicSpec::Base::InvalidClasses::DocumentReference.new(
303
+ _class: "Document",
304
+ document_id: "book:tbtd",
305
+ name: "The Tibetan Book of the Dead",
306
+ ),
307
+ PolymorphicSpec::Base::InvalidClasses::AnchorReference.new(
308
+ _class: "Anchor",
309
+ anchor_id: "book:tbtd:anchor-1",
310
+ name: "Chapter 1",
311
+ ),
312
+ ],
313
+ )
314
+ end
315
+
316
+ let(:yaml) do
317
+ <<~YAML
318
+ ---
319
+ references:
320
+ - _class: Document
321
+ name: The Tibetan Book of the Dead
322
+ document_id: book:tbtd
323
+ - _class: Anchor
324
+ name: Chapter 1
325
+ anchor_id: book:tbtd:anchor-1
326
+ YAML
327
+ end
328
+
329
+ let(:parsed_yaml) do
330
+ PolymorphicSpec::Base::SimpleReferenceSet.from_yaml(yaml)
331
+ end
332
+
333
+ let(:error_message) do
334
+ "PolymorphicSpec::Base::InvalidClasses::AnchorReference is not " \
335
+ "valid sub class of PolymorphicSpec::Base::Reference"
336
+ end
337
+
338
+ it "deserializes correctly" do
339
+ expect(parsed_yaml).to eq(reference_set)
340
+ end
341
+
342
+ it "serializes correctly" do
343
+ expect(parsed_yaml.to_yaml).to eq(yaml)
344
+ end
345
+
346
+ it "raises error" do
347
+ expect do
348
+ invalid_reference_set.validate!
349
+ end.to raise_error(Lutaml::Model::ValidationError, error_message)
350
+ end
351
+ end
352
+
353
+ context "when XML format" do
354
+ let(:reference_set) do
355
+ PolymorphicSpec::Base::SimpleReferenceSet.new(
356
+ references: [
357
+ PolymorphicSpec::Base::DocumentReference.new(
358
+ _class: "document-ref",
359
+ document_id: "book:tbtd",
360
+ name: "The Tibetan Book of the Dead",
361
+ ),
362
+ PolymorphicSpec::Base::AnchorReference.new(
363
+ _class: "anchor-ref",
364
+ anchor_id: "book:tbtd:anchor-1",
365
+ name: "Chapter 1",
366
+ ),
367
+ ],
368
+ )
369
+ end
370
+ let(:xml) do
371
+ <<~XML
372
+ <ReferenceSet>
373
+ <references reference-type="document-ref">
374
+ <name>The Tibetan Book of the Dead</name>
375
+ <document_id>book:tbtd</document_id>
376
+ </references>
377
+ <references reference-type="anchor-ref">
378
+ <name>Chapter 1</name>
379
+ <anchor_id>book:tbtd:anchor-1</anchor_id>
380
+ </references>
381
+ </ReferenceSet>
382
+ XML
383
+ end
384
+
385
+ let(:parsed_xml) do
386
+ PolymorphicSpec::Base::SimpleReferenceSet.from_xml(xml)
387
+ end
388
+
389
+ it "deserializes correctly" do
390
+ expect(parsed_xml).to eq(reference_set)
391
+ end
392
+
393
+ it "serializes correctly" do
394
+ expect(parsed_xml.to_xml.strip).to be_equivalent_to(xml.strip)
395
+ end
396
+
397
+ it "does not raise error if polymorphic is set to true" do
398
+ expect { reference_set.validate! }.not_to raise_error
399
+ end
400
+
401
+ it "has empty errors array on validate" do
402
+ expect(reference_set.validate).to eq([])
403
+ end
404
+ end
405
+ end
406
+
407
+ context "when defined in child class" do
408
+ context "when key_value formats" do
409
+ let(:reference_set) do
410
+ PolymorphicSpec::Base::ReferenceSet.new(
411
+ references: [
412
+ PolymorphicSpec::Base::DocumentReference.new(
413
+ _class: "Document",
414
+ document_id: "book:tbtd",
415
+ name: "The Tibetan Book of the Dead",
416
+ ),
417
+ PolymorphicSpec::Base::AnchorReference.new(
418
+ _class: "Anchor",
419
+ anchor_id: "book:tbtd:anchor-1",
420
+ name: "Chapter 1",
421
+ ),
422
+ ],
423
+ )
424
+ end
425
+
426
+ let(:yaml) do
427
+ <<~YAML
428
+ ---
429
+ references:
430
+ - _class: Document
431
+ name: The Tibetan Book of the Dead
432
+ document_id: book:tbtd
433
+ - _class: Anchor
434
+ name: Chapter 1
435
+ anchor_id: book:tbtd:anchor-1
436
+ YAML
437
+ end
438
+
439
+ let(:parsed_yaml) do
440
+ PolymorphicSpec::Base::ReferenceSet.from_yaml(yaml)
441
+ end
442
+
443
+ it "deserializes correctly" do
444
+ expect(parsed_yaml).to eq(reference_set)
445
+ end
446
+
447
+ it "serializes correctly" do
448
+ expect(parsed_yaml.to_yaml).to eq(yaml)
449
+ end
450
+ end
451
+
452
+ context "when XML format" do
453
+ let(:reference_set) do
454
+ PolymorphicSpec::Child::ReferenceSet.new(
455
+ references: [
456
+ PolymorphicSpec::Child::DocumentReference.new(
457
+ _class: "document-ref",
458
+ document_id: "book:tbtd",
459
+ name: "The Tibetan Book of the Dead",
460
+ ),
461
+ PolymorphicSpec::Child::AnchorReference.new(
462
+ _class: "anchor-ref",
463
+ anchor_id: "book:tbtd:anchor-1",
464
+ name: "Chapter 1",
465
+ ),
466
+ ],
467
+ )
468
+ end
469
+
470
+ let(:invalid_ref) do
471
+ PolymorphicSpec::Child::ReferenceSet.new(
472
+ references: [
473
+ PolymorphicSpec::Child::DocumentReference.new(
474
+ _class: "document-ref",
475
+ document_id: "book:tbtd",
476
+ name: "The Tibetan Book of the Dead",
477
+ ),
478
+ PolymorphicSpec::Base::AnchorReference.new(
479
+ _class: "anchor-ref",
480
+ anchor_id: "book:tbtd:anchor-1",
481
+ name: "Chapter 1",
482
+ ),
483
+ ],
484
+ )
485
+ end
486
+
487
+ let(:xml) do
488
+ <<~XML
489
+ <ReferenceSet>
490
+ <references _class="document-ref">
491
+ <name>The Tibetan Book of the Dead</name>
492
+ <document_id>book:tbtd</document_id>
493
+ </references>
494
+ <references _class="anchor-ref">
495
+ <name>Chapter 1</name>
496
+ <anchor_id>book:tbtd:anchor-1</anchor_id>
497
+ </references>
498
+ </ReferenceSet>
499
+ XML
500
+ end
501
+
502
+ let(:parsed_xml) do
503
+ PolymorphicSpec::Child::ReferenceSet.from_xml(xml)
504
+ end
505
+
506
+ it "deserializes correctly" do
507
+ expect(parsed_xml).to eq(reference_set)
508
+ end
509
+
510
+ it "serializes correctly" do
511
+ expect(parsed_xml.to_xml.strip).to be_equivalent_to(xml.strip)
512
+ end
513
+
514
+ it "does not raises error for valid polymorphic class" do
515
+ expect { reference_set.validate! }.not_to raise_error
516
+ end
517
+
518
+ it "raises error if polymorphic class is not in list" do
519
+ expect { invalid_ref.validate! }.to raise_error(Lutaml::Model::ValidationError) do |error|
520
+ expect(error).to include(Lutaml::Model::PolymorphicError)
521
+ expect(error.error_messages).to include("PolymorphicSpec::Base::AnchorReference not in [PolymorphicSpec::Child::DocumentReference, PolymorphicSpec::Child::AnchorReference]")
522
+ end
523
+ end
524
+ end
525
+ end
526
+ end
@@ -0,0 +1,194 @@
1
+ module RenderEmptySpec
2
+ class DefaultModel < Lutaml::Model::Serializable
3
+ attribute :items, :string, collection: true
4
+
5
+ xml do
6
+ root "default-model"
7
+ map_element "items", to: :items
8
+ end
9
+ end
10
+
11
+ class EmptyInitModel < Lutaml::Model::Serializable
12
+ attribute :items, :string, collection: true, initialize_empty: true
13
+
14
+ xml do
15
+ root "empty-init-model"
16
+ map_element "items", to: :items
17
+ end
18
+ end
19
+
20
+ class OmitEmptyModel < Lutaml::Model::Serializable
21
+ attribute :items, :string, collection: true
22
+
23
+ xml do
24
+ root "omit-empty-model"
25
+ map_element "items", to: :items, render_empty: :omit
26
+ end
27
+
28
+ key_value do
29
+ map "items", to: :items, render_empty: :omit
30
+ end
31
+ end
32
+
33
+ class ExplicitEmptyModel < Lutaml::Model::Serializable
34
+ attribute :items, :string, collection: true
35
+
36
+ key_value do
37
+ map "items", to: :items, render_empty: :as_empty
38
+ end
39
+ end
40
+
41
+ class ExplicitBlankModel < Lutaml::Model::Serializable
42
+ attribute :items, :string, collection: true
43
+
44
+ xml do
45
+ map_element "items", to: :items, render_empty: :as_blank
46
+ end
47
+ end
48
+
49
+ class ExplicitNilModel < Lutaml::Model::Serializable
50
+ attribute :items, :string, collection: true
51
+
52
+ xml do
53
+ root "explicit-nil-model"
54
+ map_element "items", to: :items, render_empty: :as_nil
55
+ end
56
+
57
+ yaml do
58
+ map "items", to: :items, render_empty: :as_nil
59
+ end
60
+ end
61
+ end
62
+
63
+ RSpec.describe "RenderEmptySpec" do
64
+ describe "Collection States" do
65
+ context "with default behavior (initialize_empty: false)" do
66
+ it "defaults to nil" do
67
+ model = RenderEmptySpec::DefaultModel.new
68
+ expect(model.items).to be_nil
69
+ expect(model.to_xml).to eq("<default-model/>")
70
+ end
71
+ end
72
+
73
+ context "with initialize_empty: true" do
74
+ it "defaults to empty array" do
75
+ model = RenderEmptySpec::EmptyInitModel.new
76
+ expect(model.items).to eq([])
77
+ expect(model.to_xml).to eq("<empty-init-model/>")
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "render_empty option" do
83
+ context "when :omit" do
84
+ let(:model) do
85
+ RenderEmptySpec::OmitEmptyModel.new(items: [])
86
+ end
87
+
88
+ it "omits empty collections in XML" do
89
+ expect(model.to_xml).not_to include("<items")
90
+ end
91
+
92
+ it "omits empty collections in YAML" do
93
+ expect(model.to_yaml.strip).to eq("--- {}")
94
+ end
95
+ end
96
+
97
+ context "when :as_empty" do
98
+ let(:model) do
99
+ RenderEmptySpec::ExplicitEmptyModel.new(items: [])
100
+ end
101
+
102
+ let(:parsed) do
103
+ RenderEmptySpec::ExplicitEmptyModel.from_yaml(yaml)
104
+ end
105
+
106
+ let(:yaml) do
107
+ <<~YAML
108
+ ---
109
+ items: []
110
+ YAML
111
+ end
112
+
113
+ it "renders explicit empty collection" do
114
+ expect(model.to_yaml).to eq(yaml)
115
+ end
116
+
117
+ it "sets empty values while deserialize" do
118
+ expect(parsed.items).to eq([])
119
+ end
120
+ end
121
+
122
+ context "when :as_blank" do
123
+ let(:model) do
124
+ RenderEmptySpec::ExplicitBlankModel.new(items: [])
125
+ end
126
+
127
+ let(:parsed) do
128
+ RenderEmptySpec::ExplicitBlankModel.from_xml(xml)
129
+ end
130
+
131
+ let(:xml) do
132
+ <<~XML
133
+ <explicit-blank-model>
134
+ <items/>
135
+ </explicit-blank-model>
136
+ XML
137
+ end
138
+
139
+ it "creates blank element from empty collections" do
140
+ expect(model.to_xml).to include("<items/>")
141
+ end
142
+
143
+ it "sets blank values while deserialize" do
144
+ expect(parsed.items).to eq([])
145
+ end
146
+ end
147
+
148
+ context "when :as_nil" do
149
+ let(:model) do
150
+ RenderEmptySpec::ExplicitNilModel.new(items: [])
151
+ end
152
+
153
+ describe "XML" do
154
+ let(:xml) do
155
+ <<~XML
156
+ <explicit-nil-model>
157
+ <items xsi:nil="true"/>
158
+ </explicit-nil-model>
159
+ XML
160
+ end
161
+
162
+ let(:parsed) do
163
+ RenderEmptySpec::ExplicitNilModel.from_xml(xml)
164
+ end
165
+
166
+ it "renders explicit nil" do
167
+ expect(model.to_xml).to include('<items xsi:nil="true"/>')
168
+ end
169
+
170
+ it "sets nil values while deserializing" do
171
+ expect(parsed.items).to eq([])
172
+ end
173
+ end
174
+
175
+ describe "YAML" do
176
+ let(:yaml) do
177
+ <<~YAML
178
+ ---
179
+ items: []
180
+ YAML
181
+ end
182
+
183
+ it "renders explicit nil in YAML" do
184
+ expect(model.to_yaml).to include("items:")
185
+ end
186
+
187
+ it "sets nil values while deserializing YAML" do
188
+ model = RenderEmptySpec::ExplicitNilModel.from_yaml(yaml)
189
+ expect(model.items).to eq([])
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end