lutaml-model 0.3.8 → 0.3.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: