lutaml-model 0.5.4 → 0.6.0

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +58 -21
  3. data/Gemfile +1 -0
  4. data/README.adoc +1112 -264
  5. data/lib/lutaml/model/attribute.rb +33 -10
  6. data/lib/lutaml/model/choice.rb +56 -0
  7. data/lib/lutaml/model/config.rb +1 -0
  8. data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
  9. data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
  10. data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
  11. data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
  12. data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
  13. data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
  14. data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
  15. data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
  16. data/lib/lutaml/model/error.rb +8 -0
  17. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
  18. data/lib/lutaml/model/key_value_mapping.rb +3 -1
  19. data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
  20. data/lib/lutaml/model/liquefiable.rb +59 -0
  21. data/lib/lutaml/model/mapping_hash.rb +1 -1
  22. data/lib/lutaml/model/mapping_rule.rb +15 -2
  23. data/lib/lutaml/model/schema/xml_compiler.rb +68 -26
  24. data/lib/lutaml/model/schema_location.rb +7 -0
  25. data/lib/lutaml/model/sequence.rb +71 -0
  26. data/lib/lutaml/model/serialize.rb +125 -35
  27. data/lib/lutaml/model/type/decimal.rb +0 -4
  28. data/lib/lutaml/model/type/time.rb +3 -3
  29. data/lib/lutaml/model/utils.rb +19 -15
  30. data/lib/lutaml/model/validation.rb +12 -1
  31. data/lib/lutaml/model/version.rb +1 -1
  32. data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
  33. data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
  34. data/lib/lutaml/model/xml_adapter/element.rb +32 -0
  35. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +8 -8
  36. data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
  37. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
  38. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
  39. data/lib/lutaml/model/xml_adapter/xml_document.rb +74 -13
  40. data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
  41. data/lib/lutaml/model/xml_mapping.rb +49 -7
  42. data/lib/lutaml/model/xml_mapping_rule.rb +8 -3
  43. data/lib/lutaml/model.rb +1 -0
  44. data/lutaml-model.gemspec +5 -0
  45. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
  46. data/spec/ceramic_spec.rb +39 -0
  47. data/spec/fixtures/ceramic.rb +23 -0
  48. data/spec/fixtures/xml/address_example_260.xsd +9 -0
  49. data/spec/fixtures/xml/user.xsd +10 -0
  50. data/spec/lutaml/model/cdata_spec.rb +4 -5
  51. data/spec/lutaml/model/choice_spec.rb +168 -0
  52. data/spec/lutaml/model/collection_spec.rb +1 -1
  53. data/spec/lutaml/model/custom_model_spec.rb +6 -7
  54. data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
  55. data/spec/lutaml/model/defaults_spec.rb +3 -1
  56. data/spec/lutaml/model/delegation_spec.rb +7 -5
  57. data/spec/lutaml/model/enum_spec.rb +35 -0
  58. data/spec/lutaml/model/group_spec.rb +160 -0
  59. data/spec/lutaml/model/inheritance_spec.rb +25 -0
  60. data/spec/lutaml/model/liquefiable_spec.rb +121 -0
  61. data/spec/lutaml/model/mixed_content_spec.rb +80 -41
  62. data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
  63. data/spec/lutaml/model/schema/xml_compiler_spec.rb +218 -25
  64. data/spec/lutaml/model/sequence_spec.rb +216 -0
  65. data/spec/lutaml/model/transformation_spec.rb +230 -0
  66. data/spec/lutaml/model/type_spec.rb +138 -31
  67. data/spec/lutaml/model/utils_spec.rb +32 -0
  68. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
  69. data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
  70. data/spec/lutaml/model/xml_mapping_spec.rb +143 -112
  71. metadata +67 -2
@@ -1,7 +1,6 @@
1
1
  require "spec_helper"
2
2
  require "lutaml/model/schema"
3
3
  require "lutaml/xsd"
4
- require "tmpdir"
5
4
 
6
5
  RSpec.describe Lutaml::Model::Schema::XmlCompiler do
7
6
  describe ".to_models" do
@@ -25,8 +24,8 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
25
24
  end
26
25
 
27
26
  Dir.mktmpdir do |dir|
