lutaml-model 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +36 -20
- data/README.adoc +1003 -192
- data/lib/lutaml/model/attribute.rb +6 -2
- data/lib/lutaml/model/error/collection_true_missing_error.rb +16 -0
- data/lib/lutaml/model/error/multiple_mappings_error.rb +6 -0
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/key_value_mapping.rb +25 -4
- data/lib/lutaml/model/key_value_mapping_rule.rb +16 -3
- data/lib/lutaml/model/loggable.rb +15 -0
- data/lib/lutaml/model/mapping_rule.rb +14 -2
- data/lib/lutaml/model/serialize.rb +114 -64
- data/lib/lutaml/model/type/decimal.rb +5 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +1 -0
- data/lib/lutaml/model/xml_adapter/builder/oga.rb +180 -0
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +1 -0
- data/lib/lutaml/model/xml_adapter/oga/document.rb +20 -0
- data/lib/lutaml/model/xml_adapter/oga/element.rb +117 -0
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +77 -44
- data/lib/lutaml/model/xml_adapter/xml_document.rb +14 -12
- data/lib/lutaml/model/xml_mapping.rb +3 -0
- data/lib/lutaml/model/xml_mapping_rule.rb +13 -4
- data/lib/lutaml/model.rb +1 -0
- data/spec/address_spec.rb +1 -0
- data/spec/fixtures/sample_model.rb +7 -0
- data/spec/lutaml/model/custom_model_spec.rb +47 -1
- data/spec/lutaml/model/included_spec.rb +192 -0
- data/spec/lutaml/model/mixed_content_spec.rb +48 -32
- data/spec/lutaml/model/multiple_mapping_spec.rb +329 -0
- data/spec/lutaml/model/ordered_content_spec.rb +1 -1
- data/spec/lutaml/model/render_nil_spec.rb +3 -0
- data/spec/lutaml/model/root_mappings_spec.rb +297 -0
- data/spec/lutaml/model/serializable_spec.rb +42 -7
- data/spec/lutaml/model/type/boolean_spec.rb +62 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +182 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -11
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +67 -1
- data/spec/lutaml/model/xml_adapter_spec.rb +2 -2
- data/spec/lutaml/model/xml_mapping_spec.rb +32 -9
- data/spec/sample_model_spec.rb +114 -0
- metadata +12 -2
@@ -0,0 +1,329 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "lutaml/model"
|
3
|
+
|
4
|
+
module MultipleMapping
|
5
|
+
class Product < Lutaml::Model::Serializable
|
6
|
+
attribute :name, Lutaml::Model::Type::String
|
7
|
+
attribute :localized_name, Lutaml::Model::Type::String
|
8
|
+
attribute :description, Lutaml::Model::Type::String
|
9
|
+
attribute :status, Lutaml::Model::Type::String
|
10
|
+
attribute :content, Lutaml::Model::Type::String
|
11
|
+
|
12
|
+
yaml do
|
13
|
+
map ["name", "product_name"], to: :name
|
14
|
+
map ["desc", "description"], to: :description
|
15
|
+
end
|
16
|
+
|
17
|
+
json do
|
18
|
+
map ["name", "product_name"], to: :name
|
19
|
+
map ["desc", "description"], to: :description
|
20
|
+
end
|
21
|
+
|
22
|
+
toml do
|
23
|
+
map ["name", "product_name"], to: :name
|
24
|
+
map ["desc", "description"], to: :description
|
25
|
+
end
|
26
|
+
|
27
|
+
xml do
|
28
|
+
root "product"
|
29
|
+
map_element ["name", "product-name"], to: :name
|
30
|
+
map_element ["localized-name", "localized_name"], to: :localized_name
|
31
|
+
map_element ["desc", "description"], to: :description
|
32
|
+
map_attribute ["status", "product-status"], to: :status
|
33
|
+
map_content to: :content
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class CustomModel < Lutaml::Model::Serializable
|
38
|
+
attribute :id, Lutaml::Model::Type::String
|
39
|
+
attribute :full_name, Lutaml::Model::Type::String
|
40
|
+
attribute :size, Lutaml::Model::Type::Integer
|
41
|
+
attribute :color, Lutaml::Model::Type::String
|
42
|
+
attribute :description, Lutaml::Model::Type::String
|
43
|
+
|
44
|
+
json do
|
45
|
+
map ["name", "custom_name"], with: { to: :name_to_json, from: :name_from_json }
|
46
|
+
map ["color", "shade"], with: { to: :color_to_json, from: :color_from_json }
|
47
|
+
map ["size", "dimension"], with: { to: :size_to_json, from: :size_from_json }
|
48
|
+
map ["desc", "description"], with: { to: :desc_to_json, from: :desc_from_json }
|
49
|
+
end
|
50
|
+
|
51
|
+
xml do
|
52
|
+
root "CustomModel"
|
53
|
+
map_attribute ["id", "identifier"], with: { to: :id_to_xml, from: :id_from_xml }
|
54
|
+
map_element ["name", "custom-name"], with: { to: :name_to_xml, from: :name_from_xml }
|
55
|
+
map_element ["color", "shade"], with: { to: :color_to_xml, from: :color_from_xml }
|
56
|
+
map_element ["size", "dimension"], with: { to: :size_to_xml, from: :size_from_xml }
|
57
|
+
map_element ["desc", "description"], with: { to: :desc_to_xml, from: :desc_from_xml }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Custom methods for JSON
|
61
|
+
def name_to_json(model, doc)
|
62
|
+
doc["name"] = "JSON Model: #{model.full_name}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def name_from_json(model, value)
|
66
|
+
model.full_name = value&.sub(/^JSON Model: /, "")
|
67
|
+
end
|
68
|
+
|
69
|
+
def color_to_json(model, doc)
|
70
|
+
doc["color"] = model.color.upcase
|
71
|
+
end
|
72
|
+
|
73
|
+
def color_from_json(model, value)
|
74
|
+
model.color = value&.downcase
|
75
|
+
end
|
76
|
+
|
77
|
+
def size_to_json(model, doc)
|
78
|
+
doc["size"] = model.size + 10
|
79
|
+
end
|
80
|
+
|
81
|
+
def size_from_json(model, value)
|
82
|
+
model.size = value - 10
|
83
|
+
end
|
84
|
+
|
85
|
+
def desc_to_json(model, doc)
|
86
|
+
doc["desc"] = "JSON Description: #{model.description}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def desc_from_json(model, value)
|
90
|
+
model.description = value&.sub(/^JSON Description: /, "")
|
91
|
+
end
|
92
|
+
|
93
|
+
# Custom methods for XML
|
94
|
+
def id_to_xml(model, parent, doc)
|
95
|
+
doc.add_attribute(parent, "id", "XML-#{model.id}")
|
96
|
+
end
|
97
|
+
|
98
|
+
def id_from_xml(model, value)
|
99
|
+
model.id = value&.sub(/^XML-/, "")
|
100
|
+
end
|
101
|
+
|
102
|
+
def name_to_xml(model, parent, doc)
|
103
|
+
el = doc.create_element("name")
|
104
|
+
doc.add_text(el, "XML Model: #{model.full_name}")
|
105
|
+
doc.add_element(parent, el)
|
106
|
+
end
|
107
|
+
|
108
|
+
def name_from_xml(model, value)
|
109
|
+
model.full_name = value.sub(/^XML Model: /, "")
|
110
|
+
end
|
111
|
+
|
112
|
+
def color_to_xml(model, parent, doc)
|
113
|
+
el = doc.create_element("color")
|
114
|
+
doc.add_text(el, model.color.upcase)
|
115
|
+
doc.add_element(parent, el)
|
116
|
+
end
|
117
|
+
|
118
|
+
def color_from_xml(model, value)
|
119
|
+
model.color = value.downcase
|
120
|
+
end
|
121
|
+
|
122
|
+
def size_to_xml(model, parent, doc)
|
123
|
+
el = doc.create_element("size")
|
124
|
+
doc.add_text(el, (model.size + 10).to_s)
|
125
|
+
doc.add_element(parent, el)
|
126
|
+
end
|
127
|
+
|
128
|
+
def size_from_xml(model, value)
|
129
|
+
model.size = (value.to_i || 0) - 10
|
130
|
+
end
|
131
|
+
|
132
|
+
def desc_to_xml(model, parent, doc)
|
133
|
+
el = doc.create_element("desc")
|
134
|
+
doc.add_text(el, "XML Description: #{model.description}")
|
135
|
+
doc.add_element(parent, el)
|
136
|
+
end
|
137
|
+
|
138
|
+
def desc_from_xml(model, value)
|
139
|
+
model.description = value.sub(/^XML Description: /, "")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
RSpec.describe MultipleMapping do
|
145
|
+
context "with key-value formats" do
|
146
|
+
context "with YAML format" do
|
147
|
+
let(:yaml_with_name) { "product_name: Coffee Maker\ndescription: Premium coffee maker" }
|
148
|
+
let(:yaml_with_desc) { "---\nname: Coffee Maker\ndesc: Premium coffee maker\n" }
|
149
|
+
|
150
|
+
it "handles bidirectional conversion" do
|
151
|
+
product1 = MultipleMapping::Product.from_yaml(yaml_with_name)
|
152
|
+
product2 = MultipleMapping::Product.from_yaml(yaml_with_desc)
|
153
|
+
|
154
|
+
# keys for name and description are :name and :desc respectively since
|
155
|
+
# they are first element in their respective mapping array
|
156
|
+
|
157
|
+
expected_yaml = "---\nname: Coffee Maker\ndesc: Premium coffee maker\n"
|
158
|
+
expect(product1.to_yaml).to eq(expected_yaml)
|
159
|
+
expect(product2.to_yaml).to eq(yaml_with_desc)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "with JSON format" do
|
164
|
+
let(:json_with_name) { '{"product_name":"Coffee Maker","description":"Premium coffee maker"}' }
|
165
|
+
let(:json_with_desc) { '{"name":"Coffee Maker","desc":"Premium coffee maker"}' }
|
166
|
+
|
167
|
+
it "handles bidirectional conversion" do
|
168
|
+
product1 = MultipleMapping::Product.from_json(json_with_name)
|
169
|
+
product2 = MultipleMapping::Product.from_json(json_with_desc)
|
170
|
+
|
171
|
+
# keys for name and description are :name and :desc respectively since
|
172
|
+
# they are first element in their respective mapping array
|
173
|
+
expected_json = '{"name":"Coffee Maker","desc":"Premium coffee maker"}'
|
174
|
+
|
175
|
+
expect(product1.to_json).to eq(expected_json)
|
176
|
+
expect(product2.to_json).to eq(json_with_desc)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "with XML format" do
|
182
|
+
shared_examples "xml adapter with multiple mappings" do |adapter_class|
|
183
|
+
before do
|
184
|
+
Lutaml::Model::Config.xml_adapter = adapter_class
|
185
|
+
end
|
186
|
+
|
187
|
+
around do |example|
|
188
|
+
old_adapter = Lutaml::Model::Config.xml_adapter
|
189
|
+
Lutaml::Model::Config.xml_adapter = adapter_class
|
190
|
+
|
191
|
+
example.run
|
192
|
+
ensure
|
193
|
+
Lutaml::Model::Config.xml_adapter = old_adapter
|
194
|
+
end
|
195
|
+
|
196
|
+
let(:xml_with_attributes) do
|
197
|
+
<<~XML
|
198
|
+
<product status="active">
|
199
|
+
Some content here
|
200
|
+
<name>Coffee Maker</name>
|
201
|
+
<description>Premium coffee maker</description>
|
202
|
+
</product>
|
203
|
+
XML
|
204
|
+
end
|
205
|
+
|
206
|
+
let(:xml_with_alternate_attributes) do
|
207
|
+
<<~XML
|
208
|
+
<product product-status="in-stock">
|
209
|
+
Different content
|
210
|
+
<product-name>Coffee Maker</product-name>
|
211
|
+
<desc>Premium coffee maker</desc>
|
212
|
+
</product>
|
213
|
+
XML
|
214
|
+
end
|
215
|
+
|
216
|
+
it "handles bidirectional conversion with attributes and content" do
|
217
|
+
product1 = MultipleMapping::Product.from_xml(xml_with_attributes)
|
218
|
+
product2 = MultipleMapping::Product.from_xml(xml_with_alternate_attributes)
|
219
|
+
|
220
|
+
# Key for element name is :name since it is first element in mapping array and same for status attribute
|
221
|
+
expected_xml_product1 = <<~XML
|
222
|
+
<product status="active">
|
223
|
+
<name>Coffee Maker</name>
|
224
|
+
<desc>Premium coffee maker</desc>
|
225
|
+
Some content here
|
226
|
+
</product>
|
227
|
+
XML
|
228
|
+
|
229
|
+
expected_xml_product2 = <<~XML
|
230
|
+
<product status="in-stock">
|
231
|
+
<name>Coffee Maker</name>
|
232
|
+
<desc>Premium coffee maker</desc>
|
233
|
+
Different content
|
234
|
+
</product>
|
235
|
+
XML
|
236
|
+
|
237
|
+
expect(product1.name).to eq("Coffee Maker")
|
238
|
+
expect(product1.status).to eq("active")
|
239
|
+
expect(product2.status).to eq("in-stock")
|
240
|
+
|
241
|
+
expect(product1.to_xml).to be_equivalent_to(expected_xml_product1)
|
242
|
+
expect(product2.to_xml).to be_equivalent_to(expected_xml_product2)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context "with Nokogiri adapter" do
|
247
|
+
it_behaves_like "xml adapter with multiple mappings", Lutaml::Model::XmlAdapter::NokogiriAdapter
|
248
|
+
end
|
249
|
+
|
250
|
+
context "with Ox adapter" do
|
251
|
+
it_behaves_like "xml adapter with multiple mappings", Lutaml::Model::XmlAdapter::OxAdapter
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
context "with CustomModel" do
|
256
|
+
context "with JSON format" do
|
257
|
+
let(:json_with_alternate) { '{"custom_name":"JSON Model: Vase","shade":"BLUE","dimension":22,"description":"JSON Description: A beautiful ceramic vase"}' }
|
258
|
+
let(:json_with_standard) { '{"name":"JSON Model: Vase","color":"BLUE","size":22,"desc":"JSON Description: A beautiful ceramic vase"}' }
|
259
|
+
|
260
|
+
it "handles bidirectional conversion with custom methods" do
|
261
|
+
model1 = MultipleMapping::CustomModel.from_json(json_with_alternate)
|
262
|
+
model2 = MultipleMapping::CustomModel.from_json(json_with_standard)
|
263
|
+
|
264
|
+
# keys are 'name', 'color', 'size', 'desc' respectively since
|
265
|
+
# they are first element in their respective mapping array
|
266
|
+
expected_json = '{"name":"JSON Model: Vase","color":"BLUE","size":22,"desc":"JSON Description: A beautiful ceramic vase"}'
|
267
|
+
|
268
|
+
expect(model1.to_json).to eq(expected_json)
|
269
|
+
expect(model2.to_json).to eq(expected_json)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
context "with XML format" do
|
274
|
+
shared_examples "xml adapter with custom methods" do |_adapter_class|
|
275
|
+
before do
|
276
|
+
Lutaml::Model::Config.xml_adapter = Lutaml::Model::XmlAdapter::NokogiriAdapter
|
277
|
+
end
|
278
|
+
|
279
|
+
let(:xml_with_alternate) do
|
280
|
+
<<~XML
|
281
|
+
<CustomModel identifier="123">
|
282
|
+
<custom-name>XML Model: Vase</custom-name>
|
283
|
+
<shade>BLUE</shade>
|
284
|
+
<dimension>22</dimension>
|
285
|
+
<description>XML Description: A beautiful ceramic vase</description>
|
286
|
+
</CustomModel>
|
287
|
+
XML
|
288
|
+
end
|
289
|
+
|
290
|
+
let(:xml_with_standard) do
|
291
|
+
<<~XML
|
292
|
+
<CustomModel identifier="123">
|
293
|
+
<name>XML Model: Vase</name>
|
294
|
+
<color>BLUE</color>
|
295
|
+
<size>22</size>
|
296
|
+
<desc>XML Description: A beautiful ceramic vase</desc>
|
297
|
+
</CustomModel>
|
298
|
+
XML
|
299
|
+
end
|
300
|
+
|
301
|
+
it "handles bidirectional conversion with custom methods" do
|
302
|
+
model1 = MultipleMapping::CustomModel.from_xml(xml_with_alternate)
|
303
|
+
model2 = MultipleMapping::CustomModel.from_xml(xml_with_standard)
|
304
|
+
|
305
|
+
# Element names are 'name', 'color', 'size', 'desc' respectively since
|
306
|
+
# they are first element in their respective mapping array
|
307
|
+
expected_xml = <<~XML
|
308
|
+
<CustomModel id="XML-123">
|
309
|
+
<name>XML Model: Vase</name>
|
310
|
+
<color>BLUE</color>
|
311
|
+
<size>22</size>
|
312
|
+
<desc>XML Description: A beautiful ceramic vase</desc>
|
313
|
+
</CustomModel>
|
314
|
+
XML
|
315
|
+
expect(model1.to_xml).to be_equivalent_to(expected_xml)
|
316
|
+
expect(model2.to_xml).to be_equivalent_to(expected_xml)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
context "with Nokogiri adapter" do
|
321
|
+
it_behaves_like "xml adapter with custom methods", Lutaml::Model::XmlAdapter::NokogiriAdapter
|
322
|
+
end
|
323
|
+
|
324
|
+
context "with Ox adapter" do
|
325
|
+
it_behaves_like "xml adapter with custom methods", Lutaml::Model::XmlAdapter::OxAdapter
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
@@ -77,7 +77,7 @@ RSpec.describe "OrderedContent" do
|
|
77
77
|
it_behaves_like "ordered content behavior", described_class
|
78
78
|
end
|
79
79
|
|
80
|
-
describe Lutaml::Model::XmlAdapter::OgaAdapter
|
80
|
+
describe Lutaml::Model::XmlAdapter::OgaAdapter do
|
81
81
|
it_behaves_like "ordered content behavior", described_class
|
82
82
|
end
|
83
83
|
end
|
@@ -42,6 +42,7 @@ class RenderNil < Lutaml::Model::Serializable
|
|
42
42
|
map "clay_type", to: :clay_type, render_nil: false
|
43
43
|
map "glaze", to: :glaze, render_nil: true
|
44
44
|
map "dimensions", to: :dimensions, render_nil: false
|
45
|
+
map "render_nil_nested", to: :render_nil_nested, render_nil: false
|
45
46
|
end
|
46
47
|
|
47
48
|
toml do
|
@@ -59,8 +60,10 @@ RSpec.describe RenderNil do
|
|
59
60
|
clay_type: nil,
|
60
61
|
glaze: nil,
|
61
62
|
dimensions: nil,
|
63
|
+
render_nil_nested: RenderNilNested.new,
|
62
64
|
}
|
63
65
|
end
|
66
|
+
|
64
67
|
let(:model) { described_class.new(attributes) }
|
65
68
|
|
66
69
|
it "serializes to JSON with render_nil option" do
|
@@ -0,0 +1,297 @@
|
|
1
|
+
module RootMappingSpec
|
2
|
+
class CeramicDetails < Lutaml::Model::Serializable
|
3
|
+
attribute :name, :string
|
4
|
+
attribute :insignia, :string
|
5
|
+
|
6
|
+
key_value do
|
7
|
+
map "name", to: :name
|
8
|
+
map "insignia", to: :insignia
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class CeramicWithDetails < Lutaml::Model::Serializable
|
13
|
+
attribute :ceramic_id, :string
|
14
|
+
attribute :ceramic_type, :string
|
15
|
+
attribute :ceramic_details, CeramicDetails
|
16
|
+
attribute :ceramic_urn, :string
|
17
|
+
|
18
|
+
key_value do
|
19
|
+
map "id", to: :ceramic_id
|
20
|
+
map "type", to: :ceramic_type
|
21
|
+
map "details", to: :ceramic_details
|
22
|
+
map "urn", to: :ceramic_urn
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class CeramicCollectionWithKeyAndValue < Lutaml::Model::Serializable
|
27
|
+
attribute :ceramics, CeramicWithDetails, collection: true
|
28
|
+
|
29
|
+
key_value do
|
30
|
+
map to: :ceramics,
|
31
|
+
root_mappings: {
|
32
|
+
ceramic_id: :key,
|
33
|
+
ceramic_details: :value,
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class CeramicCollectionWithKeyAndComplexValue < Lutaml::Model::Serializable
|
39
|
+
attribute :ceramics, CeramicWithDetails, collection: true
|
40
|
+
|
41
|
+
key_value do
|
42
|
+
map to: :ceramics,
|
43
|
+
root_mappings: {
|
44
|
+
ceramic_id: :key,
|
45
|
+
ceramic_type: :type,
|
46
|
+
ceramic_details: "details",
|
47
|
+
ceramic_urn: ["urn", "primary"],
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Ceramic < Lutaml::Model::Serializable
|
53
|
+
attribute :ceramic_id, :string
|
54
|
+
attribute :ceramic_name, :string
|
55
|
+
|
56
|
+
key_value do
|
57
|
+
map "id", to: :ceramic_id
|
58
|
+
map "name", to: :ceramic_name
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class CeramicCollectionWithKeyOnly < Lutaml::Model::Serializable
|
63
|
+
attribute :ceramics, Ceramic, collection: true
|
64
|
+
|
65
|
+
key_value do
|
66
|
+
map to: :ceramics, root_mappings: { ceramic_id: :key }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class CeramicCollectionWithoutCollectionTrue < Lutaml::Model::Serializable
|
71
|
+
attribute :ceramics, Ceramic
|
72
|
+
|
73
|
+
key_value do
|
74
|
+
map to: :ceramics, root_mappings: { ceramic_id: :key }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
RSpec.describe "RootMapping" do
|
80
|
+
shared_examples "having root mappings" do |format|
|
81
|
+
let(:adapter) do
|
82
|
+
Lutaml::Model::Config.public_send(:"#{format}_adapter")
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:input) do
|
86
|
+
adapter.new(input_hash).public_send(:"to_#{format}")
|
87
|
+
end
|
88
|
+
|
89
|
+
# 1. Only map to `:key`. Then only override key, the rest of the mappings stay.
|
90
|
+
context "when only `key` is mapped" do
|
91
|
+
let(:parsed) do
|
92
|
+
RootMappingSpec::CeramicCollectionWithKeyOnly.public_send(:"from_#{format}", input)
|
93
|
+
end
|
94
|
+
|
95
|
+
let(:input_hash) do
|
96
|
+
{
|
97
|
+
"vase1" => { "name" => "Imperial Vase" },
|
98
|
+
"bowl2" => { "name" => "18th Century Bowl" },
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
let(:ceramic_vase) do
|
103
|
+
RootMappingSpec::Ceramic.new(
|
104
|
+
ceramic_id: "vase1",
|
105
|
+
ceramic_name: "Imperial Vase",
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
let(:ceramic_bowl) do
|
110
|
+
RootMappingSpec::Ceramic.new(
|
111
|
+
ceramic_id: "bowl2",
|
112
|
+
ceramic_name: "18th Century Bowl",
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "parses" do
|
117
|
+
expect(parsed.ceramics.count).to eq(2)
|
118
|
+
# Because Tomlib reverses the order of the hash, so can not check based on position
|
119
|
+
expect(parsed.ceramics).to include(ceramic_vase)
|
120
|
+
expect(parsed.ceramics).to include(ceramic_bowl)
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "serialize" do
|
124
|
+
let(:collection) do
|
125
|
+
RootMappingSpec::CeramicCollectionWithKeyOnly.new(ceramics: [
|
126
|
+
ceramic_vase,
|
127
|
+
ceramic_bowl,
|
128
|
+
])
|
129
|
+
end
|
130
|
+
|
131
|
+
it "serializes correctly" do
|
132
|
+
expect(collection.public_send(:"to_#{format}")).to eq(input)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# 2. Maps `:key` and another attribute, then we override all the other mappings (clean slate)
|
138
|
+
context "when `key` and `value` are mapped" do
|
139
|
+
let(:parsed) do
|
140
|
+
RootMappingSpec::CeramicCollectionWithKeyAndValue.public_send(:"from_#{format}", input)
|
141
|
+
end
|
142
|
+
|
143
|
+
let(:input_hash) do
|
144
|
+
{
|
145
|
+
"vase1" => { "name" => "Imperial Vase", "insignia" => "Tang Tianbao" },
|
146
|
+
"bowl2" => { "name" => "18th Century Bowl", "insignia" => "Ming Wanli" },
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
let(:vase_with_details) do
|
151
|
+
RootMappingSpec::CeramicWithDetails.new(
|
152
|
+
ceramic_id: "vase1",
|
153
|
+
ceramic_details: RootMappingSpec::CeramicDetails.new(
|
154
|
+
name: "Imperial Vase",
|
155
|
+
insignia: "Tang Tianbao",
|
156
|
+
),
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
let(:bowl_with_details) do
|
161
|
+
RootMappingSpec::CeramicWithDetails.new(
|
162
|
+
ceramic_id: "bowl2",
|
163
|
+
ceramic_details: RootMappingSpec::CeramicDetails.new(
|
164
|
+
name: "18th Century Bowl",
|
165
|
+
insignia: "Ming Wanli",
|
166
|
+
),
|
167
|
+
)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "parses" do
|
171
|
+
expect(parsed.ceramics.count).to eq(2)
|
172
|
+
# Because Tomlib reverses the order of the hash, so can not check based on position
|
173
|
+
expect(parsed.ceramics).to include(vase_with_details)
|
174
|
+
expect(parsed.ceramics).to include(bowl_with_details)
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "serialize" do
|
178
|
+
let(:collection) do
|
179
|
+
RootMappingSpec::CeramicCollectionWithKeyAndValue.new(ceramics: [
|
180
|
+
vase_with_details,
|
181
|
+
bowl_with_details,
|
182
|
+
])
|
183
|
+
end
|
184
|
+
|
185
|
+
it "serializes correctly" do
|
186
|
+
expect(collection.public_send(:"to_#{format}")).to eq(input)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# 3. Maps `:key` and `:value`, then we map the key and the value body to the new mappings.
|
192
|
+
context "when `key` and complex value structure is mapped" do
|
193
|
+
let(:parsed) do
|
194
|
+
RootMappingSpec::CeramicCollectionWithKeyAndComplexValue.public_send(:"from_#{format}", input)
|
195
|
+
end
|
196
|
+
|
197
|
+
let(:input_hash) do
|
198
|
+
{
|
199
|
+
"vase1" => {
|
200
|
+
"type" => "vase",
|
201
|
+
"details" => {
|
202
|
+
"name" => "Imperial Vase",
|
203
|
+
"insignia" => "Tang Tianbao",
|
204
|
+
},
|
205
|
+
"urn" => {
|
206
|
+
"primary" => "urn:ceramic:vase:vase1",
|
207
|
+
},
|
208
|
+
},
|
209
|
+
"bowl2" => {
|
210
|
+
"type" => "bowl",
|
211
|
+
"details" => {
|
212
|
+
"name" => "18th Century Bowl",
|
213
|
+
"insignia" => "Ming Wanli",
|
214
|
+
},
|
215
|
+
"urn" => {
|
216
|
+
"primary" => "urn:ceramic:bowl:bowl2",
|
217
|
+
},
|
218
|
+
},
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
let(:vase_with_details) do
|
223
|
+
RootMappingSpec::CeramicWithDetails.new(
|
224
|
+
ceramic_id: "vase1",
|
225
|
+
ceramic_type: "vase",
|
226
|
+
ceramic_urn: "urn:ceramic:vase:vase1",
|
227
|
+
ceramic_details: RootMappingSpec::CeramicDetails.new(
|
228
|
+
name: "Imperial Vase",
|
229
|
+
insignia: "Tang Tianbao",
|
230
|
+
),
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
let(:bowl_with_details) do
|
235
|
+
RootMappingSpec::CeramicWithDetails.new(
|
236
|
+
ceramic_id: "bowl2",
|
237
|
+
ceramic_type: "bowl",
|
238
|
+
ceramic_urn: "urn:ceramic:bowl:bowl2",
|
239
|
+
ceramic_details: RootMappingSpec::CeramicDetails.new(
|
240
|
+
name: "18th Century Bowl",
|
241
|
+
insignia: "Ming Wanli",
|
242
|
+
),
|
243
|
+
)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "parses" do
|
247
|
+
expect(parsed.ceramics.count).to eq(2)
|
248
|
+
# Because Tomlib reverses the order of the hash, so can not check based on position
|
249
|
+
expect(parsed.ceramics).to include(vase_with_details)
|
250
|
+
expect(parsed.ceramics).to include(bowl_with_details)
|
251
|
+
end
|
252
|
+
|
253
|
+
describe "serialize from object" do
|
254
|
+
let(:collection) do
|
255
|
+
RootMappingSpec::CeramicCollectionWithKeyAndComplexValue.new(ceramics: [
|
256
|
+
vase_with_details,
|
257
|
+
bowl_with_details,
|
258
|
+
])
|
259
|
+
end
|
260
|
+
|
261
|
+
it "serializes correctly" do
|
262
|
+
expect(collection.public_send(:"to_#{format}")).to eq(input)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context "when `collection: true` is missing" do
|
268
|
+
let(:input_hash) do
|
269
|
+
{
|
270
|
+
"vase1" => { "name" => "Imperial Vase" },
|
271
|
+
"bowl2" => { "name" => "18th Century Bowl" },
|
272
|
+
}
|
273
|
+
end
|
274
|
+
|
275
|
+
it "raises error" do
|
276
|
+
expect do
|
277
|
+
RootMappingSpec::CeramicCollectionWithoutCollectionTrue.public_send(:"from_#{format}", input)
|
278
|
+
end.to raise_error(
|
279
|
+
Lutaml::Model::CollectionTrueMissingError,
|
280
|
+
"May be `collection: true` is missing for `ceramics` in RootMappingSpec::CeramicCollectionWithoutCollectionTrue",
|
281
|
+
)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
describe Lutaml::Model::YamlAdapter::StandardYamlAdapter do
|
287
|
+
it_behaves_like "having root mappings", :yaml
|
288
|
+
end
|
289
|
+
|
290
|
+
describe Lutaml::Model::JsonAdapter::StandardJsonAdapter do
|
291
|
+
it_behaves_like "having root mappings", :json
|
292
|
+
end
|
293
|
+
|
294
|
+
describe Lutaml::Model::TomlAdapter::TomlRbAdapter do
|
295
|
+
it_behaves_like "having root mappings", :toml
|
296
|
+
end
|
297
|
+
end
|