lutaml-model 0.5.2 → 0.5.4
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/.github/workflows/dependent-tests.yml +2 -0
- data/.rubocop_todo.yml +39 -13
- data/Gemfile +1 -0
- data/README.adoc +430 -52
- data/lib/lutaml/model/constants.rb +7 -0
- data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
- data/lib/lutaml/model/error.rb +1 -0
- data/lib/lutaml/model/key_value_mapping.rb +31 -2
- data/lib/lutaml/model/mapping_hash.rb +8 -0
- data/lib/lutaml/model/mapping_rule.rb +8 -0
- data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
- data/lib/lutaml/model/schema/xml_compiler.rb +720 -0
- data/lib/lutaml/model/schema.rb +5 -0
- data/lib/lutaml/model/serialize.rb +33 -13
- data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
- data/lib/lutaml/model/type/hash.rb +11 -11
- data/lib/lutaml/model/utils.rb +7 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +5 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +11 -15
- data/lib/lutaml/model/xml_mapping.rb +4 -2
- data/lib/lutaml/model/xml_mapping_rule.rb +1 -4
- data/lib/lutaml/model.rb +1 -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/valid_math_document.xml +4 -0
- data/spec/lutaml/model/cdata_spec.rb +2 -2
- data/spec/lutaml/model/custom_model_spec.rb +7 -20
- data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
- data/spec/lutaml/model/map_all_spec.rb +188 -0
- data/spec/lutaml/model/mixed_content_spec.rb +15 -15
- data/spec/lutaml/model/render_nil_spec.rb +29 -0
- data/spec/lutaml/model/schema/xml_compiler_spec.rb +1431 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +108 -1
- metadata +12 -2
@@ -34,6 +34,10 @@ module Lutaml
|
|
34
34
|
alias render_default? render_default
|
35
35
|
alias attribute? attribute
|
36
36
|
|
37
|
+
def render?(value)
|
38
|
+
render_nil? || (!value.nil? && !Utils.empty_collection?(value))
|
39
|
+
end
|
40
|
+
|
37
41
|
def serialize_attribute(model, element, doc)
|
38
42
|
if custom_methods[:to]
|
39
43
|
model.send(custom_methods[:to], model, element, doc)
|
@@ -80,6 +84,10 @@ module Lutaml
|
|
80
84
|
name.is_a?(Array)
|
81
85
|
end
|
82
86
|
|
87
|
+
def raw_mapping?
|
88
|
+
name == Constants::RAW_MAPPING_KEY
|
89
|
+
end
|
90
|
+
|
83
91
|
def deep_dup
|
84
92
|
raise NotImplementedError, "Subclasses must implement `deep_dup`."
|
85
93
|
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lutaml
|
4
|
+
module Model
|
5
|
+
module Schema
|
6
|
+
module Templates
|
7
|
+
module SimpleType
|
8
|
+
extend self
|
9
|
+
attr_accessor :simple_types
|
10
|
+
|
11
|
+
DEFAULT_CLASSES = %w[int integer string boolean].freeze
|
12
|
+
|
13
|
+
SUPPORTED_DATA_TYPES = {
|
14
|
+
nonNegativeInteger: { class_name: "Lutaml::Model::Type::String", validations: { pattern: /\+?[0-9]+/ } },
|
15
|
+
positiveInteger: { class_name: "Lutaml::Model::Type::Integer", validations: { min: 0 } },
|
16
|
+
base64Binary: { class_name: "Lutaml::Model::Type::String", validations: { pattern: /\A([A-Za-z0-9+\/]+={0,2}|\s)*\z/ } },
|
17
|
+
unsignedLong: { class_name: "Lutaml::Model::Type::Integer", validations: { min: 0, max: 18446744073709551615 } },
|
18
|
+
unsignedInt: { class_name: "Lutaml::Model::Type::Integer", validations: { min: 0, max: 4294967295 } },
|
19
|
+
hexBinary: { class_name: "Lutaml::Model::Type::String", validations: { pattern: /([0-9a-fA-F]{2})*/ } },
|
20
|
+
dateTime: { skippable: true, class_name: "Lutaml::Model::Type::DateTime" },
|
21
|
+
boolean: { skippable: true, class_name: "Lutaml::Model::Type::Boolean" },
|
22
|
+
integer: { skippable: true, class_name: "Lutaml::Model::Type::Integer" },
|
23
|
+
string: { skippable: true, class_name: "Lutaml::Model::Type::String" },
|
24
|
+
token: { class_name: "Lutaml::Model::Type::String", validations: { pattern: /\A[^\t\n\f\r ]+(?: [^\t\n\f\r ]+)*\z/ } },
|
25
|
+
long: { class_name: "Lutaml::Model::Type::Decimal" },
|
26
|
+
int: { skippable: true, class_name: "Lutaml::Model::Type::Integer" },
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
REF_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
|
30
|
+
# frozen_string_literal: true
|
31
|
+
|
32
|
+
require "lutaml/model"
|
33
|
+
<%= "require_relative \#{Utils.snake_case(parent_class).inspect}\n" if require_parent -%>
|
34
|
+
|
35
|
+
class <%= klass_name %> < <%= parent_class %>; end
|
36
|
+
TEMPLATE
|
37
|
+
|
38
|
+
SUPPORTED_TYPES_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
|
39
|
+
# frozen_string_literal: true
|
40
|
+
|
41
|
+
require "lutaml/model"
|
42
|
+
|
43
|
+
class <%= Utils.camel_case(klass_name.to_s) %> < <%= properties[:class_name].to_s %>
|
44
|
+
def self.cast(value)
|
45
|
+
return nil if value.nil?
|
46
|
+
|
47
|
+
value = super(value)
|
48
|
+
<%=
|
49
|
+
if pattern_exist = validations.key?(:pattern)
|
50
|
+
" pattern = %r{\#{validations[:pattern]}}\n\#{indent}raise Lutaml::Model::Type::InvalidValueError, \\"The value \\\#{value} does not match the required pattern: \\\#{pattern}\\" unless value.match?(pattern)\n"
|
51
|
+
end
|
52
|
+
-%>
|
53
|
+
<%=
|
54
|
+
if min_exist = validations.key?(:min)
|
55
|
+
" min = \#{validations[:min]}\n\#{indent}raise Lutaml::Model::Type::InvalidValueError, \\"The value \\\#{value} is less than the set limit: \\\#{min}\\" if value < min\n"
|
56
|
+
end
|
57
|
+
-%>
|
58
|
+
<%=
|
59
|
+
if max_exist = validations.key?(:max)
|
60
|
+
" max = \#{validations[:max]}\n\#{indent}raise Lutaml::Model::Type::InvalidValueError, \\"The value \\\#{value} is greater than the set limit: \\\#{max}\\" if value > max\n"
|
61
|
+
end
|
62
|
+
-%>
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
TEMPLATE
|
67
|
+
|
68
|
+
UNION_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
|
69
|
+
# frozen_string_literal: true
|
70
|
+
|
71
|
+
require "lutaml/model"
|
72
|
+
<%=
|
73
|
+
resolve_required_files(unions)&.map do |file|
|
74
|
+
next if file.nil? || file.empty?
|
75
|
+
|
76
|
+
"require_relative \\\"\#{file}\\\""
|
77
|
+
end.compact.join("\n") + "\n"
|
78
|
+
-%>
|
79
|
+
|
80
|
+
class <%= klass_name %> < Lutaml::Model::Type::Value
|
81
|
+
def self.cast(value)
|
82
|
+
return nil if value.nil?
|
83
|
+
|
84
|
+
<%= unions.map do |union|
|
85
|
+
base_class = union.base_class.split(':').last
|
86
|
+
if DEFAULT_CLASSES.include?(base_class)
|
87
|
+
"\#{SUPPORTED_DATA_TYPES.dig(base_class.to_sym, :class_name)}.cast(value)"
|
88
|
+
else
|
89
|
+
"\#{Utils.camel_case(base_class)}.cast(value)"
|
90
|
+
end
|
91
|
+
end.join(" || ") %>
|
92
|
+
end
|
93
|
+
end
|
94
|
+
TEMPLATE
|
95
|
+
|
96
|
+
MODEL_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
|
97
|
+
# frozen_string_literal: true
|
98
|
+
require "lutaml/model"
|
99
|
+
<%= "require_relative '\#{Utils.snake_case(parent_class)}'\n" if require_parent -%>
|
100
|
+
|
101
|
+
class <%= klass_name %> < <%= parent_class %>
|
102
|
+
<%= " VALUES = \#{values}.freeze\n\n" if values_exist = values&.any? -%>
|
103
|
+
<%= " LENGTHS = \#{properties[:length]&.map(&:value)}\n\n" if length_exist = properties&.key?(:length) -%>
|
104
|
+
def self.cast(value)
|
105
|
+
return nil if value.nil?
|
106
|
+
|
107
|
+
value = super(value)
|
108
|
+
<%= " raise_values_error(value) unless VALUES.include?(value)\n" if values_exist -%>
|
109
|
+
<%= " raise_length_error(value) unless LENGTHS.all?(value.length)\n" if length_exist -%>
|
110
|
+
<%=
|
111
|
+
if pattern_exist = properties.key?(:pattern)
|
112
|
+
" pattern = %r{\#{properties[:pattern]}}\n raise_pattern_error(value, pattern) unless value.match?(pattern)\n"
|
113
|
+
end
|
114
|
+
-%>
|
115
|
+
<%=
|
116
|
+
if min_length_exist = properties&.key_exist?(:min_length)
|
117
|
+
" min_length = \#{properties.min_length}\n raise_min_length_error(value, min_length) unless value.length >= min_length\n"
|
118
|
+
end
|
119
|
+
-%>
|
120
|
+
<%=
|
121
|
+
if max_length_exist = properties&.key_exist?(:max_length)
|
122
|
+
" max_length = \#{properties.max_length}\n raise_max_length_error(value, max_length) unless value.length <= max_length\n"
|
123
|
+
end
|
124
|
+
-%>
|
125
|
+
<%=
|
126
|
+
if min_bound_exist = (properties&.key_exist?(:min_inclusive) || properties&.key_exist?(:min_exclusive))
|
127
|
+
" min_bound = \#{properties[:min_inclusive] || properties[:min_exclusive]}\n raise_min_bound_error(value, min_bound) unless value >\#{'=' if properties.key?(:min_inclusive)} min_bound \n"
|
128
|
+
end
|
129
|
+
-%>
|
130
|
+
<%=
|
131
|
+
if max_bound_exist = (properties&.key_exist?(:max_inclusive) || properties&.key_exist?(:max_exclusive))
|
132
|
+
" max_bound = \#{properties[:max_inclusive] || properties[:max_exclusive]}\n raise_max_bound_error(value, max_bound) unless value <\#{'=' if properties.key?(:max_inclusive)} max_bound \n"
|
133
|
+
end
|
134
|
+
-%>
|
135
|
+
<%= "value" %>
|
136
|
+
end
|
137
|
+
<%= "\n private\n" if pattern_exist || values_exist || length_exist || min_length_exist || max_length_exist || min_bound_exist || max_bound_exist -%>
|
138
|
+
<%=
|
139
|
+
if pattern_exist
|
140
|
+
"\n def self.raise_pattern_error(value, pattern)\n raise Lutaml::Model::Type::InvalidValueError, \\"The value \\\#{value} does not match the required pattern: \\\#{pattern}\\"\n end\n"
|
141
|
+
end
|
142
|
+
-%>
|
143
|
+
<%=
|
144
|
+
if values_exist
|
145
|
+
"\n def self.raise_values_error(input_value)\n raise Lutaml::Model::InvalidValueError.new(self, input_value, VALUES)\n end\n"
|
146
|
+
end
|
147
|
+
-%>
|
148
|
+
<%=
|
149
|
+
if length_exist
|
150
|
+
"\n def self.raise_length_error(input_value)\n raise Lutaml::Model::Type::InvalidValueError, \\"The provided value \\\\\\"\\\#{input_value}\\\\\\" should match the specified lengths: \\\#{LENGTHS.join(',')}\\"\n end\n"
|
151
|
+
end
|
152
|
+
-%>
|
153
|
+
<%=
|
154
|
+
if min_length_exist
|
155
|
+
"\n def self.raise_min_length_error(input_value, min_length)\n raise Lutaml::Model::Type::InvalidValueError, \\"The provided value \\\\\\"\\\#{input_value}\\\\\\" has fewer characters than the minimum allowed \\\#{min_length}\\"\n end\n"
|
156
|
+
end
|
157
|
+
-%>
|
158
|
+
<%=
|
159
|
+
if max_length_exist
|
160
|
+
"\n def self.raise_max_length_error(input_value, max_length)\n raise Lutaml::Model::Type::InvalidValueError, \\"The provided value \\\\\\"\\\#{input_value}\\\\\\" exceeds the maximum allowed length of \\\#{max_length}\\"\n end\n"
|
161
|
+
end
|
162
|
+
-%>
|
163
|
+
<%=
|
164
|
+
if min_bound_exist
|
165
|
+
"\n def self.raise_min_bound_error(input_value, min_bound)\n raise Lutaml::Model::Type::InvalidValueError, \\"The provided value \\\\\\"\\\#{input_value}\\\\\\" is less than the minimum allowed value of \\\#{min_bound}\\"\n end\n"
|
166
|
+
end
|
167
|
+
-%>
|
168
|
+
<%=
|
169
|
+
if max_bound_exist
|
170
|
+
"\n def self.raise_max_bound_error(input_value, max_bound)\n raise Lutaml::Model::Type::InvalidValueError, \\"The provided value \\\\\\"\\\#{input_value}\\\\\\" exceeds the maximum allowed value of \\\#{max_bound}\\"\n end\n"
|
171
|
+
end
|
172
|
+
-%>
|
173
|
+
end
|
174
|
+
TEMPLATE
|
175
|
+
|
176
|
+
def create_simple_types(simple_types)
|
177
|
+
setup_supported_types
|
178
|
+
simple_types.each do |name, properties|
|
179
|
+
klass_name = Utils.camel_case(name)
|
180
|
+
@simple_types[name] = if @simple_types.key?(properties[:base_class]) && properties.one?
|
181
|
+
ref_template(properties, klass_name)
|
182
|
+
elsif properties&.key_exist?(:union)
|
183
|
+
union_template(properties, klass_name)
|
184
|
+
else
|
185
|
+
model_template(properties, klass_name)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
@simple_types
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
# klass_name is used in template using `binding`
|
194
|
+
def model_template(properties, klass_name)
|
195
|
+
base_class = properties.base_class.split(":")&.last
|
196
|
+
parent_class, require_parent = extract_parent_class(base_class)
|
197
|
+
values = properties[:values] if properties.key_exist?(:values)
|
198
|
+
MODEL_TEMPLATE.result(binding)
|
199
|
+
end
|
200
|
+
|
201
|
+
def extract_parent_class(base_class)
|
202
|
+
klass = if SUPPORTED_DATA_TYPES[base_class.to_sym]&.key?(:class_name)
|
203
|
+
parent = false
|
204
|
+
SUPPORTED_DATA_TYPES.dig(base_class.to_sym, :class_name)
|
205
|
+
else
|
206
|
+
parent = true
|
207
|
+
Utils.camel_case(base_class.to_s)
|
208
|
+
end
|
209
|
+
[klass, parent]
|
210
|
+
end
|
211
|
+
|
212
|
+
# klass_name is used in template using `binding`
|
213
|
+
def union_template(properties, klass_name)
|
214
|
+
unions = properties.union
|
215
|
+
UNION_TEMPLATE.result(binding)
|
216
|
+
end
|
217
|
+
|
218
|
+
# klass_name is used in template using `binding`
|
219
|
+
def ref_template(properties, klass_name)
|
220
|
+
parent_class = properties.base_class
|
221
|
+
require_parent = true unless properties[:base_class].include?("Lutaml::Model::")
|
222
|
+
REF_TEMPLATE.result(binding)
|
223
|
+
end
|
224
|
+
|
225
|
+
def setup_supported_types
|
226
|
+
@simple_types = MappingHash.new
|
227
|
+
indent = " "
|
228
|
+
SUPPORTED_DATA_TYPES.each do |klass_name, properties|
|
229
|
+
validations = properties[:validations] || {}
|
230
|
+
next if properties[:skippable]
|
231
|
+
|
232
|
+
@simple_types[klass_name] = SUPPORTED_TYPES_TEMPLATE.result(binding)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def resolve_required_files(unions)
|
237
|
+
unions.map do |union|
|
238
|
+
next if DEFAULT_CLASSES.include?(union.base_class.split(":").last)
|
239
|
+
|
240
|
+
Utils.snake_case(union.base_class.split(":").last)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|