lutaml-model 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +2 -0
  3. data/.rubocop_todo.yml +39 -13
  4. data/Gemfile +1 -0
  5. data/README.adoc +430 -52
  6. data/lib/lutaml/model/constants.rb +7 -0
  7. data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
  8. data/lib/lutaml/model/error.rb +1 -0
  9. data/lib/lutaml/model/key_value_mapping.rb +31 -2
  10. data/lib/lutaml/model/mapping_hash.rb +8 -0
  11. data/lib/lutaml/model/mapping_rule.rb +8 -0
  12. data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
  13. data/lib/lutaml/model/schema/xml_compiler.rb +720 -0
  14. data/lib/lutaml/model/schema.rb +5 -0
  15. data/lib/lutaml/model/serialize.rb +33 -13
  16. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
  17. data/lib/lutaml/model/type/hash.rb +11 -11
  18. data/lib/lutaml/model/utils.rb +7 -0
  19. data/lib/lutaml/model/version.rb +1 -1
  20. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +5 -1
  21. data/lib/lutaml/model/xml_adapter/xml_document.rb +11 -15
  22. data/lib/lutaml/model/xml_mapping.rb +4 -2
  23. data/lib/lutaml/model/xml_mapping_rule.rb +1 -4
  24. data/lib/lutaml/model.rb +1 -0
  25. data/spec/fixtures/xml/invalid_math_document.xml +4 -0
  26. data/spec/fixtures/xml/math_document_schema.xsd +56 -0
  27. data/spec/fixtures/xml/test_schema.xsd +53 -0
  28. data/spec/fixtures/xml/valid_math_document.xml +4 -0
  29. data/spec/lutaml/model/cdata_spec.rb +2 -2
  30. data/spec/lutaml/model/custom_model_spec.rb +7 -20
  31. data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
  32. data/spec/lutaml/model/map_all_spec.rb +188 -0
  33. data/spec/lutaml/model/mixed_content_spec.rb +15 -15
  34. data/spec/lutaml/model/render_nil_spec.rb +29 -0
  35. data/spec/lutaml/model/schema/xml_compiler_spec.rb +1431 -0
  36. data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
  37. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
  38. data/spec/lutaml/model/xml_mapping_spec.rb +108 -1
  39. metadata +12 -2
