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
@@ -3,14 +3,19 @@ require "lutaml/model"
3
3
 
4
4
  module GroupSpec
5
5
  class Ceramic < Lutaml::Model::Serializable
6
- attribute :type, :string
7
- attribute :name, :string
6
+ attribute :type, :string, default: "Data"
7
+ attribute :name, :string, default: "Starc"
8
8
 
9
9
  xml do
10
10
  no_root
11
11
  map_element :type, to: :type
12
12
  map_element :name, to: :name
13
13
  end
14
+
15
+ key_value do
16
+ map :type, to: :type
17
+ map :name, to: :name
18
+ end
14
19
  end
15
20
 
16
21
  class CeramicCollection < Lutaml::Model::Serializable
@@ -75,9 +80,139 @@ module GroupSpec
75
80
  map_element :name, to: :name
76
81
  end
77
82
  end
83
+
84
+ class CommonAttributes < Lutaml::Model::Serializable
85
+ choice do
86
+ attribute :mstyle, :string
87
+ attribute :mcol, :string
88
+ attribute :mr, :string
89
+ end
90
+
91
+ xml do
92
+ no_root
93
+ sequence do
94
+ map_element :mstyle, to: :mstyle
95
+ map_element :mr, to: :mr
96
+ end
97
+ map_attribute :mcol, to: :mcol
98
+ end
99
+ end
100
+
101
+ class Mrow < Lutaml::Model::Serializable
102
+ attribute :mi, :string
103
+ import_model CommonAttributes
104
+
105
+ xml do
106
+ root "mrow"
107
+ map_element :mi, to: :mi
108
+ end
109
+
110
+ import_model GroupOfItems
111
+
112
+ key_value do
113
+ map :mcol, to: :mcol
114
+ end
115
+
116
+ import_model Ceramic
117
+ end
118
+
119
+ class Mfrac < Lutaml::Model::Serializable
120
+ attribute :num, :string
121
+ import_model CommonAttributes
122
+
123
+ xml do
124
+ root "mfrac"
125
+ map_element :num, to: :num
126
+ end
127
+ end
128
+
129
+ class ContributionInfo < Lutaml::Model::Serializable
130
+ attribute :person, :string
131
+ attribute :organization, :string
132
+
133
+ xml do
134
+ no_root
135
+ map_element "person", to: :person
136
+ map_element "organization", to: :organization
137
+ end
138
+ end
139
+
140
+ class Contributor < Lutaml::Model::Serializable
141
+ attribute :role, :string
142
+ import_model_attributes ContributionInfo
143
+
144
+ xml do
145
+ root "contributor"
146
+ map_element "role", to: :role
147
+ map_element "person", to: :person
148
+ map_element "organization", to: :organization
149
+ end
150
+ end
151
+
152
+ class Identifier < Lutaml::Model::Serializable
153
+ attribute :id, :string
154
+ attribute :remarks, :string, collection: true
155
+ attribute :remark_items, :string, collection: true
156
+
157
+ key_value do
158
+ map "id", to: :id
159
+ map "remarks", to: :remarks
160
+ map "remark_items", to: :remark_items
161
+ end
162
+ end
163
+
164
+ class ModelElement < Lutaml::Model::Serializable
165
+ import_model Identifier
166
+ end
78
167
  end
79
168
 
80
169
  RSpec.describe "Group" do
170
+ context "when serializing and deserializing import model having no_root" do
171
+ let(:xml) do
172
+ <<~XML
173
+ <mrow xmlns:ex1="http://www.example.com" xmlns:GML="http://www.sparxsystems.com/profiles/GML/1.0">
174
+ <mstyle>italic</mstyle>
175
+ <mr>y</mr>
176
+ <mi>x</mi>
177
+ <name>Smith</name>
178
+ <type>product</type>
179
+ <GML:description>Item</GML:description>
180
+ </mrow>
181
+ XML
182
+ end
183
+
184
+ let(:input_xml) do
185
+ <<~XML
186
+ <contributor>
187
+ <role>author</role>
188
+ <person>John Doe</person>
189
+ <organization>ACME</organization>
190
+ </contributor>
191
+ XML
192
+ end
193
+
194
+ it "parse the imported model correctly" do
195
+ parsed = GroupSpec::Mrow.from_xml(xml)
196
+ expect(parsed.mi).to eq("x")
197
+ expect(parsed.mstyle).to eq("italic")
198
+ expect(parsed.name).to eq("Smith")
199
+ expect(parsed.type).to eq("product")
200
+ expect(parsed.description).to eq("Item")
201
+ end
202
+
203
+ it "parse the imported model attributes correctly" do
204
+ parsed = GroupSpec::Contributor.from_xml(input_xml)
205
+ expect(parsed.person).to eq("John Doe")
206
+ expect(parsed.role).to eq("author")
207
+ expect(parsed.organization).to eq("ACME")
208
+ end
209
+
210
+ it "serialize the imported model correctly" do
211
+ instance = GroupSpec::Mrow.new(mi: "x", mstyle: "italic", mr: "y", name: "Smith", type: "product", description: "Item")
212
+ expect(instance.to_xml).to be_equivalent_to(xml)
213
+ end
214
+ end
215
+
81
216
  context "with no_root" do
