lutaml-model 0.3.8 → 0.3.10

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.
@@ -0,0 +1,29 @@
1
+ module Lutaml
2
+ module Model
3
+ class CollectionCountOutOfRangeError < Error
4
+ def initialize(attr_name, value, range)
5
+ @attr_name = attr_name
6
+ @value = value
7
+ @range = range
8
+
9
+ super()
10
+ end
11
+
12
+ def to_s
13
+ "#{@attr_name} count is #{@value.size}, must be #{range_to_string}"
14
+ end
15
+
16
+ private
17
+
18
+ def range_to_string
19
+ if @range.end.nil?
20
+ "at least #{@range.begin}"
21
+ elsif @range.begin == @range.end
22
+ "exactly #{@range.begin}"
23
+ else
24
+ "between #{@range.begin} and #{@range.end}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # lib/lutaml/model/error/validation_error.rb
2
+ module Lutaml
3
+ module Model
4
+ class ValidationError < Error
5
+ attr_reader :errors
6
+
7
+ def initialize(errors)
8
+ @errors = errors
9
+ super(errors.join(", "))
10
+ end
11
+
12
+ def include?(error_class)
13
+ errors.any?(error_class)
14
+ end
15
+
16
+ def error_messages
17
+ errors.map(&:message)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -7,3 +7,5 @@ end
7
7
 
8
8
  require_relative "error/invalid_value_error"
9
9
  require_relative "error/unknown_adapter_type_error"
10
+ require_relative "error/collection_count_out_of_range_error"
11
+ require_relative "error/validation_error"
@@ -0,0 +1,59 @@
1
+ module Lutaml
2
+ module Model
3
+ class Location
4
+ attr_reader :namespace, :location
5
+
6
+ def initialize(namespace:, location:)
7
+ @namespace = namespace
8
+ @location = location
9
+ end
10
+
11
+ def to_xml_attribute
12
+ "#{@namespace} #{@location}".strip
13
+ end
14
+ end
15
+
16
+ class SchemaLocation
17
+ DEFAULT_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance".freeze
18
+
19
+ attr_reader :namespace, :prefix, :schema_location
20
+
21
+ def initialize(schema_location:, prefix: "xsi",
22
+ namespace: DEFAULT_NAMESPACE)
23
+ @original_schema_location = schema_location
24
+ @schema_location = parsed_schema_locations(schema_location)
25
+ @prefix = prefix
26
+ @namespace = namespace
27
+ end
28
+
29
+ def to_xml_attributes
30
+ {
31
+ "xmlns:#{prefix}" => namespace,
32
+ "#{prefix}:schemaLocation" => schema_location.map(&:to_xml_attribute).join(" "),
33
+ }
34
+ end
35
+
36
+ def [](index)
37
+ @schema_location[index]
38
+ end
39
+
40
+ def size
41
+ @schema_location.size
42
+ end
43
+
44
+ private
45
+
46
+ def parsed_schema_locations(schema_location)
47
+ locations = if schema_location.is_a?(Hash)
48
+ schema_location
49
+ else
50
+ schema_location.split.each_slice(2)
51
+ end
52
+
53
+ locations.map do |n, l|
54
+ Location.new(namespace: n, location: l)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -9,11 +9,14 @@ require_relative "xml_mapping"
9
9
  require_relative "key_value_mapping"
10
10
  require_relative "json_adapter"
11
11
  require_relative "comparable_model"
12
+ require_relative "schema_location"
13
+ require_relative "validation"
12
14
 
13
15
  module Lutaml
14
16
  module Model
15
17
  module Serialize
16
18
  include ComparableModel
19
+ include Validation
17
20
 
18
21
  def self.included(base)
19
22
  base.extend(ClassMethods)
@@ -52,25 +55,10 @@ module Lutaml
52
55
 
53
56
  define_method(:"#{name}=") do |value|
54
57
  instance_variable_set(:"@#{name}", value)
55
- validate
58
+ # validate!(name)
56
59
  end
57
60
  end
58
61
 
59
- # Check if the value to be assigned is valid for the attribute
60
- def attr_value_valid?(name, value)
61
- attr = attributes[name]
62
-
63
- return true unless attr.options[:values]
64
-
65
- # Allow nil values if there's no default
66
- return true if value.nil? && !attr.default
67
-
68
- # Use the default value if the value is nil
69
- value = attr.default if value.nil?
70
-
71
- attr.options[:values].include?(value)
72
- end
73
-
74
62
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
75
63
  define_method(format) do |&block|
76
64
  klass = format == :xml ? XmlMapping : KeyValueMapping
@@ -92,8 +80,8 @@ module Lutaml
92
80
  define_method(:"of_#{format}") do |hash|