@@ -0,0 +1,188 @@
1
+ require "spec_helper"
2
+
3
+ module MapAllSpec
4
+ class Document < Lutaml::Model::Serializable
5
+ attribute :content, :string
6
+
7
+ xml do
8
+ root "document"
9
+ map_all to: :content
10
+ end
11
+
12
+ json do
13
+ map_all to: :content
14
+ end
15
+
16
+ yaml do
17
+ map_all to: :content
18
+ end
19
+
20
+ toml do
21
+ map_all to: :content
22
+ end
23
+ end
24
+
25
+ class InvalidDocument < Lutaml::Model::Serializable
26
+ attribute :content, :string
27
+ attribute :title, :string
28
+
29
+ json do
30
+ map_all to: :content
31
+ end
32
+
33
+ yaml do
34
+ map_element "title", to: :title
35
+ end
36
+ end
37
+
38
+ RSpec.describe "MapAll" do
39
+ describe "XML serialization" do
40
+ let(:xml_content) do
41
+ <<~XML
42
+ <document>
43
+ Content with <b>tags</b> and <i>formatting</i>.
44
+ <metadata>
45
+ <author>John Doe</author>
46
+ <date>2024-01-15</date>
47
+ </metadata>
48
+ </document>
49
+ XML
50
+ end
51
+
52
+ let(:sub_xml_content) do
53
+ <<~XML
54
+ Content with <b>tags</b> and <i>formatting</i>.
55
+ <metadata>
56
+ <author>John Doe</author>
57
+ <date>2024-01-15</date>
58
+ </metadata>
59
+ XML
60
+ end
61
+
62
+ it "captures all XML content" do
63
+ doc = Document.from_xml(xml_content)
64
+ expect(doc.content).to be_equivalent_to(sub_xml_content)
65
+ end
66
+
67
+ it "preserves XML content through round trip" do
68
+ doc = Document.from_xml(xml_content)
69
+ regenerated = doc.to_xml
70
+ expect(regenerated).to be_equivalent_to(xml_content)
71
+ end
72
+ end
73
+
74
+ describe "JSON serialization" do
75
+ let(:json_content) do
76
+ {
77
+ "sections" => [
78
+ { "title" => "Introduction", "text" => "Chapter 1" },
79
+ { "title" => "Conclusion", "text" => "Final chapter" },
80
+ ],
81
+ "metadata" => {
82
+ "author" => "John Doe",
83
+ "date" => "2024-01-15",
84
+ },
85
+ }.to_json
86
+ end
87
+
88
+ it "captures all JSON content" do
89
+ doc = Document.from_json(json_content)
90
+ parsed = JSON.parse(doc.content)
91
+ expect(parsed["sections"].first["title"]).to eq("Introduction")
92
+ expect(parsed["metadata"]["author"]).to eq("John Doe")
93
+ end
94
+
95
+ it "preserves JSON content through round trip" do
96
+ doc = Document.from_json(json_content)
97
+ regenerated = doc.to_json
98
+ expect(JSON.parse(regenerated)).to eq(JSON.parse(json_content))
99
+ end
100
+ end
101
+
102
+ describe "YAML serialization" do
103
+ let(:yaml_content) do
104
+ <<~YAML
105
+ sections:
106
+ - title: Introduction
107
+ text: Chapter 1
108
+ - title: Conclusion
109
+ text: Final chapter
110
+ metadata:
111
+ author: John Doe
112
+ date: 2024-01-15
113
+ YAML
114
+ end
115
+
116
+ it "captures all YAML content" do
117
+ doc = Document.from_yaml(yaml_content)
118
+ parsed = YAML.safe_load(doc.content, permitted_classes: [Date])
119
+ expect(parsed["sections"].first["title"]).to eq("Introduction")
120
+ expect(parsed["metadata"]["author"]).to eq("John Doe")
121
+ end
122
+
123
+ it "preserves YAML content through round trip" do
124
+ doc = Document.from_yaml(yaml_content)
125
+ regenerated = doc.to_yaml
126
+ expect(YAML.safe_load(regenerated, permitted_classes: [Date])).to eq(YAML.safe_load(yaml_content, permitted_classes: [Date]))
127
+ end
128
+ end
129
+
130
+ describe "TOML serialization" do
131
+ let(:toml_content) do
132
+ <<~TOML
133
+ title = "Document Title"
134
+
135
+ [metadata]
136
+ author = "John Doe"
137
+ date = "2024-01-15"
138
+
139
+ [[sections]]
140
+ title = "Introduction"
141
+ text = "Chapter 1"
142
+
143
+ [[sections]]
144
+ title = "Conclusion"
145
+ text = "Final chapter"
146
+ TOML
147
+ end
148
+
149
+ it "captures all TOML content" do
150
+ doc = Document.from_toml(toml_content)
151
+ parsed = TomlRB.parse(doc.content)
152
+ expect(parsed["sections"].first["title"]).to eq("Introduction")
153
+ expect(parsed["metadata"]["author"]).to eq("John Doe")
154
+ end
155
+
156
+ it "preserves TOML content through round trip" do
157
+ doc = Document.from_toml(toml_content)
158
+ regenerated = doc.to_toml
159
+ expect(TomlRB.parse(regenerated)).to eq(TomlRB.parse(toml_content))
160
+ end
161
+ end
162
+
163
+ describe "invalid mapping combinations" do
164
+ it "raises error when combining map_all with other mappings" do
165
+ expect do
166
+ InvalidDocument.json do
167
+ map_element "title", to: :title
168
+ end
169
+ end.to raise_error(
170
+ StandardError,
171
+ "map_all is not allowed with other mappings",
172
+ )
173
+ end
174
+
175
+ it "raises error when combining other mappings are used with map_all" do
176
+ expect do
177
+ InvalidDocument.yaml do
178
+ map_element "title", to: :title
179
+ map_all to: :content
180
+ end
181
+ end.to raise_error(
182
+ StandardError,
183
+ "map_all is not allowed with other mappings",
184
+ )
185
+ end
186
+ end
187
+ end
188
+ end
@@ -539,10 +539,10 @@ RSpec.describe "MixedContent" do
539
539
  let(:expected_nokogiri_xml) do
540
540
  <<~XML
541
541
  <SpecialCharContentWithRawOptionAndMixedOption><special>
