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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +47 -38
- data/README.adoc +323 -65
- data/lib/lutaml/model/attribute.rb +114 -3
- data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +29 -0
- data/lib/lutaml/model/error/validation_error.rb +21 -0
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/schema_location.rb +59 -0
- data/lib/lutaml/model/serialize.rb +53 -34
- data/lib/lutaml/model/validation.rb +24 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +13 -3
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +3 -0
- data/lib/lutaml/model/xml_adapter/xml_document.rb +26 -14
- data/lib/lutaml/model/xml_mapping.rb +1 -3
- data/lutaml-model.gemspec +2 -0
- metadata +22 -3
@@ -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
|
data/lib/lutaml/model/error.rb
CHANGED
@@ -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
|
-
|
96
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -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
|
63
|
-
|
64
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
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
|
-
|
164
|
-
|
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
|
-
|
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
|
-
|
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.
|
216
|
-
|
217
|
-
|
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
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.
|
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-
|
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:
|