82
217
  let(:mapper) { GroupSpec::CeramicCollection }
83
218
 
@@ -116,26 +251,193 @@ RSpec.describe "Group" do
116
251
  end
117
252
 
118
253
  context "with model" do
119
- it "import attributes" do
120
- expect(GroupSpec::ComplexType.attributes).to include(GroupSpec::GroupOfItems.attributes)
254
+ shared_examples "imports attributes from" do |source_class, target_class|
255
+ it "#{source_class.name} correctly" do
256
+ source_attributes = source_class.attributes
257
+ target_attributes = target_class.attributes
258
+
259
+ source_attributes.each do |name, attr|
260
+ expect(target_attributes[name].name).to eq(attr.name)
261
+ expect(target_attributes[name].type).to eq(attr.type)
262
+ expect(target_attributes[name].options).to eq(attr.options)
263
+ end
264
+ end
121
265
  end
122
266
 
123
- it "import mappings in xml block" do
124
- expect(GroupSpec::ComplexType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
267
+ shared_examples "imports mappings from" do |source_class, target_class|
268
+ it "#{source_class.name} correctly" do
269
+ source_elements = source_class.mappings_for(:xml).elements
270
+ target_elements = target_class.mappings_for(:xml).elements
271
+
272
+ source_elements.each do |element|
273
+ matching_element = target_elements.find { |e| e.name == element.name }
274
+ expect(matching_element).not_to be_nil
275
+ expect(matching_element.to).to eq(element.to)
276
+ end
277
+ end
278
+ end
279
+
280
+ describe GroupSpec::ComplexType do
281
+ it_behaves_like "imports attributes from", GroupSpec::GroupOfItems, described_class
282
+ it_behaves_like "imports mappings from", GroupSpec::GroupOfItems, described_class
283
+ end
284
+
285
+ describe GroupSpec::GenericType do
286
+ it_behaves_like "imports mappings from", GroupSpec::GroupOfItems, described_class
287
+ end
288
+
289
+ describe GroupSpec::SimpleType do
290
+ it_behaves_like "imports attributes from", GroupSpec::GroupOfItems, described_class
291
+ it_behaves_like "imports mappings from", GroupSpec::GroupOfItems, described_class
292
+ end
293
+
294
+ describe GroupSpec::ModelElement do
295
+ it_behaves_like "imports attributes from", GroupSpec::Identifier, described_class
296
+ it_behaves_like "imports mappings from", GroupSpec::Identifier, described_class
297
+ end
298
+
299
+ describe GroupSpec::Mrow do
300
+ it_behaves_like "imports attributes from", GroupSpec::CommonAttributes, described_class
301
+ it_behaves_like "imports attributes from", GroupSpec::Ceramic, described_class
302
+ it_behaves_like "imports mappings from", GroupSpec::CommonAttributes, described_class
303
+ it_behaves_like "imports mappings from", GroupSpec::Ceramic, described_class
304
+ end
305
+
306
+ describe GroupSpec::Contributor do
307
+ it_behaves_like "imports attributes from", GroupSpec::ContributionInfo, described_class
125
308
  end
126
309
 
127
- it "import mappings outside xml block" do
128
- expect(GroupSpec::GenericType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
310
+ context "when importing multiple models with overlapping attributes" do
311
+ let(:mrow_instance) do
312
+ GroupSpec::Mrow.new
313
+ end
314
+
315
+ it "uses Ceramic's default values for overlapping attributes" do
316
+ expect(mrow_instance.type).to eq("Data")
317
+ expect(mrow_instance.name).to eq("Starc")
318
+ end
319
+
320
+ it "maintains the correct XML serialization order from last import" do
321
+ xml = mrow_instance.to_xml
322
+ expected_xml = "<mrow xmlns:ex1='http://www.example.com' xmlns:GML='http://www.sparxsystems.com/profiles/GML/1.0'/>"
323
+
324
+ expect(xml).to be_equivalent_to(expected_xml)
325
+ end
129
326
  end
130
327
 
131
- it "import attributes and mappings in xml block" do
132
- expect(GroupSpec::ComplexType.attributes).to include(GroupSpec::GroupOfItems.attributes)
133
- expect(GroupSpec::ComplexType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
328
+ context "when update the imported attribute" do
329
+ it "updates the attribute `mstyle` only in `Mrow`" do
330
+ GroupSpec::Mrow.attributes[:mstyle].instance_variable_set(:@type, :integer)
331
+ expect(GroupSpec::Mrow.attributes[:mstyle].type).to eq(:integer)
332
+ end
333
+
334
+ it "maintains original type for the attribute `mstyle` in `Mfrac`" do
335
+ expect(GroupSpec::Mfrac.attributes[:mstyle].type).to eq(Lutaml::Model::Type::String)
336
+ end
337
+
338
+ it "maintains original type for the attribute `mstyle` in importable class `CommonAttributes`" do
339
+ expect(GroupSpec::CommonAttributes.attributes[:mstyle].type).to eq(Lutaml::Model::Type::String)
340
+ end
341
+ end
342
+
343
+ context "when updating imported choice" do
344
+ it "updates choice min/max only in Mrow" do
345
+ choice = GroupSpec::Mrow.choice_attributes.first
346
+ choice.instance_variable_set(:@min, 2)
347
+ choice.instance_variable_set(:@max, 3)
348
+
349
+ expect(choice.min).to eq(2)
350
+ expect(choice.max).to eq(3)
351
+ end
352
+
353
+ it "maintains original choice min/max in Mfrac" do
354
+ choice = GroupSpec::Mfrac.choice_attributes.first
355
+ expect(choice.min).to eq(1)
356
+ expect(choice.max).to eq(1)
357
+ end
358
+
359
+ it "maintains original choice min/max in CommonAttributes" do
360
+ choice = GroupSpec::CommonAttributes.choice_attributes.first
361
+ expect(choice.min).to eq(1)
362
+ expect(choice.max).to eq(1)
363
+ end
134
364
  end
135
365
 
136
- it "import attributes and mappings outside the xml block" do
137
- expect(GroupSpec::SimpleType.attributes).to include(GroupSpec::GroupOfItems.attributes)
138
- expect(GroupSpec::SimpleType.mappings_for(:xml).elements).to include(*GroupSpec::GroupOfItems.mappings_for(:xml).elements)
366
+ context "when updating imported mappings" do
367
+ let(:new_namespace) { "http://www.example.com/new" }
368
+ let(:new_prefix) { "test" }
369
+
370
+ context "with element mappings" do
371
+ it "updates the mapping namespace only in `Mrow`" do
372
+ mapping = GroupSpec::Mrow.mappings_for(:xml).elements.find { |e| e.name == :mstyle }
373
+ mapping.instance_variable_set(:@namespace, new_namespace)
374
+ mapping.instance_variable_set(:@prefix, new_prefix)
375
+
376
+ expect(mapping.namespace).to eq(new_namespace)
377
+ expect(mapping.prefix).to eq(new_prefix)
378
+ end
379
+
380
+ it "maintains original namespace for `mstyle` mapping in `Mfrac`" do
381
+ mapping = GroupSpec::Mfrac.mappings_for(:xml).elements.find { |e| e.name == :mstyle }
382
+ expect(mapping.namespace).to be_nil
383
+ expect(mapping.prefix).to be_nil
384
+ end
385
+
386
+ it "maintains original namespace for `mstyle` mapping in `CommonAttributes`" do
387
+ mapping = GroupSpec::CommonAttributes.mappings_for(:xml).elements.find { |e| e.name == :mstyle }
388
+ expect(mapping.namespace).to be_nil
389
+ expect(mapping.prefix).to be_nil
390
+ end
391
+ end
392
+
393
+ context "with attribute mappings" do
394
+ it "updates attribute mapping only in `Mrow`" do
395
+ mapping = GroupSpec::Mrow.mappings_for(:xml).attributes.find { |a| a.name == :mcol }
396
+ mapping.instance_variable_set(:@namespace, new_namespace)
397
+
398
+ expect(mapping.namespace).to eq(new_namespace)
399
+ end
400
+
401
+ it "maintains original attribute mapping in `Mfrac`" do
402
+ mapping = GroupSpec::Mfrac.mappings_for(:xml).attributes.find { |a| a.name == :mcol }
403
+ expect(mapping.namespace).to be_nil
404
+ end
405
+
406
+ it "maintains original attribute mapping in `CommonAttributes`" do
407
+ mapping = GroupSpec::CommonAttributes.mappings_for(:xml).attributes.find { |a| a.name == :mcol }
408
+ expect(mapping.namespace).to be_nil
409
+ end
410
+ end
411
+
412
+ context "with sequence elements" do
413
+ it "updates sequence elements only in `Mrow`" do
414
+ sequence = GroupSpec::Mrow.mappings_for(:xml).element_sequence.first
415
+ sequence.attributes << Lutaml::Model::XmlMappingRule.new(
416
+ "new_element",
417
+ to: :new_element,
418
+ namespace: "http://example.com",
419
+ prefix: "test",
420
+ )
421
+
422
+ expect(sequence.attributes.map(&:name)).to include("new_element")
423
+ end
424
+
425
+ it "maintains original sequence elements in `Mfrac`" do
426
+ original_sequence = GroupSpec::Mfrac.mappings_for(:xml).element_sequence.first
427
+ expect(original_sequence.attributes.map(&:name)).not_to include("new_element")
428
+ end
429
+
430
+ it "maintains original sequence elements in `CommonAttributes`" do
431
+ original_sequence = GroupSpec::CommonAttributes.mappings_for(:xml).element_sequence.first
432
+ expect(original_sequence.attributes.map(&:name)).not_to include("new_element")
433
+ end
434
+
435
+ it "creates new sequence object with the new mapping object as model of sequence in `Mrow`" do
436
+ mrow_sequence_model = GroupSpec::Mrow.mappings_for(:xml).element_sequence[0].model
437
+ common_attributes_sequence_model = GroupSpec::CommonAttributes.mappings_for(:xml).element_sequence[0].model
438
+ expect(mrow_sequence_model).not_to be(common_attributes_sequence_model)
439
+ end
440
+ end
139
441
  end
140
442
 
141
443
  it "raises error if root is defined on imported class" do
@@ -1,9 +1,7 @@
1
1
  require "spec_helper"
2
- require_relative "../../../lib/lutaml/model/key_value_mapping"
3
- require_relative "../../../lib/lutaml/model/key_value_mapping_rule"
4
2
 
5
3
  RSpec.describe Lutaml::Model::KeyValueMapping do
6
- let(:mapping) { described_class.new }
4
+ let(:mapping) { described_class.new(:json) }
7
5
 
8
6
  context "with delegate option" do
9
7
  before do
@@ -49,6 +47,10 @@ RSpec.describe Lutaml::Model::KeyValueMapping do
49
47
  expect(dup_mapping.object_id).not_to eq(mapping.object_id)
50
48
  end
51
49
 
50
+ it "correctly duplicates mapping with format" do
51
+ expect(mapping.format).to eq(dup_mapping.format)
52
+ end
53
+
52
54
  it "correctly duplicates mapping with `to:`" do
53
55
  m = mapping.mappings[0]
54
56
  dup_m = dup_mapping.mappings[0]
@@ -67,6 +69,8 @@ RSpec.describe Lutaml::Model::KeyValueMapping do
67
69
 
68
70
  expect(m.child_mappings).to eq(dup_m.child_mappings)
69
71
  expect(m.child_mappings.object_id).not_to eq(dup_m.child_mappings.object_id)
72
+
73
+ expect(m.format).to eq(dup_m.format)
70
74
  end
71
75
 
72
76
  it "correctly duplicates mapping with custom methods" do
@@ -98,4 +102,38 @@ RSpec.describe Lutaml::Model::KeyValueMapping do
98
102
  expect(mapping.mappings[0].raw_mapping?).to be true
99
103
  end
100
104
  end
105
+
106
+ describe "validation errors" do
107
+ it "raises error when render_nil is :as_blank" do
108
+ mapping = described_class.new
109
+ expect do
110
+ mapping.map("test", to: :field, render_nil: :as_blank)
111
+ end.to raise_error(
112
+ Lutaml::Model::IncorrectMappingArgumentsError,
113
+ ":as_blank is not supported for key-value mappings",
114
+ )
115
+ end
116
+
117
+ context "with TOML format" do
118
+ let(:mapping) { Lutaml::Model::TomlMapping.new }
119
+
120
+ it "raises error when render_nil is :as_nil" do
121
+ expect do
122
+ mapping.map("test", to: :field, render_nil: :as_nil)
123
+ end.to raise_error(
124
+ Lutaml::Model::IncorrectMappingArgumentsError,
125
+ "nil values are not supported in toml format",
126
+ )
127
+ end
128
+
129
+ it "raises error when render_empty is :as_nil" do
130
+ expect do
131
+ mapping.map("test", to: :field, render_empty: :as_nil)
132
+ end.to raise_error(
133
+ Lutaml::Model::IncorrectMappingArgumentsError,
134
+ "nil values are not supported in toml format",
135
+ )
136
+ end
137
+ end
138
+ end
101
139
  end