542
- B &lt;p&gt;R&amp;amp;C&lt;/p&gt;
543
- C &lt;p&gt;J&amp;#x2014;C&lt;/p&gt;
544
- O &lt;p&gt;A &amp;amp; B &lt;/p&gt;
545
- F &lt;p&gt;Z &amp;#xA9;S&lt;/p&gt;
542
+ B <p>R&amp;C</p>
543
+ C <p>JC</p>
544
+ O <p>A &amp; B </p>
545
+ F <p>Z ©S</p>
546
546
  </special></SpecialCharContentWithRawOptionAndMixedOption>
547
547
  XML
548
548
  end
@@ -550,10 +550,10 @@ RSpec.describe "MixedContent" do
550
550
  let(:expected_ox_xml) do
551
551
  <<~XML
552
552
  <SpecialCharContentWithRawOptionAndMixedOption>
553
- <special> B &lt;p&gt;R&amp;amp;C&lt;/p&gt;
554
- C &lt;p&gt;J—C&lt;/p&gt;
555
- O &lt;p&gt;A &amp;amp; B &lt;/p&gt;
556
- F &lt;p&gt;Z ©S&lt;/p&gt;
553
+ <special> B <p>R&amp;C</p>
554
+ C <p>J—C</p>
555
+ O <p>A &amp; B </p>
556
+ F <p>Z ©S</p>
557
557
  </special>
558
558
  </SpecialCharContentWithRawOptionAndMixedOption>
559
559
  XML
@@ -562,10 +562,10 @@ RSpec.describe "MixedContent" do
562
562
  let(:expected_oga_xml) do
563
563
  <<~XML
564
564
  <SpecialCharContentWithRawOptionAndMixedOption>
565
- <special> B &lt;p&gt;R&amp;C&lt;/p&gt;
566
- C &lt;p&gt;J—C&lt;/p&gt;
567
- O &lt;p&gt;A &amp; B &lt;/p&gt;
568
- F &lt;p&gt;Z ©S&lt;/p&gt;
565
+ <special> B <p>R&amp;C</p>
566
+ C <p>J—C</p>
567
+ O <p>A &amp; B </p>
568
+ F <p>Z ©S</p>
569
569
  </special>
570
570
  </SpecialCharContentWithRawOptionAndMixedOption>
571
571
  XML
@@ -604,9 +604,9 @@ RSpec.describe "MixedContent" do
604
604
  end
605
605
 
606
606
  describe ".to_xml" do
607
- let(:expected_nokogiri_xml) { "B &lt;p&gt;R&lt;/p&gt;" }
608
- let(:expected_oga_xml) { "B &lt;p&gt;R&amp;C&lt;/p&gt;" }
609
- let(:expected_ox_xml) { "B &lt;p&gt;R&amp;amp;C&lt;/p&gt;" }
607
+ let(:expected_nokogiri_xml) { "B <p>R</p>" }
608
+ let(:expected_oga_xml) { "B <p>R&amp;C</p>" }
609
+ let(:expected_ox_xml) { "B <p>R&amp;C</p>" }
610
610
 
611
611
  it "serializes special char mixed content correctly" do
612
612
  parsed = MixedContentSpec::SpecialCharContentWithRawAndMixedOption.from_xml(xml)
@@ -136,4 +136,33 @@ RSpec.describe RenderNil do
136
136
  expect(pottery.name).to eq("Unnamed Pottery")
137
137
  expect(pottery.glaze).to be_nil
138
138
  end
139
+
140
+ context "with empty string as values for attributes" do
141
+ let(:attributes) do
142
+ {
143
+ name: "",
144
+ clay_type: "",
145
+ glaze: "",
146
+ dimensions: [],
147
+ render_nil_nested: RenderNilNested.new,
148
+ }
149
+ end
150
+
151
+ it "does not tread empty string as nil" do
152
+ expected_yaml = <<~YAML
153
+ ---
154
+ name: ''
155
+ clay_type: ''
156
+ glaze: ''
157
+ YAML
158
+
159
+ generated_yaml = model.to_yaml.strip
160
+
161
+ # Removing empty spaces from the end of the line because of and issue in
162
+ # libyaml -> https://github.com/yaml/libyaml/issues/46
163
+ generated_yaml = generated_yaml.gsub(": \n", ":\n")
164
+
165
+ expect(generated_yaml).to eq(expected_yaml.strip)
166
+ end
167
+ end
139
168
  end