28
- it "generates the models, requires them, and tests them with valid and invalid xml" do
29
- described_class.to_models(File.read("spec/fixtures/xml/test_schema.xsd"), output_dir: dir)
27
+ it "creates the model files, requires them, and tests them with valid and invalid xml" do
28
+ described_class.to_models(File.read("spec/fixtures/xml/test_schema.xsd"), output_dir: dir, create_files: true)
30
29
  expect(File).to exist("#{dir}/ct_math_test.rb")
31
30
  expect(File).to exist("#{dir}/st_integer255.rb")
32
31
  expect(File).to exist("#{dir}/long.rb")
@@ -38,20 +37,25 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
38
37
  end
39
38
  end
40
39
 
41
- context "when processing examples from classes generated by valid xml schema" do
40
+ context "when processing examples from classes/files generated by valid xml schema" do
42
41
  Dir.mktmpdir do |dir|
43
42
  before do
44
43
  described_class.to_models(
45
44
  File.read("spec/fixtures/xml/math_document_schema.xsd"),
46
45
  output_dir: dir,
46
+ create_files: true,
47
47
  )
48
48
  require_relative "#{dir}/math_document"
49
49
  end
50
50
 
51
- let(:valid_example) { File.read("spec/fixtures/xml/valid_math_document.xml") }
52
- let(:invalid_example) { File.read("spec/fixtures/xml/invalid_math_document.xml") }
51
+ let(:valid_example) do
52
+ File.read("spec/fixtures/xml/valid_math_document.xml")
53
+ end
54
+ let(:invalid_example) do
55
+ File.read("spec/fixtures/xml/invalid_math_document.xml")
56
+ end
53
57
 
54
- it "does not raise error with valid example" do
58
+ it "does not raise error with valid example and creates files" do
55
59
  expect(defined?(MathDocument)).to eq("constant")
56
60
  parsed = MathDocument.from_xml(valid_example)
57
61
  expect(parsed.title).to eql("Example Title")
@@ -65,6 +69,100 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
65
69
  end
66
70
  end
67
71
  end
72
+
73
+ context "when processing example from lutaml-model#260" do
74
+ Dir.mktmpdir do |dir|
75
+ before do
76
+ described_class.to_models(
77
+ File.read("spec/fixtures/xml/address_example_260.xsd"),
78
+ output_dir: dir,
79
+ create_files: true,
80
+ )
81
+ require_relative "#{dir}/address"
82
+ end
83
+
84
+ let(:address) do
85
+ <<~ADD
86
+ <Address>
87
+ Oxford Street
88
+ <City>London</City>
89
+ <ZIP>E1 6AN</ZIP>
90
+ </Address>
91
+ ADD
92
+ end
93
+
94
+ it "matches parsed xml with input" do
95
+ expect(defined?(Address)).to eq("constant")
96
+ expect(Address.from_xml(address).to_xml).to be_equivalent_to(address)
97
+ end
98
+ end
99
+ end
100
+
101
+ context "when classes are generated but files are not created" do
102
+ let(:schema_classes_hash) do
103
+ described_class.to_models(
104
+ File.read("spec/fixtures/xml/user.xsd"),
105
+ )
106
+ end
107
+
108
+ let(:expected_classes) do
109
+ [
110
+ "NonNegativeInteger",
111
+ "PositiveInteger",
112
+ "Base64Binary",
113
+ "UnsignedLong",
114
+ "UnsignedInt",
115
+ "HexBinary",
116
+ "Token",
117
+ "Long",
118
+ "User",
119
+ ]
120
+ end
121
+
122
+ it "matches the expected class names of the schema" do
123
+ expect(schema_classes_hash.keys).to eql(expected_classes)
124
+ end
125
+ end
126
+
127
+ context "when classes are generated and loaded but files are not created" do
128
+ before do
129
+ described_class.to_models(
130
+ File.read("spec/fixtures/xml/user.xsd"),
131
+ load_classes: true,
132
+ )
133
+ end
134
+
135
+ let(:expected_classes) do
136
+ %w[
137
+ NonNegativeInteger
138
+ PositiveInteger
139
+ Base64Binary
140
+ UnsignedLong
141
+ UnsignedInt
142
+ HexBinary
143
+ Token
144
+ Long
145
+ User
146
+ ]
147
+ end
148
+
149
+ let(:xml) do
150
+ <<~XML
151
+ <User>
152
+ <id>1112</id>
153
+ <age>29</age>
154
+ <token>u9dId901dp13f</token>
155
+ </User>
156
+ XML
157
+ end
158
+
159
+ it "matches the expected class names of the schema" do
160
+ expected_classes.each do |klass|
161
+ expect(be_const_defined(klass)).to be_truthy
162
+ end
163
+ expect(User.from_xml(xml).to_xml).to be_equivalent_to(xml)
164
+ end
165
+ end
68
166
  end
