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.
@@ -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
- type.public_send(:"as_#{format}", value, options)
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
@@ -0,0 +1,6 @@
1
+ module Lutaml
2
+ module Model
3
+ class MultipleMappingsError < Error
4
+ end
5
+ end
6
+ end
@@ -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
- validate!(name, to, with)
22
+ mapping_name = name_for_mapping(root_mappings, name)
23
+ validate!(mapping_name, to, with)
24
+
22
25
  @mappings << KeyValueMappingRule.new(
23
- name,
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
- id: nil
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
- id: nil
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
- @id = id
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 apply_child_mappings(hash, child_mappings)
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
- [attr_name, attr_value]
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 = child_obj.send(attr_name)
382
+ map_key = attr_value
346
383
  elsif path == :value
347
- map_value = child_obj.send(attr_name)
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, child_obj.send(attr_name))
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 "May be `collection: true` is missing for #{self} in #{options[:caller_class]}"
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, _options = {})
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 doc.key?(rule_name.to_s)
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 = apply_child_mappings(value, rule.child_mappings)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.5.0"
5
+ VERSION = "0.5.2"
6
6
  end
7
7
  end
@@ -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?
data/lib/lutaml/model.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "model/version"
4
+ require_relative "model/loggable"
4
5
  require_relative "model/type"
5
6
  require_relative "model/utils"
6
7
  require_relative "model/serializable"