93
81
  if hash.is_a?(Array)
94
82
  return hash.map do |item|
95
- apply_mappings(item, format)
96
- end
83
+ apply_mappings(item, format)
84
+ end
97
85
  end
98
86
 
99
87
  apply_mappings(hash, format)
@@ -276,7 +264,10 @@ module Lutaml
276
264
  end
277
265
 
278
266
  if rule.custom_methods[:from]
279
- value = new.send(rule.custom_methods[:from], instance, value) if value && !value.empty?
267
+ if value && !value.empty?
268
+ value = new.send(rule.custom_methods[:from], instance,
269
+ value)
270
+ end
280
271
  next
281
272
  end
282
273
 
@@ -313,6 +304,14 @@ module Lutaml
313
304
  instance.ordered = mappings_for(:xml).mixed_content? || options[:mixed_content]
314
305
  end
315
306
 
307
+ if doc["__schema_location"]
308
+ instance.schema_location = Lutaml::Model::SchemaLocation.new(
309
+ schema_location: doc["__schema_location"][:schema_location],
310
+ prefix: doc["__schema_location"][:prefix],
311
+ namespace: doc["__schema_location"][:namespace],
312
+ )
313
+ end
314
+
316
315
  mappings.each do |rule|
317
316
  attr = attributes[rule.to]
318
317
  raise "Attribute '#{rule.to}' not found in #{self}" unless attr
@@ -373,9 +372,11 @@ module Lutaml
373
372
  end
374
373
  end
375
374
 
376
- attr_accessor :element_order
375
+ attr_accessor :element_order, :schema_location
377
376
 
378
377
  def initialize(attrs = {})
378
+ @validate_on_set = attrs.delete(:validate_on_set) || false
379
+
379
380
  return unless self.class.attributes
380
381
 
381
382
  if attrs.is_a?(Lutaml::Model::MappingHash)
@@ -383,13 +384,41 @@ module Lutaml
383
384
  @element_order = attrs.item_order
384
385
  end
385
386
 
387
+ if attrs.key?(:schema_location)
388
+ self.schema_location = attrs[:schema_location]
389
+ end
390
+
386
391
  self.class.attributes.each do |name, attr|
387
- value = self.class.attr_value(attrs, name, attr)
392
+ value = if attrs.key?(name) || attrs.key?(name.to_s)
393
+ self.class.attr_value(attrs, name, attr)
394
+ else
395
+ attr.default
396
+ end
397
+
398
+ # Initialize collections with an empty array if no value is provided
399
+ if attr.collection? && value.nil?
400
+ value = []
401
+ end
388
402
 
389
- send(:"#{name}=", self.class.ensure_utf8(value))
403
+ instance_variable_set(:"@#{name}", self.class.ensure_utf8(value))
404
+ end
405
+ end
406
+
407
+ def method_missing(method_name, *args)
408
+ if method_name.to_s.end_with?("=") && self.class.attributes.key?(method_name.to_s.chomp("=").to_sym)
409
+ define_singleton_method(method_name) do |value|
410
+ instance_variable_set(:"@#{method_name.to_s.chomp('=')}", value)
411
+ end
412
+ send(method_name, *args)
413
+ else
414
+ super
390
415
  end
416
+ end
391
417
 
392
- validate
418
+ def validate_attribute!(attr_name)
419
+ attr = self.class.attributes[attr_name]
420
+ value = instance_variable_get(:"@#{attr_name}")
421
+ attr.validate_value!(value)
393
422
  end
394
423
 
395
424
  def ordered?
@@ -410,7 +439,7 @@ module Lutaml
410
439
 
411
440
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
412
441
  define_method(:"to_#{format}") do |options = {}|
413
- validate
442
+ validate!
414
443
  adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
415
444
  representation = if format == :xml
416
445
  self
@@ -422,16 +451,6 @@ module Lutaml
422
451
  adapter.new(representation).public_send(:"to_#{format}", options)
423
452
  end
424
453
  end
425
-
426
- def validate
427
- self.class.attributes.each do |name, attr|
428
- value = send(name)
429
- unless self.class.attr_value_valid?(name, value)
430
- raise Lutaml::Model::InvalidValueError.new(name, value,
431
- attr.options[:values])
432
- end
433
- end
434
- end
435
454
  end
436
455
  end
437
456
  end
@@ -0,0 +1,24 @@
1
+ module Lutaml
2
+ module Model
3
+ module Validation
4
+ def validate
5
+ errors = []
6
+ self.class.attributes.each do |name, attr|
7
+ value = instance_variable_get(:"@#{name}")
8
+ begin
9
+ attr.validate_value!(value)
10
+ rescue Lutaml::Model::InvalidValueError,
11
+ Lutaml::Model::CollectionCountOutOfRangeError => e
12
+ errors << e
13
+ end
14
+ end
15
+ errors
16
+ end
17
+
18
+ def validate!
19
+ errors = validate
20
+ raise Lutaml::Model::ValidationError.new(errors) if errors.any?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.3.8"
5
+ VERSION = "0.3.10"
6
6
  end