69
167
 
70
168
  describe "structure setup methods" do
@@ -136,7 +234,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
136
234
  <xsd:attribute name="test_attribute" type="xsd:string"/>
137
235
  <xsd:group name="test_group"/>
138
236
  <xsd:simpleType name="test_simple_type"/>
139
- <xsd:complexType name="test_complex_type"/>
237
+ <xsd:complexType name="test_complex_type" mixed="true"/>
140
238
  <xsd:attributeGroup name="test_attribute_group"/>
141
239
  </xsd:schema>
142
240
  XSD
@@ -148,7 +246,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
148
246
  attributes: { "test_attribute" => { name: "test_attribute", base_class: "xsd:string" } },
149
247
  group_types: { "test_group" => {} },
150
248
  simple_types: { "test_simple_type" => {} },
151
- complex_types: { "test_complex_type" => {} },
249
+ complex_types: { "test_complex_type" => { mixed: true } },
152
250
  attribute_groups: { "test_attribute_group" => {} },
153
251
  }
154
252
  end
@@ -260,7 +358,15 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
260
358
  ct.attribute_group = [Lutaml::Xsd::AttributeGroup.new(name: "test_attribute_group")]
261
359
  ct.group = [Lutaml::Xsd::Group.new(name: "test_group")]
262
360
  ct.simple_content = [Lutaml::Xsd::SimpleContent.new(name: "test_simple_content")]
263
- ct.element_order = ["attribute", "sequence", "choice", "complex_content", "attribute_group", "group", "simple_content"]
361
+ ct.element_order = create_pattern_mapping([
362
+ ["Element", "attribute"],
363
+ ["Element", "sequence"],
364
+ ["Element", "choice"],
365
+ ["Element", "complex_content"],
366
+ ["Element", "attribute_group"],
367
+ ["Element", "group"],
368
+ ["Element", "simple_content"],
369
+ ])
264
370
  end
265
371
  end
266
372
 
@@ -272,6 +378,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
272
378
  complex_content: {},
273
379
  attribute_groups: [{}],
274
380
  group: {},
381
+ mixed: false,
275
382
  simple_content: nil,
276
383
  }
277
384
  end
@@ -289,7 +396,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
289
396
  end
290
397
 
291
398
  it "initializes the instance variables with empty MappingHash" do
292
- expect(described_class.send(:setup_complex_type, complex_type)).to be_empty
399
+ expect(described_class.send(:setup_complex_type, complex_type)).to eq({ mixed: false })
293
400
  end
294
401
  end
295
402
  end
@@ -299,7 +406,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
299
406
  let(:complex_type) do
300
407
  Lutaml::Xsd::SimpleContent.new.tap do |ct|
301
408
  ct.extension = Lutaml::Xsd::ExtensionSimpleContent.new(base: "test_extension")
302
- ct.element_order = ["extension"]
409
+ ct.element_order = [Lutaml::Model::XmlAdapter::Element.new("Element", "extension")]
303
410
  end
304
411
  end
305
412
 
@@ -314,7 +421,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
314
421
  let(:complex_type) do
315
422
  Lutaml::Xsd::SimpleContent.new.tap do |ct|
316
423
  ct.restriction = Lutaml::Xsd::RestrictionSimpleContent.new(base: "test_restriction")
317
- ct.element_order = ["restriction"]
424
+ ct.element_order = [Lutaml::Model::XmlAdapter::Element.new("Element", "restriction")]
318
425
  end
319
426
  end
320
427
 
@@ -338,7 +445,14 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
338
445
  Lutaml::Xsd::Group.new(name: "test_group"),
