lutaml-model 0.6.7 → 0.7.1

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 (60) 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_todo.yml +18 -33
  5. data/README.adoc +4380 -2557
  6. data/lib/lutaml/model/attribute.rb +94 -15
  7. data/lib/lutaml/model/choice.rb +7 -0
  8. data/lib/lutaml/model/comparable_model.rb +48 -9
  9. data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +1 -1
  10. data/lib/lutaml/model/error/polymorphic_error.rb +9 -0
  11. data/lib/lutaml/model/error.rb +1 -0
  12. data/lib/lutaml/model/mapping/json_mapping.rb +17 -0
  13. data/lib/lutaml/model/{key_value_mapping.rb → mapping/key_value_mapping.rb} +58 -14
  14. data/lib/lutaml/model/{key_value_mapping_rule.rb → mapping/key_value_mapping_rule.rb} +18 -2
  15. data/lib/lutaml/model/mapping/mapping_rule.rb +299 -0
  16. data/lib/lutaml/model/mapping/toml_mapping.rb +25 -0
  17. data/lib/lutaml/model/{xml_mapping.rb → mapping/xml_mapping.rb} +97 -15
  18. data/lib/lutaml/model/{xml_mapping_rule.rb → mapping/xml_mapping_rule.rb} +20 -3
  19. data/lib/lutaml/model/mapping/yaml_mapping.rb +17 -0
  20. data/lib/lutaml/model/mapping.rb +14 -0
  21. data/lib/lutaml/model/schema/xml_compiler.rb +15 -15
  22. data/lib/lutaml/model/sequence.rb +2 -2
  23. data/lib/lutaml/model/serialize.rb +247 -97
  24. data/lib/lutaml/model/type/date.rb +1 -1
  25. data/lib/lutaml/model/type/date_time.rb +2 -2
  26. data/lib/lutaml/model/type/hash.rb +1 -1
  27. data/lib/lutaml/model/type/time.rb +2 -2
  28. data/lib/lutaml/model/type/time_without_date.rb +2 -2
  29. data/lib/lutaml/model/uninitialized_class.rb +64 -0
  30. data/lib/lutaml/model/utils.rb +14 -0
  31. data/lib/lutaml/model/validation.rb +1 -0
  32. data/lib/lutaml/model/version.rb +1 -1
  33. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  34. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  35. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  36. data/lib/lutaml/model/xml_adapter/xml_document.rb +38 -17
  37. data/lib/lutaml/model/xml_adapter/xml_element.rb +17 -7
  38. data/lib/lutaml/model.rb +1 -0
  39. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +3 -3
  40. data/spec/fixtures/person.rb +5 -5
  41. data/spec/lutaml/model/attribute_spec.rb +37 -1
  42. data/spec/lutaml/model/cdata_spec.rb +2 -2
  43. data/spec/lutaml/model/collection_spec.rb +50 -2
  44. data/spec/lutaml/model/comparable_model_spec.rb +92 -27
  45. data/spec/lutaml/model/defaults_spec.rb +1 -1
  46. data/spec/lutaml/model/enum_spec.rb +1 -1
  47. data/spec/lutaml/model/group_spec.rb +316 -14
  48. data/spec/lutaml/model/key_value_mapping_spec.rb +41 -3
  49. data/spec/lutaml/model/polymorphic_spec.rb +348 -0
  50. data/spec/lutaml/model/render_empty_spec.rb +194 -0
  51. data/spec/lutaml/model/render_nil_spec.rb +206 -22
  52. data/spec/lutaml/model/simple_model_spec.rb +9 -9
  53. data/spec/lutaml/model/value_map_spec.rb +240 -0
  54. data/spec/lutaml/model/xml/namespace/nested_with_explicit_namespace_spec.rb +85 -0
  55. data/spec/lutaml/model/xml/xml_element_spec.rb +93 -0
  56. data/spec/lutaml/model/xml_mapping_rule_spec.rb +102 -2
  57. data/spec/lutaml/model/xml_mapping_spec.rb +45 -3
  58. data/spec/sample_model_spec.rb +3 -3
  59. metadata +20 -8
  60. data/lib/lutaml/model/mapping_rule.rb +0 -109
