lutaml-model 0.5.2 → 0.5.4

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 (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