339
446
  Lutaml::Xsd::Group.new(ref: "test_ref"),
340
447
  ]
341
- ct.element_order = ["sequence", "group", "element", "choice", "element", "group"]
448
+ ct.element_order = create_pattern_mapping([
449
+ ["Element", "sequence"],
450
+ ["Element", "group"],
451
+ ["Element", "element"],
452
+ ["Element", "choice"],
453
+ ["Element", "element"],
454
+ ["Element", "group"],
455
+ ])
342
456
  end
343
457
  end
344
458
 
@@ -375,7 +489,10 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
375
489
  Lutaml::Xsd::Group.new.tap do |ct|
376
490
  ct.sequence = [Lutaml::Xsd::Sequence.new(name: "test_sequence")]
377
491
  ct.choice = [Lutaml::Xsd::Choice.new(name: "test_choice")]
378
- ct.element_order = ["sequence", "choice"]
492
+ ct.element_order = create_pattern_mapping([
493
+ ["Element", "sequence"],
494
+ ["Element", "choice"],
495
+ ])
379
496
  end
380
497
  end
381
498
 
@@ -407,7 +524,12 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
407
524
  ct.sequence = [Lutaml::Xsd::Sequence.new(name: "test_sequence")]
408
525
  ct.choice = [Lutaml::Xsd::Choice.new(name: "test_choice")]
409
526
  ct.group = [Lutaml::Xsd::Group.new(name: "test_group")]
410
- ct.element_order = ["sequence", "choice", "group", "element"]
527
+ ct.element_order = create_pattern_mapping([
528
+ ["Element", "sequence"],
529
+ ["Element", "choice"],
530
+ ["Element", "group"],
531
+ ["Element", "element"],
532
+ ])
411
533
  end
412
534
  end
413
535
 
@@ -516,7 +638,10 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
516
638
  Lutaml::Xsd::AttributeGroup.new.tap do |attr_group|
517
639
  attr_group.attribute = [Lutaml::Xsd::Attribute.new(name: "test_name", type: "test_type")]
518
640
  attr_group.attribute_group = [Lutaml::Xsd::AttributeGroup.new(name: "test_name", type: "test_type")]
519
- attr_group.element_order = ["attribute", "attribute_group"]
641
+ attr_group.element_order = create_pattern_mapping([
642
+ ["Element", "attribute"],
643
+ ["Element", "attribute_group"],
644
+ ])
520
645
  end
521
646
  end
522
647
 
@@ -622,7 +747,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
622
747
  element.min_occurs = 0
623
748
  element.max_occurs = 1
624
749
  element.complex_type = Lutaml::Xsd::ComplexType.new(name: "test_complex_type")
625
- element.element_order = ["complex_type"]
750
+ element.element_order = [Lutaml::Model::XmlAdapter::Element.new("Element", "complex_type")]
626
751
  end
627
752
  end
628
753
 
@@ -656,11 +781,11 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
656
781
  element.type = "test_type"
657
782
  element.name = "test_name"
658
783
  element.complex_type = Lutaml::Xsd::ComplexType.new(name: "test_complex_type")
659
- element.element_order = ["complex_type"]
784
+ element.element_order = [Lutaml::Model::XmlAdapter::Element.new("Element", "complex_type")]
660
785
  end
661
786
  end
662
787
 
663
- let(:expected_hash) { { type_name: "test_type", element_name: "test_name", complex_type: {} } }
788
+ let(:expected_hash) { { type_name: "test_type", element_name: "test_name", complex_type: { mixed: false } } }
664
789
 
665
790
  it "returns the expected hash" do
666
791
  expect(described_class.send(:setup_element, element)).to eql(expected_hash)
@@ -776,7 +901,12 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
776
901
  ]
777
902
  extension.sequence = Lutaml::Xsd::Sequence.new
778
903
  extension.choice = Lutaml::Xsd::Choice.new
779
- extension.element_order = ["attribute", "sequence", "choice", "attribute"]
904
+ extension.element_order = create_pattern_mapping([
905
+ ["Element", "attribute"],
906
+ ["Element", "sequence"],
907
+ ["Element", "choice"],
908
+ ["Element", "attribute"],
909
+ ])
780
910
  end
