lutaml-model 0.5.0 → 0.5.2
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 +4 -4
- data/README.adoc +837 -184
- data/lib/lutaml/model/attribute.rb +9 -3
- 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 +26 -4
- data/lib/lutaml/model/key_value_mapping_rule.rb +15 -4
- data/lib/lutaml/model/loggable.rb +15 -0
- data/lib/lutaml/model/mapping_rule.rb +2 -2
- data/lib/lutaml/model/serialize.rb +59 -15
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +3 -3
- data/lib/lutaml/model/xml_mapping.rb +4 -0
- data/lib/lutaml/model/xml_mapping_rule.rb +2 -5
- data/lib/lutaml/model.rb +1 -0
- data/spec/lutaml/model/root_mappings_spec.rb +297 -0
- data/spec/lutaml/model/serializable_spec.rb +41 -6
- data/spec/lutaml/model/with_child_mapping_spec.rb +182 -0
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +66 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +8 -0
- metadata +9 -5
@@ -142,7 +142,7 @@ module Lutaml
|
|
142
142
|
# Use the default value if the value is nil
|
143
143
|
value = default if value.nil?
|
144
144
|
|
145
|
-
valid_value!(value) && valid_collection!(value) && valid_pattern!(value)
|
145
|
+
valid_value!(value) && valid_collection!(value, self) && valid_pattern!(value)
|
146
146
|
end
|
147
147
|
|
148
148
|
def validate_collection_range
|
@@ -169,7 +169,9 @@ module Lutaml
|
|
169
169
|
end
|
170
170
|
end
|
171
171
|
|
172
|
-
def valid_collection!(value)
|
172
|
+
def valid_collection!(value, caller)
|
173
|
+
raise Lutaml::Model::CollectionTrueMissingError.new(name, caller) if value.is_a?(Array) && !collection?
|
174
|
+
|
173
175
|
return true unless collection?
|
174
176
|
|
175
177
|
# Allow nil values for collections during initialization
|
@@ -207,12 +209,16 @@ module Lutaml
|
|
207
209
|
end
|
208
210
|
|
209
211
|
def serialize(value, format, options = {})
|
212
|
+
return if value.nil?
|
213
|
+
|
210
214
|
if value.is_a?(Array)
|
211
215
|
value.map do |v|
|
212
216
|
serialize(v, format, options)
|
213
217
|
end
|
214
218
|
elsif type <= Serialize
|
215
|
-
|
219
|
+
if Utils.present?(value)
|
220
|
+
type.public_send(:"as_#{format}", value, options)
|
221
|
+
end
|
216
222
|
else
|
217
223
|
# Convert to Value instance if not already
|
218
224
|
value = type.new(value) unless value.is_a?(Type::Value)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
class CollectionTrueMissingError < Error
|
4
|
+
def initialize(attr_name, caller_class)
|
5
|
+
@attr_name = attr_name
|
6
|
+
@caller = caller_class
|
7
|
+
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"May be `collection: true` is missing for `#{@attr_name}` in #{@caller}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/lutaml/model/error.rb
CHANGED
@@ -14,3 +14,5 @@ require_relative "error/validation_error"
|
|
14
14
|
require_relative "error/type_not_enabled_error"
|
15
15
|
require_relative "error/type_error"
|
16
16
|
require_relative "error/unknown_type_error"
|
17
|
+
require_relative "error/multiple_mappings_error"
|
18
|
+
require_relative "error/collection_true_missing_error"
|
@@ -10,28 +10,38 @@ module Lutaml
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def map(
|
13
|
-
name,
|
13
|
+
name = nil,
|
14
14
|
to: nil,
|
15
15
|
render_nil: false,
|
16
16
|
render_default: false,
|
17
17
|
with: {},
|
18
18
|
delegate: nil,
|
19
|
-
child_mappings: nil
|
19
|
+
child_mappings: nil,
|
20
|
+
root_mappings: nil
|
20
21
|
)
|
21
|
-
|
22
|
+
mapping_name = name_for_mapping(root_mappings, name)
|
23
|
+
validate!(mapping_name, to, with)
|
24
|
+
|
22
25
|
@mappings << KeyValueMappingRule.new(
|
23
|
-
|
26
|
+
mapping_name,
|
24
27
|
to: to,
|
25
28
|
render_nil: render_nil,
|
26
29
|
render_default: render_default,
|
27
30
|
with: with,
|
28
31
|
delegate: delegate,
|
29
32
|
child_mappings: child_mappings,
|
33
|
+
root_mappings: root_mappings,
|
30
34
|
)
|
31
35
|
end
|
32
36
|
|
33
37
|
alias map_element map
|
34
38
|
|
39
|
+
def name_for_mapping(root_mappings, name)
|
40
|
+
return "root_mapping" if root_mappings
|
41
|
+
|
42
|
+
name
|
43
|
+
end
|
44
|
+
|
35
45
|
def validate!(key, to, with)
|
36
46
|
if to.nil? && with.empty?
|
37
47
|
msg = ":to or :with argument is required for mapping '#{key}'"
|
@@ -42,6 +52,14 @@ module Lutaml
|
|
42
52
|
msg = ":with argument for mapping '#{key}' requires :to and :from keys"
|
43
53
|
raise IncorrectMappingArgumentsError.new(msg)
|
44
54
|
end
|
55
|
+
|
56
|
+
validate_mappings(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def validate_mappings(name)
|
60
|
+
if @mappings.any?(&:root_mapping?) || (name == "root_mapping" && @mappings.any?)
|
61
|
+
raise MultipleMappingsError.new("root_mappings cannot be used with other mappings")
|
62
|
+
end
|
45
63
|
end
|
46
64
|
|
47
65
|
def deep_dup
|
@@ -53,6 +71,10 @@ module Lutaml
|
|
53
71
|
def duplicate_mappings
|
54
72
|
@mappings.map(&:deep_dup)
|
55
73
|
end
|
74
|
+
|
75
|
+
def find_by_to(to)
|
76
|
+
@mappings.find { |m| m.to.to_s == to.to_s }
|
77
|
+
end
|
56
78
|
end
|
57
79
|
end
|
58
80
|
end
|
@@ -3,7 +3,8 @@ require_relative "mapping_rule"
|
|
3
3
|
module Lutaml
|
4
4
|
module Model
|
5
5
|
class KeyValueMappingRule < MappingRule
|
6
|
-
attr_reader :child_mappings
|
6
|
+
attr_reader :child_mappings,
|
7
|
+
:root_mappings
|
7
8
|
|
8
9
|
def initialize(
|
9
10
|
name,
|
@@ -13,7 +14,7 @@ module Lutaml
|
|
13
14
|
with: {},
|
14
15
|
delegate: nil,
|
15
16
|
child_mappings: nil,
|
16
|
-
|
17
|
+
root_mappings: nil
|
17
18
|
)
|
18
19
|
super(
|
19
20
|
name,
|
@@ -21,11 +22,17 @@ module Lutaml
|
|
21
22
|
render_nil: render_nil,
|
22
23
|
render_default: render_default,
|
23
24
|
with: with,
|
24
|
-
delegate: delegate
|
25
|
-
id: id
|
25
|
+
delegate: delegate
|
26
26
|
)
|
27
27
|
|
28
28
|
@child_mappings = child_mappings
|
29
|
+
@root_mappings = root_mappings
|
30
|
+
end
|
31
|
+
|
32
|
+
def hash_mappings
|
33
|
+
return @root_mappings if @root_mappings
|
34
|
+
|
35
|
+
@child_mappings
|
29
36
|
end
|
30
37
|
|
31
38
|
def deep_dup
|
@@ -38,6 +45,10 @@ module Lutaml
|
|
38
45
|
child_mappings: Utils.deep_dup(child_mappings),
|
39
46
|
)
|
40
47
|
end
|
48
|
+
|
49
|
+
def root_mapping?
|
50
|
+
name == "root_mapping"
|
51
|
+
end
|
41
52
|
end
|
42
53
|
end
|
43
54
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
module Loggable
|
4
|
+
def self.included(base)
|
5
|
+
base.define_method :warn_auto_handling do |name|
|
6
|
+
caller_file = File.basename(caller_locations(2, 1)[0].path)
|
7
|
+
caller_line = caller_locations(2, 1)[0].lineno
|
8
|
+
|
9
|
+
str = "[Lutaml::Model] WARN: `#{name}` is handled by default. No need to explecitly define at `#{caller_file}:#{caller_line}`"
|
10
|
+
warn(str)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -17,7 +17,7 @@ module Lutaml
|
|
17
17
|
with: {},
|
18
18
|
attribute: false,
|
19
19
|
delegate: nil,
|
20
|
-
|
20
|
+
root_mappings: nil
|
21
21
|
)
|
22
22
|
@name = name
|
23
23
|
@to = to
|
@@ -26,7 +26,7 @@ module Lutaml
|
|
26
26
|
@custom_methods = with
|
27
27
|
@attribute = attribute
|
28
28
|
@delegate = delegate
|
29
|
-
@
|
29
|
+
@root_mappings = root_mappings
|
30
30
|
end
|
31
31
|
|
32
32
|
alias from name
|
@@ -271,8 +271,10 @@ module Lutaml
|
|
271
271
|
|
272
272
|
attribute = attributes[name]
|
273
273
|
|
274
|
+
next hash.merge!(generate_hash_from_child_mappings(value, format, rule.root_mappings)) if rule.root_mapping?
|
275
|
+
|
274
276
|
value = if rule.child_mappings
|
275
|
-
generate_hash_from_child_mappings(value, rule.child_mappings)
|
277
|
+
generate_hash_from_child_mappings(value, format, rule.child_mappings)
|
276
278
|
else
|
277
279
|
attribute.serialize(value, format, options)
|
278
280
|
end
|
@@ -313,11 +315,11 @@ module Lutaml
|
|
313
315
|
end
|
314
316
|
end
|
315
317
|
|
316
|
-
def
|
318
|
+
def translate_mappings(hash, child_mappings, attr, format)
|
317
319
|
return hash unless child_mappings
|
318
320
|
|
319
321
|
hash.map do |key, value|
|
320
|
-
child_mappings.to_h do |attr_name, path|
|
322
|
+
child_hash = child_mappings.to_h do |attr_name, path|
|
321
323
|
attr_value = if path == :key
|
322
324
|
key
|
323
325
|
elsif path == :value
|
@@ -327,32 +329,68 @@ module Lutaml
|
|
327
329
|
value.dig(*path.map(&:to_s))
|
328
330
|
end
|
329
331
|
|
330
|
-
|
332
|
+
attr_rule = attr.type.mappings_for(format).find_by_to(attr_name)
|
333
|
+
[attr_rule.from.to_s, attr_value]
|
334
|
+
end
|
335
|
+
|
336
|
+
if child_mappings.values == [:key] && hash.values.all?(Hash)
|
337
|
+
child_hash.merge!(value)
|
331
338
|
end
|
339
|
+
|
340
|
+
attr.type.apply_hash_mapping(
|
341
|
+
child_hash,
|
342
|
+
attr.type.model.new,
|
343
|
+
format,
|
344
|
+
{ mappings: attr.type.mappings_for(format).mappings },
|
345
|
+
)
|
332
346
|
end
|
333
347
|
end
|
334
348
|
|
335
|
-
def generate_hash_from_child_mappings(value, child_mappings)
|
349
|
+
def generate_hash_from_child_mappings(value, format, child_mappings)
|
336
350
|
return value unless child_mappings
|
337
351
|
|
338
352
|
hash = {}
|
339
353
|
|
354
|
+
if child_mappings.values == [:key]
|
355
|
+
klass = value.first.class
|
356
|
+
mappings = klass.mappings_for(format)
|
357
|
+
|
358
|
+
klass.attributes.each_key do |name|
|
359
|
+
next if child_mappings.key?(name.to_sym) || child_mappings.key?(name.to_s)
|
360
|
+
|
361
|
+
child_mappings[name.to_sym] = mappings.find_by_to(name)&.name.to_s || name.to_s
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
340
365
|
value.each do |child_obj|
|
341
366
|
map_key = nil
|
342
367
|
map_value = {}
|
343
368
|
child_mappings.each do |attr_name, path|
|
369
|
+
attr_value = child_obj.send(attr_name)
|
370
|
+
|
371
|
+
attr_value = if attr_value.is_a?(Lutaml::Model::Serialize)
|
372
|
+
attr_value.to_yaml_hash
|
373
|
+
elsif attr_value.is_a?(Array) && attr_value.first.is_a?(Lutaml::Model::Serialize)
|
374
|
+
attr_value.map(&:to_yaml_hash)
|
375
|
+
else
|
376
|
+
attr_value
|
377
|
+
end
|
378
|
+
|
379
|
+
next if Utils.blank?(attr_value)
|
380
|
+
|
344
381
|
if path == :key
|
345
|
-
map_key =
|
382
|
+
map_key = attr_value
|
346
383
|
elsif path == :value
|
347
|
-
map_value =
|
384
|
+
map_value = attr_value
|
348
385
|
else
|
349
386
|
path = [path] unless path.is_a?(Array)
|
350
387
|
path[0...-1].inject(map_value) do |acc, k|
|
351
388
|
acc[k.to_s] ||= {}
|
352
|
-
end.public_send(:[]=, path.last.to_s,
|
389
|
+
end.public_send(:[]=, path.last.to_s, attr_value)
|
353
390
|
end
|
354
391
|
end
|
355
392
|
|
393
|
+
map_value = nil if map_value.empty?
|
356
394
|
hash[map_key] = map_value
|
357
395
|
end
|
358
396
|
|
@@ -380,12 +418,15 @@ module Lutaml
|
|
380
418
|
def apply_mappings(doc, format, options = {})
|
381
419
|
instance = options[:instance] || model.new
|
382
420
|
return instance if Utils.blank?(doc)
|
421
|
+
|
422
|
+
options[:mappings] = mappings_for(format).mappings
|
383
423
|
return apply_xml_mapping(doc, instance, options) if format == :xml
|
384
424
|
|
385
425
|
apply_hash_mapping(doc, instance, format, options)
|
386
426
|
end
|
387
427
|
|
388
428
|
def apply_xml_mapping(doc, instance, options = {})
|
429
|
+
options = Utils.deep_dup(options)
|
389
430
|
instance.encoding = options[:encoding]
|
390
431
|
return instance unless doc
|
391
432
|
|
@@ -393,10 +434,10 @@ module Lutaml
|
|
393
434
|
options[:default_namespace] =
|
394
435
|
mappings_for(:xml)&.namespace_uri
|
395
436
|
end
|
396
|
-
mappings = mappings_for(:xml).mappings
|
437
|
+
mappings = options[:mappings] || mappings_for(:xml).mappings
|
397
438
|
|
398
439
|
if doc.is_a?(Array)
|
399
|
-
raise
|
440
|
+
raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class])
|
400
441
|
end
|
401
442
|
|
402
443
|
if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
|
@@ -443,8 +484,8 @@ module Lutaml
|
|
443
484
|
instance
|
444
485
|
end
|
445
486
|
|
446
|
-
def apply_hash_mapping(doc, instance, format,
|
447
|
-
mappings = mappings_for(format).mappings
|
487
|
+
def apply_hash_mapping(doc, instance, format, options = {})
|
488
|
+
mappings = options[:mappings] || mappings_for(format).mappings
|
448
489
|
mappings.each do |rule|
|
449
490
|
raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
|
450
491
|
|
@@ -453,7 +494,9 @@ module Lutaml
|
|
453
494
|
names = rule.multiple_mappings? ? rule.name : [rule.name]
|
454
495
|
|
455
496
|
value = names.collect do |rule_name|
|
456
|
-
if
|
497
|
+
if rule.root_mapping?
|
498
|
+
doc
|
499
|
+
elsif doc.key?(rule_name.to_s)
|
457
500
|
doc[rule_name.to_s]
|
458
501
|
elsif doc.key?(rule_name.to_sym)
|
459
502
|
doc[rule_name.to_sym]
|
@@ -470,8 +513,9 @@ module Lutaml
|
|
470
513
|
next
|
471
514
|
end
|
472
515
|
|
473
|
-
value =
|
474
|
-
value = attr.cast(value, format)
|
516
|
+
value = translate_mappings(value, rule.hash_mappings, attr, format)
|
517
|
+
value = attr.cast(value, format) unless rule.hash_mappings
|
518
|
+
attr.valid_collection!(value, self)
|
475
519
|
|
476
520
|
rule.deserialize(instance, value, attributes, self)
|
477
521
|
end
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -109,9 +109,9 @@ module Lutaml
|
|
109
109
|
prefix: attr.namespace_prefix,
|
110
110
|
schema_location: attr.value,
|
111
111
|
}
|
112
|
-
else
|
113
|
-
result[attr.namespaced_name] = attr.value
|
114
112
|
end
|
113
|
+
|
114
|
+
result[attr.namespaced_name] = attr.value
|
115
115
|
end
|
116
116
|
|
117
117
|
result
|
@@ -328,7 +328,7 @@ module Lutaml
|
|
328
328
|
{}
|
329
329
|
end
|
330
330
|
|
331
|
-
if element.respond_to?(:schema_location) && element.schema_location && !options[:except]&.include?(:schema_location)
|
331
|
+
if element.respond_to?(:schema_location) && element.schema_location.is_a?(Lutaml::Model::SchemaLocation) && !options[:except]&.include?(:schema_location)
|
332
332
|
attrs.merge!(element.schema_location.to_xml_attributes)
|
333
333
|
end
|
334
334
|
|
@@ -3,6 +3,8 @@ require_relative "xml_mapping_rule"
|
|
3
3
|
module Lutaml
|
4
4
|
module Model
|
5
5
|
class XmlMapping
|
6
|
+
include Lutaml::Model::Loggable
|
7
|
+
|
6
8
|
TYPES = {
|
7
9
|
attribute: :map_attribute,
|
8
10
|
element: :map_element,
|
@@ -92,6 +94,8 @@ module Lutaml
|
|
92
94
|
nil)
|
93
95
|
)
|
94
96
|
validate!(name, to, with, type: TYPES[:attribute])
|
97
|
+
warn_auto_handling(name) if name == "schemaLocation"
|
98
|
+
|
95
99
|
rule = XmlMappingRule.new(
|
96
100
|
name,
|
97
101
|
to: to,
|
@@ -19,8 +19,7 @@ module Lutaml
|
|
19
19
|
namespace_set: false,
|
20
20
|
prefix_set: false,
|
21
21
|
attribute: false,
|
22
|
-
default_namespace: nil
|
23
|
-
id: nil
|
22
|
+
default_namespace: nil
|
24
23
|
)
|
25
24
|
super(
|
26
25
|
name,
|
@@ -29,8 +28,7 @@ module Lutaml
|
|
29
28
|
render_default: render_default,
|
30
29
|
with: with,
|
31
30
|
delegate: delegate,
|
32
|
-
attribute: attribute
|
33
|
-
id: id
|
31
|
+
attribute: attribute
|
34
32
|
)
|
35
33
|
|
36
34
|
@namespace = if namespace.to_s == "inherit"
|
@@ -47,7 +45,6 @@ module Lutaml
|
|
47
45
|
|
48
46
|
@namespace_set = namespace_set
|
49
47
|
@prefix_set = prefix_set
|
50
|
-
@id = id
|
51
48
|
end
|
52
49
|
|
53
50
|
def namespace_set?
|