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