781
911
  end
782
912
 
@@ -832,12 +962,17 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
832
962
  context "when given element contains element_order but no instance relevant elements/instances" do
833
963
  let(:element) do
834
964
  Lutaml::Xsd::Element.new.tap do |element|
835
- element.element_order = ["annotation", "simple_type", "complex_type", "key"]
965
+ element.element_order = create_pattern_mapping([
966
+ ["Element", "annotation"],
967
+ ["Element", "simple_type"],
968
+ ["Element", "complex_type"],
969
+ ["Element", "key"],
970
+ ])
836
971
  end
837
972
  end
838
973
 
839
974
  it "raises an error when element_order contains elements that isn't an attribute of the instance" do
840
- element.element_order << "test_element"
975
+ element.element_order << Lutaml::Model::XmlAdapter::Element.new("Element", "test_element")
841
976
  expect { described_class.send(:resolved_element_order, element) }.to raise_error(NoMethodError)
842
977
  end
843
978
 
@@ -854,7 +989,12 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
854
989
  element.simple_type = Lutaml::Xsd::SimpleType.new
855
990
  element.complex_type = Lutaml::Xsd::ComplexType.new
856
991
  element.key = Lutaml::Xsd::Key.new
857
- element.element_order = ["annotation", "simple_type", "complex_type", "key"]
992
+ element.element_order = create_pattern_mapping([
993
+ ["Element", "annotation"],
994
+ ["Element", "simple_type"],
995
+ ["Element", "complex_type"],
996
+ ["Element", "key"],
997
+ ])
858
998
  end
859
999
  end
860
1000
 
@@ -913,6 +1053,24 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
913
1053
  end
914
1054
  end
915
1055
 
1056
+ describe ".resolve_element_class" do
1057
+ context "when element.type_name is one of the standard classes" do
1058
+ described_class::DEFAULT_CLASSES.each do |standard_class|
1059
+ it "returns the element_class value for #{standard_class.capitalize}" do
1060
+ type_name_hash = to_mapping_hash({ type_name: "xsd:#{standard_class}" })
1061
+ expect(described_class.send(:resolve_element_class, type_name_hash)).to eql(":#{standard_class}")
1062
+ end
1063
+ end
1064
+ end
1065
+
1066
+ context "when element.type_name is not one of the standard classes" do
1067
+ it "returns the element_class value" do
1068
+ type_name_hash = to_mapping_hash({ type_name: "test_st_element1" })
1069
+ expect(described_class.send(:resolve_element_class, type_name_hash)).to eql("TestStElement1")
1070
+ end
1071
+ end
1072
+ end
1073
+
916
1074
  describe ".resolve_occurs" do
917
1075
  context "when min_occurs and max_occurs are present" do
918
1076
  it "returns the collection: true" do
@@ -1392,7 +1550,7 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
1392
1550
  end
1393
1551
 
1394
1552
  describe ".required_files_elements" do
1395
- context "when elements are given" do
1553
+ context "when given element's data type is not one of standard class" do
1396
1554
  before do
1397
1555
  described_class.instance_variable_set(:@required_files, [])
1398
1556
  described_class.instance_variable_set(:@elements, elements)
@@ -1421,10 +1579,45 @@ RSpec.describe Lutaml::Model::Schema::XmlCompiler do
1421
1579
  expect(described_class.instance_variable_get(:@required_files)).to eql(["ct_element1", "ct_element2"])
1422
1580
  end
1423
1581
  end
1582
+
1583
+ context "when given element's data type is one of standard class" do
1584
+ before do
1585
+ described_class.instance_variable_set(:@required_files, [])
1586
+ described_class.instance_variable_set(:@elements, elements)
1587
+ end
1588
+
1589
+ after do
1590
+ described_class.instance_variable_set(:@required_files, nil)
1591
+ described_class.instance_variable_set(:@elements, nil)
1592
+ end
1593
+
1594
+ let(:elements) do
1595
+ {
1596
+ "CT_Element1" => to_mapping_hash({ type_name: "w:CT_Element1" }),
1597
+ "CT_Element2" => to_mapping_hash({ type_name: "xsd:string" }),
1598
+ }
1599
+ end
1600
+
1601
+ let(:content) do
1602
+ [
1603
+ to_mapping_hash({ type_name: "CT_Element1" }),
1604
+ to_mapping_hash({ ref_class: "CT_Element2" }),
1605
+ ]
1606
+ end
1607
+
1608
+ it "populates @required_files variable with the names of the required files excluding default classes" do
1609
+ described_class.send(:required_files_elements, content)
1610
+ expect(described_class.instance_variable_get(:@required_files)).to eql(["ct_element1"])
1611
+ end
1612
+ end
1424
1613
  end