@@ -0,0 +1,348 @@
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
+ class ReferenceSet < Lutaml::Model::Serializable
51
+ attribute :references, Reference, collection: true, polymorphic: [
52
+ DocumentReference,
53
+ AnchorReference,
54
+ ]
55
+ end
56
+ end
57
+
58
+ module Child
59
+ # Case: If we have no access to the base class and we need to
60
+ # define polymorphism in the sub-classes.
61
+ class Reference < Lutaml::Model::Serializable
62
+ attribute :name, :string
63
+
64
+ xml do
65
+ map_element "name", to: :name
66
+ end
67
+
68
+ key_value do
69
+ map "name", to: :name
70
+ end
71
+ end
72
+
73
+ class DocumentReference < Reference
74
+ attribute :_class, :string
75
+ attribute :document_id, :string
76
+
77
+ xml do
78
+ map_element "document_id", to: :document_id
79
+ map_attribute "_class", to: :_class
80
+ end
81
+
82
+ key_value do
83
+ map "document_id", to: :document_id
84
+ map "_class", to: :_class
85
+ end
86
+ end
87
+
88
+ class AnchorReference < Reference
89
+ attribute :_class, :string
90
+ attribute :anchor_id, :string
91
+
92
+ xml do
93
+ map_element "anchor_id", to: :anchor_id
94
+ map_attribute "_class", to: :_class
95
+ end
96
+
97
+ key_value do
98
+ map "anchor_id", to: :anchor_id
99
+ map "_class", to: :_class
100
+ end
101
+ end
102
+
103
+ class ReferenceSet < Lutaml::Model::Serializable
104
+ attribute :references, Reference, collection: true, polymorphic: [
105
+ DocumentReference,
106
+ AnchorReference,
107
+ ]
108
+
109
+ xml do
110
+ root "ReferenceSet"
111
+
112
+ map_element "references", to: :references, polymorphic: {
113
+ # This refers to the attribute in the polymorphic model, you need
114
+ # to specify the attribute name (which is specified in the sub-classed model).
115
+ attribute: "_class",
116
+ class_map: {
117
+ "document-ref" => "PolymorphicSpec::Child::DocumentReference",
118
+ "anchor-ref" => "PolymorphicSpec::Child::AnchorReference",
119
+ },
120
+ }
121
+ end
122
+
123
+ key_value do
124
+ map "references", to: :references, polymorphic: {
125
+ attribute: "_class",
126
+ class_map: {
127
+ "Document" => "PolymorphicSpec::Child::DocumentReference",
128
+ "Anchor" => "PolymorphicSpec::Child::AnchorReference",
129
+ },
130
+ }
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ RSpec.describe "Polymorphic" do
137
+ context "when defined in base class" do
138
+ context "when key_value formats" do
139
+ let(:reference_set) do
140
+ PolymorphicSpec::Base::ReferenceSet.new(
141
+ references: [
142
+ PolymorphicSpec::Base::DocumentReference.new(
143
+ _class: "Document",
144
+ document_id: "book:tbtd",
145
+ name: "The Tibetan Book of the Dead",
146
+ ),
147
+ PolymorphicSpec::Base::AnchorReference.new(
148
+ _class: "Anchor",
149
+ anchor_id: "book:tbtd:anchor-1",
150
+ name: "Chapter 1",
151
+ ),
152
+ ],
153
+ )
154
+ end
155
+
156
+ let(:yaml) do
157
+ <<~YAML
158
+ ---
159
+ references:
160
+ - _class: Document
161
+ name: The Tibetan Book of the Dead
162
+ document_id: book:tbtd
163
+ - _class: Anchor
164
+ name: Chapter 1
165
+ anchor_id: book:tbtd:anchor-1
166
+ YAML
167
+ end
168
+
169
+ let(:parsed_yaml) do
170
+ PolymorphicSpec::Base::ReferenceSet.from_yaml(yaml)
171
+ end
172
+
173
+ it "deserializes correctly" do
174
+ expect(parsed_yaml).to eq(reference_set)
175
+ end
176
+
177
+ it "serializes correctly" do
178
+ expect(parsed_yaml.to_yaml).to eq(yaml)
179
+ end
180
+ end
181
+
182
+ context "when XML format" do
183
+ let(:reference_set) do
184
+ PolymorphicSpec::Base::ReferenceSet.new(
185
+ references: [
186
+ PolymorphicSpec::Base::DocumentReference.new(
187
+ _class: "document-ref",
188
+ document_id: "book:tbtd",
189
+ name: "The Tibetan Book of the Dead",
190
+ ),
191
+ PolymorphicSpec::Base::AnchorReference.new(
192
+ _class: "anchor-ref",
193
+ anchor_id: "book:tbtd:anchor-1",
194
+ name: "Chapter 1",
195
+ ),
196
+ ],
197
+ )
198
+ end
199
+
200
+ let(:xml) do
201
+ <<~XML
202
+ <ReferenceSet>
203
+ <references reference-type="document-ref">
204
+ <name>The Tibetan Book of the Dead</name>
205
+ <document_id>book:tbtd</document_id>
206
+ </references>
207
+ <references reference-type="anchor-ref">
208
+ <name>Chapter 1</name>
209
+ <anchor_id>book:tbtd:anchor-1</anchor_id>
210
+ </references>
211
+ </ReferenceSet>
212
+ XML
213
+ end
214
+
215
+ let(:parsed_xml) do
216
+ PolymorphicSpec::Base::ReferenceSet.from_xml(xml)
217
+ end
218
+
219
+ it "deserializes correctly" do
220
+ expect(parsed_xml).to eq(reference_set)
221
+ end
222
+
223
+ it "serializes correctly" do
224
+ expect(parsed_xml.to_xml.strip).to be_equivalent_to(xml.strip)
225
+ end
226
+ end
227
+ end
228
+
229
+ context "when defined in child class" do
230
+ context "when key_value formats" do
231
+ let(:reference_set) do
232
+ PolymorphicSpec::Base::ReferenceSet.new(
233
+ references: [
234
+ PolymorphicSpec::Base::DocumentReference.new(
235
+ _class: "Document",
236
+ document_id: "book:tbtd",
237
+ name: "The Tibetan Book of the Dead",
238
+ ),
239
+ PolymorphicSpec::Base::AnchorReference.new(
240
+ _class: "Anchor",
241
+ anchor_id: "book:tbtd:anchor-1",
242
+ name: "Chapter 1",
243
+ ),
244
+ ],
245
+ )
246
+ end
247
+
248
+ let(:yaml) do
249
+ <<~YAML
250
+ ---
251
+ references:
252
+ - _class: Document
253
+ name: The Tibetan Book of the Dead
254
+ document_id: book:tbtd
255
+ - _class: Anchor
256
+ name: Chapter 1
257
+ anchor_id: book:tbtd:anchor-1
258
+ YAML
259
+ end
260
+
261
+ let(:parsed_yaml) do
262
+ PolymorphicSpec::Base::ReferenceSet.from_yaml(yaml)
263
+ end
264
+
265
+ it "deserializes correctly" do
266
+ expect(parsed_yaml).to eq(reference_set)
267
+ end
268
+
269
+ it "serializes correctly" do
270
+ expect(parsed_yaml.to_yaml).to eq(yaml)
271
+ end
272
+ end
273
+
274
+ context "when XML format" do
275
+ let(:reference_set) do
276
+ PolymorphicSpec::Child::ReferenceSet.new(
277
+ references: [
278
+ PolymorphicSpec::Child::DocumentReference.new(
279
+ _class: "document-ref",
280
+ document_id: "book:tbtd",
281
+ name: "The Tibetan Book of the Dead",
282
+ ),
283
+ PolymorphicSpec::Child::AnchorReference.new(
284
+ _class: "anchor-ref",
285
+ anchor_id: "book:tbtd:anchor-1",
286
+ name: "Chapter 1",
287
+ ),
288
+ ],
289
+ )
290
+ end
291
+
292
+ let(:invalid_ref) do
293
+ PolymorphicSpec::Child::ReferenceSet.new(
294
+ references: [
295
+ PolymorphicSpec::Child::DocumentReference.new(
296
+ _class: "document-ref",
297
+ document_id: "book:tbtd",
298
+ name: "The Tibetan Book of the Dead",
299
+ ),
300
+ PolymorphicSpec::Base::AnchorReference.new(
301
+ _class: "anchor-ref",
302
+ anchor_id: "book:tbtd:anchor-1",
303
+ name: "Chapter 1",
304
+ ),
305
+ ],
306
+ )
307
+ end
308
+
309
+ let(:xml) do
310
+ <<~XML
311
+ <ReferenceSet>
312
+ <references _class="document-ref">
313
+ <name>The Tibetan Book of the Dead</name>
314
+ <document_id>book:tbtd</document_id>
315
+ </references>
316
+ <references _class="anchor-ref">
317
+ <name>Chapter 1</name>
318
+ <anchor_id>book:tbtd:anchor-1</anchor_id>
319
+ </references>
320
+ </ReferenceSet>
321
+ XML
322
+ end
323
+
324
+ let(:parsed_xml) do
325
+ PolymorphicSpec::Child::ReferenceSet.from_xml(xml)
326
+ end
327
+
328
+ it "deserializes correctly" do
329
+ expect(parsed_xml).to eq(reference_set)
330
+ end
331
+
332
+ it "serializes correctly" do
333
+ expect(parsed_xml.to_xml.strip).to be_equivalent_to(xml.strip)
334
+ end
335
+
336
+ it "does not raises error for valid polymorphic class" do
337
+ expect { reference_set.validate! }.not_to raise_error
338
+ end
339
+
340
+ it "raises error if polymorphic class is not in list" do
341
+ expect { invalid_ref.validate! }.to raise_error(Lutaml::Model::ValidationError) do |error|
342
+ expect(error).to include(Lutaml::Model::PolymorphicError)
343
+ expect(error.error_messages).to include("PolymorphicSpec::Base::AnchorReference not in [PolymorphicSpec::Child::DocumentReference, PolymorphicSpec::Child::AnchorReference]")
344
+ end
345
+ end
346
+ end
347
+ end
348
+ 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