7
7
  end
@@ -17,9 +17,14 @@ module Lutaml
17
17
 
18
18
  def initialize(xml)
19
19
  @xml = xml
20
+ @current_namespace = nil
20
21
  end
21
22
 
22
23
  def create_element(name, attributes = {})
24
+ if @current_namespace && !name.start_with?("#{@current_namespace}:")
25
+ name = "#{@current_namespace}:#{name}"
26
+ end
27
+
23
28
  if block_given?
24
29
  xml.element(name, attributes) do |element|
25
30
  yield(self.class.new(element))
@@ -36,6 +41,8 @@ module Lutaml
36
41
  def create_and_add_element(element_name, prefix: nil, attributes: {})
37
42
  prefixed_name = if prefix
38
43
  "#{prefix}:#{element_name}"
44
+ elsif @current_namespace && !element_name.start_with?("#{@current_namespace}:")
45
+ "#{@current_namespace}:#{element_name}"
39
46
  else
40
47
  element_name
41
48
  end
@@ -47,6 +54,8 @@ module Lutaml
47
54
  else
48
55
  xml.element(prefixed_name, attributes)
49
56
  end
57
+
58
+ @current_namespace = nil
50
59
  end
51
60
 
52
61
  def <<(text)
@@ -59,9 +68,10 @@ module Lutaml
59
68
 
60
69
  # Add XML namespace to document
61
70
  #
62
- # Ox doesn't support XML namespaces so this method does nothing.
63
- def add_namespace_prefix(_prefix)
64
- # :noop:
71
+ # Ox doesn't support XML namespaces so we only save the
72
+ # current namespace prefix to add it to the element's name later.
73
+ def add_namespace_prefix(prefix)
74
+ @current_namespace = prefix
65
75
  self
66
76
  end
67
77
 
@@ -20,6 +20,8 @@ module Lutaml
20
20
  elsif ordered?(@root, options)
21
21
  build_ordered_element(builder, @root, options)
22
22
  else
23
+ mapper_class = options[:mapper_class] || @root.class
24
+ options[:xml_attributes] = build_namespace_attributes(mapper_class)
23
25
  build_element(builder, @root, options)
24
26
  end
25
27
 
@@ -46,6 +48,7 @@ module Lutaml
46
48
  curr_index = index_hash[name] += 1
47
49
 
48
50
  element_rule = xml_mapping.find_by_name(name)
51
+ next if element_rule.nil?
49
52
 
50
53
  attribute_def = attribute_definition_for(element, element_rule,
51
54
  mapper_class: mapper_class)
@@ -77,16 +77,23 @@ module Lutaml
77
77
  element.children.each_with_object(result) do |child, hash|
78
78
  value = child.text? ? child.text : parse_element(child)
79
79
 
80
- if hash[child.unprefixed_name]
81
- hash[child.unprefixed_name] =
82
- [hash[child.unprefixed_name], value].flatten
83
- else
84
- hash[child.unprefixed_name] = value
85
- end
80
+ hash[child.unprefixed_name] = if hash[child.unprefixed_name]
81
+ [hash[child.unprefixed_name], value].flatten
82
+ else
83
+ value
84
+ end
86
85
  end
87
86
 
88
87
  element.attributes.each_value do |attr|
89
- result[attr.unprefixed_name] = attr.value
88
+ if attr.unprefixed_name == "schemaLocation"
89
+ result["__schema_location"] = {
90
+ namespace: attr.namespace,
91
+ prefix: attr.namespace_prefix,
92
+ schema_location: attr.value,
93
+ }
94
+ else
95
+ result[attr.unprefixed_name] = attr.value
96
+ end
90
97
  end
91
98
 
92
99
  result
@@ -150,6 +157,9 @@ module Lutaml
150
157
  attributes = options[:xml_attributes] ||= {}
151
158
  attributes = build_attributes(element,
152
159
  xml_mapping, options).merge(attributes)&.compact
160
+ if element.respond_to?(:schema_location) && element.schema_location
161
+ attributes.merge!(element.schema_location.to_xml_attributes)
162
+ end
153
163
 
154
164
  prefix = if options.key?(:namespace_prefix)
155
165
  options[:namespace_prefix]
@@ -160,10 +170,10 @@ module Lutaml
160
170
  prefixed_xml = xml.add_namespace_prefix(prefix)
161
171
  tag_name = options[:tag_name] || xml_mapping.root_element
