lutaml-model 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-tests.yml +2 -0
- data/.rubocop_todo.yml +86 -23
- data/Gemfile +2 -0
- data/README.adoc +1441 -220
- data/lib/lutaml/model/attribute.rb +33 -10
- data/lib/lutaml/model/choice.rb +56 -0
- data/lib/lutaml/model/config.rb +1 -0
- data/lib/lutaml/model/constants.rb +7 -0
- data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
- data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
- data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
- data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
- data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
- data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
- data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
- data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
- data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
- data/lib/lutaml/model/error.rb +9 -0
- data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
- data/lib/lutaml/model/key_value_mapping.rb +34 -3
- data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
- data/lib/lutaml/model/liquefiable.rb +59 -0
- data/lib/lutaml/model/mapping_hash.rb +9 -1
- data/lib/lutaml/model/mapping_rule.rb +19 -2
- data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +762 -0
- data/lib/lutaml/model/schema.rb +5 -0
- data/lib/lutaml/model/schema_location.rb +7 -0
- data/lib/lutaml/model/sequence.rb +71 -0
- data/lib/lutaml/model/serialize.rb +139 -33
- data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
- data/lib/lutaml/model/type/decimal.rb +0 -4
- data/lib/lutaml/model/type/hash.rb +11 -11
- data/lib/lutaml/model/type/time.rb +3 -3
- data/lib/lutaml/model/utils.rb +19 -15
- data/lib/lutaml/model/validation.rb +12 -1
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
- data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
- data/lib/lutaml/model/xml_adapter/element.rb +32 -0
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +13 -9
- data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
- data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
- data/lib/lutaml/model/xml_adapter/xml_document.rb +82 -25
- data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
- data/lib/lutaml/model/xml_mapping.rb +53 -9
- data/lib/lutaml/model/xml_mapping_rule.rb +8 -6
- data/lib/lutaml/model.rb +2 -0
- data/lutaml-model.gemspec +5 -0
- data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
- data/spec/ceramic_spec.rb +39 -0
- data/spec/fixtures/ceramic.rb +23 -0
- data/spec/fixtures/xml/address_example_260.xsd +9 -0
- data/spec/fixtures/xml/invalid_math_document.xml +4 -0
- data/spec/fixtures/xml/math_document_schema.xsd +56 -0
- data/spec/fixtures/xml/test_schema.xsd +53 -0
- data/spec/fixtures/xml/user.xsd +10 -0
- data/spec/fixtures/xml/valid_math_document.xml +4 -0
- data/spec/lutaml/model/cdata_spec.rb +4 -5
- data/spec/lutaml/model/choice_spec.rb +168 -0
- data/spec/lutaml/model/collection_spec.rb +1 -1
- data/spec/lutaml/model/custom_model_spec.rb +7 -21
- data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
- data/spec/lutaml/model/defaults_spec.rb +3 -1
- data/spec/lutaml/model/delegation_spec.rb +7 -5
- data/spec/lutaml/model/enum_spec.rb +35 -0
- data/spec/lutaml/model/group_spec.rb +160 -0
- data/spec/lutaml/model/inheritance_spec.rb +25 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
- data/spec/lutaml/model/liquefiable_spec.rb +121 -0
- data/spec/lutaml/model/map_all_spec.rb +188 -0
- data/spec/lutaml/model/mixed_content_spec.rb +95 -56
- data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
- data/spec/lutaml/model/schema/xml_compiler_spec.rb +1624 -0
- data/spec/lutaml/model/sequence_spec.rb +216 -0
- data/spec/lutaml/model/transformation_spec.rb +230 -0
- data/spec/lutaml/model/type_spec.rb +138 -31
- data/spec/lutaml/model/utils_spec.rb +32 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
- data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +250 -112
- metadata +77 -2
@@ -10,6 +10,9 @@ module Lutaml
|
|
10
10
|
collection
|
11
11
|
values
|
12
12
|
pattern
|
13
|
+
transform
|
14
|
+
choice
|
15
|
+
sequence
|
13
16
|
].freeze
|
14
17
|
|
15
18
|
def initialize(name, type, options = {})
|
@@ -33,6 +36,10 @@ module Lutaml
|
|
33
36
|
@options[:delegate]
|
34
37
|
end
|
35
38
|
|
39
|
+
def transform
|
40
|
+
@options[:transform] || {}
|
41
|
+
end
|
42
|
+
|
36
43
|
def cast_type!(type)
|
37
44
|
case type
|
38
45
|
when Symbol
|
@@ -81,15 +88,17 @@ module Lutaml
|
|
81
88
|
end
|
82
89
|
|
83
90
|
def default
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
cast_value(default_value)
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_value
|
95
|
+
if delegate
|
96
|
+
type.attributes[to].default
|
97
|
+
elsif options[:default].is_a?(Proc)
|
98
|
+
options[:default].call
|
99
|
+
else
|
100
|
+
options[:default]
|
101
|
+
end
|
93
102
|
end
|
94
103
|
|
95
104
|
def pattern
|
@@ -100,8 +109,16 @@ module Lutaml
|
|
100
109
|
@options.key?(:values) ? @options[:values] : []
|
101
110
|
end
|
102
111
|
|
112
|
+
def transform_import_method
|
113
|
+
transform[:import]
|
114
|
+
end
|
115
|
+
|
116
|
+
def transform_export_method
|
117
|
+
transform[:export]
|
118
|
+
end
|
119
|
+
|
103
120
|
def valid_value!(value)
|
104
|
-
return true if value.nil? &&
|
121
|
+
return true if value.nil? && singular?
|
105
122
|
return true unless enum?
|
106
123
|
|
107
124
|
unless valid_value?(value)
|
@@ -153,6 +170,10 @@ module Lutaml
|
|
153
170
|
raise ArgumentError, "Invalid collection range: #{range}"
|
154
171
|
end
|
155
172
|
|
173
|
+
validate_range!(range)
|
174
|
+
end
|
175
|
+
|
176
|
+
def validate_range!(range)
|
156
177
|
if range.begin.nil?
|
157
178
|
raise ArgumentError,
|
158
179
|
"Invalid collection range: #{range}. Begin must be specified."
|
@@ -235,6 +256,8 @@ module Lutaml
|
|
235
256
|
end
|
236
257
|
elsif type <= Serialize && value.is_a?(Hash)
|
237
258
|
type.apply_mappings(value, format, options)
|
259
|
+
elsif !value.nil? && !value.is_a?(type)
|
260
|
+
type.send(:"from_#{format}", value)
|
238
261
|
else
|
239
262
|
type.cast(value)
|
240
263
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
class Choice
|
4
|
+
attr_reader :attributes,
|
5
|
+
:model,
|
6
|
+
:min,
|
7
|
+
:max
|
8
|
+
|
9
|
+
def initialize(model, min, max)
|
10
|
+
@attributes = []
|
11
|
+
@model = model
|
12
|
+
@min = min
|
13
|
+
@max = max
|
14
|
+
|
15
|
+
raise Lutaml::Model::InvalidChoiceRangeError.new(@min, @max) if @min.negative? || @max.negative?
|
16
|
+
end
|
17
|
+
|
18
|
+
def attribute(name, type, options = {})
|
19
|
+
options[:choice] = self
|
20
|
+
@attributes << @model.attribute(name, type, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def choice(min: 1, max: 1, &block)
|
24
|
+
@attributes << Choice.new(@model, min, max).tap do |c|
|
25
|
+
c.instance_eval(&block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_content!(object)
|
30
|
+
validated_attributes = []
|
31
|
+
valid = valid_attributes(object, validated_attributes)
|
32
|
+
|
33
|
+
raise Lutaml::Model::ChoiceUpperBoundError.new(validated_attributes, @max) if valid.count > @max
|
34
|
+
raise Lutaml::Model::ChoiceLowerBoundError.new(validated_attributes, @min) if valid.count < @min
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def valid_attributes(object, validated_attributes)
|
40
|
+
@attributes.each do |attribute|
|
41
|
+
if attribute.is_a?(Choice)
|
42
|
+
begin
|
43
|
+
attribute.validate_content!(object)
|
44
|
+
validated_attributes << attribute
|
45
|
+
rescue Lutaml::Model::ChoiceLowerBoundError
|
46
|
+
end
|
47
|
+
elsif Utils.present?(object.public_send(attribute.name))
|
48
|
+
validated_attributes << attribute.name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
validated_attributes
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/lutaml/model/config.rb
CHANGED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
class IncorrectSequenceError < Error
|
4
|
+
def initialize(defined_order_element, expected_order_element)
|
5
|
+
super("Element `#{expected_order_element}` does not match the expected sequence order element `#{defined_order_element}`")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
class InvalidChoiceRangeError < Error
|
4
|
+
def initialize(min, max)
|
5
|
+
@min = min
|
6
|
+
@max = max
|
7
|
+
|
8
|
+
super()
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
if @min.negative?
|
13
|
+
"Choice lower bound `#{@min}` must be positive"
|
14
|
+
else
|
15
|
+
"Choice upper bound `#{@max}` must be positive"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/lutaml/model/error.rb
CHANGED
@@ -16,3 +16,12 @@ require_relative "error/type_error"
|
|
16
16
|
require_relative "error/unknown_type_error"
|
17
17
|
require_relative "error/multiple_mappings_error"
|
18
18
|
require_relative "error/collection_true_missing_error"
|
19
|
+
require_relative "error/type/invalid_value_error"
|
20
|
+
require_relative "error/incorrect_sequence_error"
|
21
|
+
require_relative "error/choice_upper_bound_error"
|
22
|
+
require_relative "error/no_root_mapping_error"
|
23
|
+
require_relative "error/import_model_with_root_error"
|
24
|
+
require_relative "error/invalid_choice_range_error"
|
25
|
+
require_relative "error/unknown_sequence_mapping_error"
|
26
|
+
require_relative "error/choice_lower_bound_error"
|
27
|
+
require_relative "error/no_root_namespace_error"
|
@@ -10,7 +10,12 @@ module Lutaml
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def to_json(*args)
|
13
|
-
|
13
|
+
options = args.first || {}
|
14
|
+
if options[:pretty]
|
15
|
+
JSON.pretty_generate(@attributes, *args)
|
16
|
+
else
|
17
|
+
JSON.generate(@attributes, *args)
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
16
21
|
end
|
@@ -17,7 +17,8 @@ module Lutaml
|
|
17
17
|
with: {},
|
18
18
|
delegate: nil,
|
19
19
|
child_mappings: nil,
|
20
|
-
root_mappings: nil
|
20
|
+
root_mappings: nil,
|
21
|
+
transform: {}
|
21
22
|
)
|
22
23
|
mapping_name = name_for_mapping(root_mappings, name)
|
23
24
|
validate!(mapping_name, to, with)
|
@@ -31,11 +32,33 @@ module Lutaml
|
|
31
32
|
delegate: delegate,
|
32
33
|
child_mappings: child_mappings,
|
33
34
|
root_mappings: root_mappings,
|
35
|
+
transform: transform,
|
34
36
|
)
|
35
37
|
end
|
36
38
|
|
37
39
|
alias map_element map
|
38
40
|
|
41
|
+
def map_all(
|
42
|
+
to: nil,
|
43
|
+
render_nil: false,
|
44
|
+
render_default: false,
|
45
|
+
with: {},
|
46
|
+
delegate: nil
|
47
|
+
)
|
48
|
+
@raw_mapping = true
|
49
|
+
validate!(Constants::RAW_MAPPING_KEY, to, with)
|
50
|
+
@mappings << KeyValueMappingRule.new(
|
51
|
+
Constants::RAW_MAPPING_KEY,
|
52
|
+
to: to,
|
53
|
+
render_nil: render_nil,
|
54
|
+
render_default: render_default,
|
55
|
+
with: with,
|
56
|
+
delegate: delegate,
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
alias map_all_content map_all
|
61
|
+
|
39
62
|
def name_for_mapping(root_mappings, name)
|
40
63
|
return "root_mapping" if root_mappings
|
41
64
|
|
@@ -43,12 +66,14 @@ module Lutaml
|
|
43
66
|
end
|
44
67
|
|
45
68
|
def validate!(key, to, with)
|
46
|
-
|
69
|
+
validate_mappings!(key)
|
70
|
+
|
71
|
+
if to.nil? && with.empty? && !@raw_mapping
|
47
72
|
msg = ":to or :with argument is required for mapping '#{key}'"
|
48
73
|
raise IncorrectMappingArgumentsError.new(msg)
|
49
74
|
end
|
50
75
|
|
51
|
-
if !with.empty? && (with[:from].nil? || with[:to].nil?)
|
76
|
+
if !with.empty? && (with[:from].nil? || with[:to].nil?) && !@raw_mapping
|
52
77
|
msg = ":with argument for mapping '#{key}' requires :to and :from keys"
|
53
78
|
raise IncorrectMappingArgumentsError.new(msg)
|
54
79
|
end
|
@@ -62,6 +87,12 @@ module Lutaml
|
|
62
87
|
end
|
63
88
|
end
|
64
89
|
|
90
|
+
def validate_mappings!(_type)
|
91
|
+
if (@raw_mapping && Utils.present?(@mappings)) || (!@raw_mapping && @mappings.any?(&:raw_mapping?))
|
92
|
+
raise StandardError, "map_all is not allowed with other mappings"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
65
96
|
def deep_dup
|
66
97
|
self.class.new.tap do |new_mapping|
|
67
98
|
new_mapping.instance_variable_set(:@mappings, duplicate_mappings)
|
@@ -14,7 +14,8 @@ module Lutaml
|
|
14
14
|
with: {},
|
15
15
|
delegate: nil,
|
16
16
|
child_mappings: nil,
|
17
|
-
root_mappings: nil
|
17
|
+
root_mappings: nil,
|
18
|
+
transform: {}
|
18
19
|
)
|
19
20
|
super(
|
20
21
|
name,
|
@@ -22,7 +23,8 @@ module Lutaml
|
|
22
23
|
render_nil: render_nil,
|
23
24
|
render_default: render_default,
|
24
25
|
with: with,
|
25
|
-
delegate: delegate
|
26
|
+
delegate: delegate,
|
27
|
+
transform: transform
|
26
28
|
)
|
27
29
|
|
28
30
|
@child_mappings = child_mappings
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "liquid"
|
2
|
+
|
3
|
+
module Lutaml
|
4
|
+
module Model
|
5
|
+
module Liquefiable
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def register_liquid_drop_class
|
12
|
+
if drop_class
|
13
|
+
raise "#{drop_class_name} Already exists!"
|
14
|
+
end
|
15
|
+
|
16
|
+
const_set(drop_class_name,
|
17
|
+
Class.new(Liquid::Drop) do
|
18
|
+
def initialize(object)
|
19
|
+
super()
|
20
|
+
@object = object
|
21
|
+
end
|
22
|
+
end)
|
23
|
+
end
|
24
|
+
|
25
|
+
def drop_class_name
|
26
|
+
@drop_class_name ||= if name
|
27
|
+
"#{to_s.split('::').last}Drop"
|
28
|
+
else
|
29
|
+
"Drop"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def drop_class
|
34
|
+
const_get(drop_class_name)
|
35
|
+
rescue StandardError
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def register_drop_method(method_name)
|
40
|
+
register_liquid_drop_class unless drop_class
|
41
|
+
|
42
|
+
drop_class.define_method(method_name) do
|
43
|
+
value = @object.public_send(method_name)
|
44
|
+
|
45
|
+
if value.is_a?(Array)
|
46
|
+
value.map(&:to_liquid)
|
47
|
+
else
|
48
|
+
value.to_liquid
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_liquid
|
55
|
+
self.class.drop_class.new(self)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -11,7 +11,7 @@ module Lutaml
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def item_order
|
14
|
-
@item_order
|
14
|
+
@item_order
|
15
15
|
end
|
16
16
|
|
17
17
|
def fetch(key)
|
@@ -36,6 +36,14 @@ module Lutaml
|
|
36
36
|
key?("#cdata-section") || key?("text")
|
37
37
|
end
|
38
38
|
|
39
|
+
def assign_or_append_value(key, value)
|
40
|
+
self[key] = if self[key]
|
41
|
+
[self[key], value].flatten
|
42
|
+
else
|
43
|
+
value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
39
47
|
def ordered?
|
40
48
|
@ordered
|
41
49
|
end
|
@@ -7,7 +7,8 @@ module Lutaml
|
|
7
7
|
:render_default,
|
8
8
|
:attribute,
|
9
9
|
:custom_methods,
|
10
|
-
:delegate
|
10
|
+
:delegate,
|
11
|
+
:transform
|
11
12
|
|
12
13
|
def initialize(
|
13
14
|
name,
|
@@ -17,7 +18,8 @@ module Lutaml
|
|
17
18
|
with: {},
|
18
19
|
attribute: false,
|
19
20
|
delegate: nil,
|
20
|
-
root_mappings: nil
|
21
|
+
root_mappings: nil,
|
22
|
+
transform: {}
|
21
23
|
)
|
22
24
|
@name = name
|
23
25
|
@to = to
|
@@ -27,6 +29,7 @@ module Lutaml
|
|
27
29
|
@attribute = attribute
|
28
30
|
@delegate = delegate
|
29
31
|
@root_mappings = root_mappings
|
32
|
+
@transform = transform
|
30
33
|
end
|
31
34
|
|
32
35
|
alias from name
|
@@ -71,6 +74,8 @@ module Lutaml
|
|
71
74
|
end
|
72
75
|
|
73
76
|
model.public_send(delegate).public_send(:"#{to}=", value)
|
77
|
+
elsif transform_method = transform[:import] || attributes[to].transform_import_method
|
78
|
+
model.public_send(:"#{to}=", transform_method.call(value))
|
74
79
|
else
|
75
80
|
model.public_send(:"#{to}=", value)
|
76
81
|
end
|
@@ -84,6 +89,18 @@ module Lutaml
|
|
84
89
|
name.is_a?(Array)
|
85
90
|
end
|
86
91
|
|
92
|
+
def raw_mapping?
|
93
|
+
name == Constants::RAW_MAPPING_KEY
|
94
|
+
end
|
95
|
+
|
96
|
+
def eql?(other)
|
97
|
+
other.class == self.class &&
|
98
|
+
instance_variables.all? do |var|
|
99
|
+
instance_variable_get(var) == other.instance_variable_get(var)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
alias == eql?
|
103
|
+
|
87
104
|
def deep_dup
|
88
105
|
raise NotImplementedError, "Subclasses must implement `deep_dup`."
|
89
106
|
end
|