lutaml-model 0.5.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.
@@ -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,6 +209,8 @@ 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)
@@ -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,59 @@ 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
+ attr_value = attr_value.to_yaml_hash if attr_value.is_a?(Lutaml::Model::Serialize)
371
+
344
372
  if path == :key
345
- map_key = child_obj.send(attr_name)
373
+ map_key = attr_value
346
374
  elsif path == :value
347
- map_value = child_obj.send(attr_name)
375
+ map_value = attr_value
348
376
  else
349
377
  path = [path] unless path.is_a?(Array)
350
378
  path[0...-1].inject(map_value) do |acc, k|
351
379
  acc[k.to_s] ||= {}
352
- end.public_send(:[]=, path.last.to_s, child_obj.send(attr_name))
380
+ end.public_send(:[]=, path.last.to_s, attr_value)
353
381
  end
354
382
  end
355
383
 
384
+ map_value = nil if map_value.empty?
356
385
  hash[map_key] = map_value
357
386
  end
358
387
 
@@ -380,12 +409,15 @@ module Lutaml
380
409
  def apply_mappings(doc, format, options = {})
381
410
  instance = options[:instance] || model.new
382
411
  return instance if Utils.blank?(doc)
412
+
413
+ options[:mappings] = mappings_for(format).mappings
383
414
  return apply_xml_mapping(doc, instance, options) if format == :xml
384
415
 
385
416
  apply_hash_mapping(doc, instance, format, options)
386
417
  end
387
418
 
388
419
  def apply_xml_mapping(doc, instance, options = {})
420
+ options = Utils.deep_dup(options)
389
421
  instance.encoding = options[:encoding]
390
422
  return instance unless doc
391
423
 
@@ -393,10 +425,10 @@ module Lutaml
393
425
  options[:default_namespace] =
394
426
  mappings_for(:xml)&.namespace_uri
395
427
  end
396
- mappings = mappings_for(:xml).mappings
428
+ mappings = options[:mappings] || mappings_for(:xml).mappings
397
429
 
398
430
  if doc.is_a?(Array)
399
- raise "May be `collection: true` is missing for #{self} in #{options[:caller_class]}"
431
+ raise Lutaml::Model::CollectionTrueMissingError(self, option[:caller_class])
400
432
  end
401
433
 
402
434
  if instance.respond_to?(:ordered=) && doc.is_a?(Lutaml::Model::MappingHash)
@@ -443,8 +475,8 @@ module Lutaml
443
475
  instance
444
476
  end
445
477
 
446
- def apply_hash_mapping(doc, instance, format, _options = {})
447
- mappings = mappings_for(format).mappings
478
+ def apply_hash_mapping(doc, instance, format, options = {})
479
+ mappings = options[:mappings] || mappings_for(format).mappings
448
480
  mappings.each do |rule|
449
481
  raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
450
482
 
@@ -453,7 +485,9 @@ module Lutaml
453
485
  names = rule.multiple_mappings? ? rule.name : [rule.name]
454
486
 
455
487
  value = names.collect do |rule_name|
456
- if doc.key?(rule_name.to_s)
488
+ if rule.root_mapping?
489
+ doc
490
+ elsif doc.key?(rule_name.to_s)
457
491
  doc[rule_name.to_s]
458
492
  elsif doc.key?(rule_name.to_sym)
459
493
  doc[rule_name.to_sym]
@@ -470,8 +504,9 @@ module Lutaml
470
504
  next
471
505
  end
472
506
 
473
- value = apply_child_mappings(value, rule.child_mappings)
474
- value = attr.cast(value, format)
507
+ value = translate_mappings(value, rule.hash_mappings, attr, format)
508
+ value = attr.cast(value, format) unless rule.hash_mappings
509
+ attr.valid_collection!(value, self)
475
510
 
476
511
  rule.deserialize(instance, value, attributes, self)
477
512
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.5.0"
5
+ VERSION = "0.5.1"
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"