lutaml-model 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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