1425
1614
  end
1426
1615
  end
1427
1616
 
1617
+ def create_pattern_mapping(array)
1618
+ array.map { |type, text| Lutaml::Model::XmlAdapter::Element.new(type, text) }
1619
+ end
1620
+
1428
1621
  def to_mapping_hash(content)
1429
1622
  @hash ||= Lutaml::Model::MappingHash.new
1430
1623
  @hash.merge(content)
@@ -0,0 +1,216 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module SequenceSpec
5
+ class Ceramic < Lutaml::Model::Serializable
6
+ attribute :id, :string
7
+ attribute :name, :string
8
+ attribute :type, :string
9
+ attribute :color, :string
10
+ attribute :bold, :string
11
+ attribute :text, :string
12
+ attribute :usage, :string
13
+ attribute :size, :string
14
+ attribute :first_name, :string
15
+ attribute :last_name, :string
16
+ attribute :tag, :string
17
+ attribute :temperature, :string
18
+
19
+ xml do
20
+ root "Ceramic"
21
+ map_element :tag, to: :tag
22
+
23
+ sequence do
24
+ map_element :id, to: :id
25
+ map_element :name, to: :name
26
+ map_element :type, to: :type
27
+ map_element :color, to: :color
28
+ map_element :bold, to: :bold
29
+ map_element :text, to: :text
30
+ sequence do
31
+ map_element :usage, to: :usage
32
+ map_element :size, to: :size
33
+ end
34
+ end
35
+
36
+ sequence do
37
+ map_element :first_name, to: :first_name
38
+ map_element :last_name, to: :last_name
39
+ end
40
+
41
+ map_element :temperature, to: :temperature
42
+ end
43
+ end
44
+
45
+ class CeramicCollection < Lutaml::Model::Serializable
46
+ attribute :ceramic, Ceramic, collection: 1..2
47
+
48
+ xml do
49
+ root "collection"
50
+ map_element "ceramic", to: :ceramic
51
+ end
52
+ end
53
+ end
54
+
55
+ RSpec.describe "Sequence" do
56
+ context "with nesting sequence" do
57
+ let(:mapper) { SequenceSpec::Ceramic }
58
+
59
+ it "don't raise error for a valid instance, if given attribute for sequence has correct order" do
60
+ xml = <<~XML
61
+ <Ceramic>
62
+ <tag>Nik</tag>
63
+ <id>1</id>
64
+ <name>Vase</name>
65
+ <type>Decorative</type>
66
+ <color>Blue</color>
67
+ <bold>Heading</bold>
68
+ <text>Header</text>
69
+ <usage>Indoor</usage>
70
+ <size>Medium</size>
71
+ <first_name>Dale</first_name>
72
+ <last_name>Steyn</last_name>
73
+ <temperature>Normal</temperature>
74
+ </Ceramic>
75
+ XML
76
+
77
+ expect { mapper.from_xml(xml) }.not_to raise_error
78
+ end
79
+
80
+ it "don't raise error for a valid instance with sequence range, if given attribute for sequence has correct order" do
81
+ xml = <<~XML
82
+ <collection>
83
+ <ceramic>
84
+ <tag>Nik</tag>
85
+ <id>1</id>
86
+ <name>Vase</name>
87
+ <type>Decorative</type>
88
+ <color>Blue</color>
89
+ <bold>Heading</bold>
90
+ <text>Header</text>
91
+ <usage>Indoor</usage>
92
+ <size>Medium</size>
93
+ <first_name>Dale</first_name>
94
+ <last_name>Steyn</last_name>
95
+ <temperature>Normal</temperature>
96
+ </ceramic>
97
+ <ceramic>
98
+ <tag>Nik</tag>
99
+ <id>1</id>
100
+ <name>Vase</name>
101
+ <type>Decorative</type>
102
+ <color>Blue</color>
103
+ <bold>Heading</bold>
104
+ <text>Header</text>
105
+ <usage>Indoor</usage>
106
+ <size>Medium</size>
107
+ <first_name>Dale</first_name>
108
+ <last_name>Steyn</last_name>
109
+ <temperature>Normal</temperature>
110
+ </ceramic>
111
+ </collection>
112
+ XML
113
+
114
+ expect do
115
+ SequenceSpec::CeramicCollection.from_xml(xml)
116
+ end.not_to raise_error
117
+ end
118
+
119
+ it "raises error, if given attributes order is incorrect in sequence" do
120
+ xml = <<~XML
121
+ <Ceramic>
122
+ <tag>Nik</tag>
123
+ <temperature>High</temperature>
124
+ <first_name>Micheal</first_name>
125
+ <id>1</id>
126
+ <name>Vase</name>
127
+ <type>Decorative</type>
128
+ <color>Blue</color>
129
+ <bold>Heading</bold>
130
+ <usage>Indoor</usage>
131
+ <size>Medium</size>
132
+ <last_name>Johnson</last_name>
133
+ <text>Header</text>
134
+ </Ceramic>
135
+ XML
136
+
137
+ expect do
138
+ mapper.from_xml(xml)
139
+ end.to raise_error(Lutaml::Model::IncorrectSequenceError) do |error|
140
+ expect(error.message).to eq("Element `usage` does not match the expected sequence order element `text`")
141
+ end
142
+ end
143
+
144
+ it "raises error with sequence range, if given attributes order is incorrect in sequence" do
145
+ invalid_xml = <<~XML
146
+ <collection>
147
+ <ceramic>
148
+ <id>1</id>
149
+ <name>Vase</name>
150
+ <type>Decorative</type>
151
+ <color>Blue</color>
152
+ <bold>Heading</bold>
153
+ <text>Header</text>
154
+ <usage>Indoor</usage>
155
+ <size>Medium</size>
156
+ <first_name>Dale</first_name>
157
+ <last_name>Steyn</last_name>
158
+ <temperature>Normal</temperature>
159
+ <tag>Nik</tag>
160
+ </ceramic>
161
+
162
+ <ceramic>
163
+ <id>2</id>
164
+ <name>Nick</name>
165
+ <type>Unique</type>
166
+ <color>Red</color>
167
+ <bold>Name</bold>
168
+ <text>Body</text>
169
+ <usage>Outdoor</usage>
170
+ <size>Small</size>
171
+ <first_name>Smith</first_name>
172
+ <last_name>Ash</last_name>
173
+ <temperature>High</temperature>
174
+ <tag>Adid</tag>
175
+ </ceramic>
176
+
177
+ <ceramic>
178
+ <id>3</id>
179
+ <name>Starc</name>
180
+ <type>Int</type>
181
+ <color>White</color>
182
+ <bold>Act</bold>
183
+ <text>Footer</text>
184
+ <usage>Nothing</usage>
185
+ <size>Large</size>
186
+ <first_name>Dale</first_name>
187
+ <last_name>Steyn</last_name>
188
+ <temperature>Normal</temperature>
189
+ <tag>Bet</tag>
190
+ </ceramic>
191
+ </collection>
192
+ XML
193
+
194
+ expect do
195
+ SequenceSpec::CeramicCollection.from_xml(invalid_xml).validate!
196
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
197
+ expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
198
+ expect(error.error_messages).to eq(["ceramic count is 3, must be between 1 and 2"])
199
+ end
200
+ end
201
+
202
+ it "raises error, if mapping other map_element are defined in sequence" do
203
+ expect do
204
+ Class.new(Lutaml::Model::Serializable) do
205
+ attribute :type, :string
206
+
207
+ xml do
208
+ sequence do
209
+ map_attribute :type, to: :type
210
+ end
211
+ end
212
+ end
213
+ end.to raise_error(Lutaml::Model::UnknownSequenceMappingError, "map_attribute is not allowed in sequence")
214
+ end
215
+ end
216
+ end