162
172
 
163
- xml.create_and_add_element(tag_name, prefix: prefix,
164
- attributes: attributes) do
173
+ prefixed_xml.create_and_add_element(tag_name, prefix: prefix,
174
+ attributes: attributes) do
165
175
  if options.key?(:namespace_prefix) && !options[:namespace_prefix]
166
- xml.add_namespace_prefix(nil)
176
+ prefixed_xml.add_namespace_prefix(nil)
167
177
  end
168
178
 
169
179
  xml_mapping.elements.each do |element_rule|
@@ -177,7 +187,7 @@ module Lutaml
177
187
  value = [value] if attribute_def.collection? && !value.is_a?(Array)
178
188
 
179
189
  add_to_xml(
180
- xml,
190
+ prefixed_xml,
181
191
  element_rule.prefix,
182
192
  value,
183
193
  options.merge({ attribute: attribute_def, rule: element_rule }),
@@ -212,9 +222,11 @@ module Lutaml
212
222
 
213
223
  attrs = {}
214
224
 
215
- if xml_mappings.namespace_prefix
216
- attrs["xmlns:#{xml_mappings.namespace_prefix}"] =
217
- xml_mappings.namespace_uri
225
+ if xml_mappings.namespace_uri
226
+ prefixed_name = ["xmlns",
227
+ xml_mappings.namespace_prefix].compact.join(":")
228
+
229
+ attrs[prefixed_name] = xml_mappings.namespace_uri
218
230
  end
219
231
 
220
232
  xml_mappings.mappings.each do |mapping_rule|
@@ -44,8 +44,7 @@ module Lutaml
44
44
  delegate: nil,
45
45
  namespace: (namespace_set = false
46
46
  nil),
47
- prefix: nil,
48
- mixed: false
47
+ prefix: nil
49
48
  )
50
49
  @elements[name] = XmlMappingRule.new(
51
50
  name,
@@ -55,7 +54,6 @@ module Lutaml
55
54
  delegate: delegate,
56
55
  namespace: namespace,
57
56
  prefix: prefix,
58
- mixed_content: mixed,
59
57
  namespace_set: namespace_set != false,
60
58
  )
61
59
  end
data/lutaml-model.gemspec CHANGED
@@ -30,5 +30,7 @@ Gem::Specification.new do |spec|
30
30
  end
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
32
 
33
+ spec.add_dependency "bigdecimal"
33
34
  spec.add_dependency "thor"
35
+ spec.metadata["rubygems_mfa_required"] = "true"
34
36
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.8
4
+ version: 0.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-27 00:00:00.000000000 Z
11
+ date: 2024-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bigdecimal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: thor
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -55,8 +69,10 @@ files:
55
69
  - lib/lutaml/model/comparison.rb
56
70
  - lib/lutaml/model/config.rb
57
71
  - lib/lutaml/model/error.rb
72
+ - lib/lutaml/model/error/collection_count_out_of_range_error.rb
58
73
  - lib/lutaml/model/error/invalid_value_error.rb
59
74
  - lib/lutaml/model/error/unknown_adapter_type_error.rb
75
+ - lib/lutaml/model/error/validation_error.rb
60
76
  - lib/lutaml/model/json_adapter.rb
61
77
  - lib/lutaml/model/json_adapter/json_document.rb
62
78
  - lib/lutaml/model/json_adapter/json_object.rb
@@ -72,6 +88,7 @@ files:
72
88
  - lib/lutaml/model/schema/relaxng_schema.rb
73
89
  - lib/lutaml/model/schema/xsd_schema.rb
74
90
  - lib/lutaml/model/schema/yaml_schema.rb
91
+ - lib/lutaml/model/schema_location.rb
75
92
  - lib/lutaml/model/serializable.rb
76
93
  - lib/lutaml/model/serialize.rb
77
94
  - lib/lutaml/model/toml_adapter.rb
@@ -83,6 +100,7 @@ files:
83
100
  - lib/lutaml/model/type/date_time.rb
84
101
  - lib/lutaml/model/type/time_without_date.rb
85
102
  - lib/lutaml/model/utils.rb
103
+ - lib/lutaml/model/validation.rb
86
104
  - lib/lutaml/model/version.rb
87
105
  - lib/lutaml/model/xml_adapter.rb
88
106
  - lib/lutaml/model/xml_adapter/builder/nokogiri.rb
@@ -105,7 +123,8 @@ files:
105
123
  homepage: https://github.com/lutaml/lutaml-model
106
124
  licenses:
107
125
  - BSD-2-Clause
108
- metadata: {}
126
+ metadata:
127
+ rubygems_mfa_required: 'true'
109
128
  post_install_message:
110
129
  rdoc_options: []
111
130
  require_paths: