lutaml-model 0.8.126 → 0.8.128
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/README.adoc +60 -0
- data/docs/_guides/keyvalue-serialization.adoc +75 -0
- data/docs/_pages/value_types.adoc +102 -0
- data/lib/compat/opal/lutaml_model_boot.rb +15 -0
- data/lib/lutaml/key_value/transform.rb +238 -30
- data/lib/lutaml/model/error/unknown_mapping_error.rb +18 -0
- data/lib/lutaml/model/schema/generator/property.rb +60 -9
- data/lib/lutaml/model/serialize/format_conversion.rb +115 -2
- data/lib/lutaml/model/serialize/transformation_builder.rb +4 -3
- data/lib/lutaml/model/transform.rb +29 -4
- data/lib/lutaml/model/transformation_registry.rb +7 -5
- data/lib/lutaml/model/type/date_time_with_timezone.rb +28 -0
- data/lib/lutaml/model/type/date_with_timezone.rb +28 -0
- data/lib/lutaml/model/type/day_time_duration.rb +35 -0
- data/lib/lutaml/model/type/email_address.rb +28 -0
- data/lib/lutaml/model/type/hostname.rb +30 -0
- data/lib/lutaml/model/type/ip_v4_address.rb +27 -0
- data/lib/lutaml/model/type/ip_v6_address.rb +47 -0
- data/lib/lutaml/model/type/metaschema_pattern.rb +34 -0
- data/lib/lutaml/model/type/non_negative_integer.rb +25 -0
- data/lib/lutaml/model/type/positive_integer.rb +25 -0
- data/lib/lutaml/model/type/token.rb +25 -0
- data/lib/lutaml/model/type/uri_reference.rb +29 -0
- data/lib/lutaml/model/type/uuid.rb +25 -0
- data/lib/lutaml/model/type/value.rb +9 -0
- data/lib/lutaml/model/type/year_month_duration.rb +29 -0
- data/lib/lutaml/model/type.rb +27 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model.rb +2 -0
- data/lib/lutaml/xml/adapter/xml_serializer.rb +8 -6
- data/lib/lutaml/xml/model_transform.rb +8 -4
- data/lib/lutaml/xml/schema/xsd_schema.rb +30 -9
- data/lib/lutaml/xml/transformation.rb +3 -2
- data/spec/lutaml/key_value/child_map_plan_spec.rb +169 -0
- data/spec/lutaml/model/metaschema_types_spec.rb +367 -0
- data/spec/lutaml/model/serialization_profiles_spec.rb +276 -0
- metadata +20 -2
|
@@ -11,18 +11,23 @@ module Lutaml
|
|
|
11
11
|
|
|
12
12
|
def self.data_to_model(context, data, format, options = {})
|
|
13
13
|
register = options[:register] || Lutaml::Model::Config.default_register
|
|
14
|
-
transform = cached_transform(context, register)
|
|
14
|
+
transform = cached_transform(context, register, options[:mappings])
|
|
15
15
|
transform.data_to_model(data, format, options)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def self.model_to_data(context, model, format, options = {})
|
|
19
19
|
register = model.lutaml_register if model.is_a?(Lutaml::Model::Serialize)
|
|
20
20
|
register ||= Lutaml::Model::Config.default_register
|
|
21
|
-
transform = cached_transform(context, register)
|
|
21
|
+
transform = cached_transform(context, register, options[:mappings])
|
|
22
22
|
transform.model_to_data(model, format, options)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
# An explicit mapping (#176 `mapping:` profiles / `mappings:` seam)
|
|
26
|
+
# gets a dedicated transform so per-instance compiled state (walk
|
|
27
|
+
# plans, serialize plans) never mixes with the base mapping's.
|
|
28
|
+
def self.cached_transform(context, register, mappings = nil)
|
|
29
|
+
return override_transform(context, register, mappings) if mappings
|
|
30
|
+
|
|
26
31
|
@transform_cache ||= {}
|
|
27
32
|
cache_key = [context, register]
|
|
28
33
|
entry = @transform_cache[cache_key]
|
|
@@ -32,8 +37,25 @@ module Lutaml
|
|
|
32
37
|
@transform_cache[cache_key] = new(context, register)
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
def self.override_transform(context, register, mappings)
|
|
41
|
+
@override_transform_cache ||= {}
|
|
42
|
+
cache_key = [context, register, mappings.object_id]
|
|
43
|
+
entry = @override_transform_cache[cache_key]
|
|
44
|
+
return entry if entry
|
|
45
|
+
|
|
46
|
+
evict_override_if_needed if @override_transform_cache.size >= MAX_CACHE_SIZE
|
|
47
|
+
@override_transform_cache[cache_key] = new(context, register, mappings)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.evict_override_if_needed
|
|
51
|
+
keys = @override_transform_cache.keys.first(@override_transform_cache.size / 2)
|
|
52
|
+
keys.each { |k| @override_transform_cache.delete(k) }
|
|
53
|
+
end
|
|
54
|
+
private_class_method :evict_override_if_needed
|
|
55
|
+
|
|
35
56
|
def self.clear_cache!
|
|
36
57
|
@transform_cache&.clear
|
|
58
|
+
@override_transform_cache&.clear
|
|
37
59
|
end
|
|
38
60
|
|
|
39
61
|
def self.cache_size
|
|
@@ -59,9 +81,10 @@ module Lutaml
|
|
|
59
81
|
|
|
60
82
|
attr_reader :context, :lutaml_register
|
|
61
83
|
|
|
62
|
-
def initialize(context, register = nil)
|
|
84
|
+
def initialize(context, register = nil, mappings_override = nil)
|
|
63
85
|
@context = context
|
|
64
86
|
@lutaml_register = register || Lutaml::Model::Config.default_register
|
|
87
|
+
@lutaml_mapping_override = mappings_override
|
|
65
88
|
end
|
|
66
89
|
|
|
67
90
|
def attributes
|
|
@@ -106,6 +129,8 @@ module Lutaml
|
|
|
106
129
|
end
|
|
107
130
|
|
|
108
131
|
def mappings_for(format, register = nil)
|
|
132
|
+
return @lutaml_mapping_override if @lutaml_mapping_override
|
|
133
|
+
|
|
109
134
|
context.mappings_for(format, register)
|
|
110
135
|
end
|
|
111
136
|
|
|
@@ -113,8 +113,9 @@ module Lutaml
|
|
|
113
113
|
# @param format [Symbol] The format (:xml, :json, :yaml, :hash, :toml)
|
|
114
114
|
# @param register [Symbol, Register, nil] The register for type resolution
|
|
115
115
|
# @return [Transformation, nil] The transformation, or nil if cycle detected
|
|
116
|
-
def get_or_build_transformation(model_class, format, register
|
|
117
|
-
|
|
116
|
+
def get_or_build_transformation(model_class, format, register,
|
|
117
|
+
mapping: nil)
|
|
118
|
+
key = transformation_key(model_class, format, register, mapping)
|
|
118
119
|
current_thread = Thread.current
|
|
119
120
|
|
|
120
121
|
@mutex.synchronize do
|
|
@@ -144,7 +145,7 @@ module Lutaml
|
|
|
144
145
|
|
|
145
146
|
# Build transformation OUTSIDE the lock to avoid deadlock
|
|
146
147
|
# (building may trigger recursive calls to get_or_build_transformation)
|
|
147
|
-
mapping
|
|
148
|
+
mapping ||= get_or_build_mapping(model_class, format, register)
|
|
148
149
|
transformation = build_transformation(model_class, mapping, format,
|
|
149
150
|
register)
|
|
150
151
|
|
|
@@ -221,7 +222,7 @@ module Lutaml
|
|
|
221
222
|
# @param format [Symbol] The format
|
|
222
223
|
# @param register [Symbol, Register, nil] The register
|
|
223
224
|
# @return [Symbol] Unique cache key
|
|
224
|
-
def transformation_key(model_class, format, register)
|
|
225
|
+
def transformation_key(model_class, format, register, mapping = nil)
|
|
225
226
|
register_id = extract_register_id(register)
|
|
226
227
|
# Always include object_id to prevent cache pollution between classes
|
|
227
228
|
# with the same name (common in test fixtures)
|
|
@@ -233,7 +234,8 @@ module Lutaml
|
|
|
233
234
|
else
|
|
234
235
|
"#{class_name}@0x#{model_class.object_id.to_s(16)}"
|
|
235
236
|
end
|
|
236
|
-
:"#{class_id}#{format}#{register_id}"
|
|
237
|
+
key = :"#{class_id}#{format}#{register_id}"
|
|
238
|
+
mapping ? :"#{key}#m#{mapping.object_id}" : key
|
|
237
239
|
end
|
|
238
240
|
|
|
239
241
|
# Build unique key for mapping cache
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema date-time-with-timezone datatype (#107): a
|
|
7
|
+
# date-time that must carry a UTC offset or Z.
|
|
8
|
+
#
|
|
9
|
+
# @example Using DateTimeWithTimezone type
|
|
10
|
+
# attribute :generated_at, :date_time_with_timezone
|
|
11
|
+
class DateTimeWithTimezone < DateTime
|
|
12
|
+
TIMEZONE = /(Z|[+-]\d{2}:\d{2})\z/
|
|
13
|
+
|
|
14
|
+
def self.valid?(value)
|
|
15
|
+
value.is_a?(::String) ? value.match?(TIMEZONE) : !cast(value).nil?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.json_schema_format
|
|
19
|
+
"date-time"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.default_xsd_type
|
|
23
|
+
"xs:dateTime"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema date-with-timezone datatype (#107): a date that
|
|
7
|
+
# must carry a UTC offset or Z.
|
|
8
|
+
#
|
|
9
|
+
# @example Using DateWithTimezone type
|
|
10
|
+
# attribute :effective_on, :date_with_timezone
|
|
11
|
+
class DateWithTimezone < Date
|
|
12
|
+
TIMEZONE = /(Z|[+-]\d{2}:\d{2})\z/
|
|
13
|
+
|
|
14
|
+
def self.valid?(value)
|
|
15
|
+
value.is_a?(::String) ? value.match?(TIMEZONE) : !cast(value).nil?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.json_schema_format
|
|
19
|
+
"date"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.default_xsd_type
|
|
23
|
+
"xs:date"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema day-time-duration datatype (#107): an ISO 8601
|
|
7
|
+
# duration expressed in days, hours, minutes, and seconds (no
|
|
8
|
+
# years or months), per the xs:dayTimeDuration form. Built by
|
|
9
|
+
# interpolation with no formatting whitespace, so the pattern's
|
|
10
|
+
# source is export-clean for xs:pattern facets.
|
|
11
|
+
#
|
|
12
|
+
# @example Using DayTimeDuration type
|
|
13
|
+
# attribute :elapsed, :day_time_duration
|
|
14
|
+
class DayTimeDuration < Duration
|
|
15
|
+
ELAPSED_TIME =
|
|
16
|
+
"(?:[0-9]+H(?:[0-9]+M)?(?:[0-9]+(?:\\.[0-9]+)?S)?" \
|
|
17
|
+
"|[0-9]+M(?:[0-9]+(?:\\.[0-9]+)?S)?" \
|
|
18
|
+
"|[0-9]+(?:\\.[0-9]+)?S)"
|
|
19
|
+
PATTERN = /\A-?P(?:[0-9]+D(?:T#{ELAPSED_TIME})?|T#{ELAPSED_TIME})\z/
|
|
20
|
+
|
|
21
|
+
def self.valid?(value)
|
|
22
|
+
value.is_a?(::String) ? value.match?(PATTERN) : !cast(value).nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.json_schema_format
|
|
26
|
+
"duration"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.default_xsd_type
|
|
30
|
+
"xs:dayTimeDuration"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema email-address datatype (#107): a string containing
|
|
7
|
+
# an at-sign, per the metaschema-datatypes pattern `.+@.+`.
|
|
8
|
+
#
|
|
9
|
+
# @example Using EmailAddress type
|
|
10
|
+
# attribute :contact, :email_address
|
|
11
|
+
class EmailAddress < String
|
|
12
|
+
extend MetaschemaPattern
|
|
13
|
+
|
|
14
|
+
PATTERN = /\A.+@.+\z/
|
|
15
|
+
|
|
16
|
+
pattern PATTERN
|
|
17
|
+
|
|
18
|
+
def self.json_schema_format
|
|
19
|
+
"email"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.default_xsd_type
|
|
23
|
+
"xs:string"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema hostname datatype (#107): a non-empty string with
|
|
7
|
+
# no outer whitespace. The normative XSD carries no hostname pattern
|
|
8
|
+
# yet (the metaschema spec notes it is pending), so only the
|
|
9
|
+
# trimmed-string base constraint applies.
|
|
10
|
+
#
|
|
11
|
+
# @example Using Hostname type
|
|
12
|
+
# attribute :host, :hostname
|
|
13
|
+
class Hostname < String
|
|
14
|
+
extend MetaschemaPattern
|
|
15
|
+
|
|
16
|
+
PATTERN = /\A\S(.*\S)?\z/
|
|
17
|
+
|
|
18
|
+
pattern PATTERN
|
|
19
|
+
|
|
20
|
+
def self.json_schema_format
|
|
21
|
+
"idn-hostname"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.default_xsd_type
|
|
25
|
+
"xs:string"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema ip-v4-address datatype (#107): dotted-quad IPv4
|
|
7
|
+
# per the metaschema-datatypes pattern.
|
|
8
|
+
#
|
|
9
|
+
# @example Using IpV4Address type
|
|
10
|
+
# attribute :address, :ip_v4_address
|
|
11
|
+
class IpV4Address < String
|
|
12
|
+
extend MetaschemaPattern
|
|
13
|
+
|
|
14
|
+
OCTET = /(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])/
|
|
15
|
+
PATTERN = /\A#{OCTET.source}\.#{OCTET.source}\.#{OCTET.source}\.#{OCTET.source}\z/
|
|
16
|
+
|
|
17
|
+
def self.json_schema_format
|
|
18
|
+
"ipv4"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.default_xsd_type
|
|
22
|
+
"xs:string"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema ip-v6-address datatype (#107): RFC3513 IPv6 text
|
|
7
|
+
# form, per the metaschema-datatypes pattern (the generalized
|
|
8
|
+
# StackOverflow IPv6 expression with metaschema customizations,
|
|
9
|
+
# including link-local zones and embedded IPv4). Built by
|
|
10
|
+
# interpolation with no formatting whitespace, so the pattern's
|
|
11
|
+
# source is export-clean for xs:pattern facets.
|
|
12
|
+
#
|
|
13
|
+
# @example Using IpV6Address type
|
|
14
|
+
# attribute :address, :ip_v6_address
|
|
15
|
+
class IpV6Address < String
|
|
16
|
+
extend MetaschemaPattern
|
|
17
|
+
|
|
18
|
+
HEX4 = "[0-9a-fA-F]{1,4}"
|
|
19
|
+
OCTET = "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])"
|
|
20
|
+
EMBEDDED_V4 = "(#{OCTET}\\.){3,3}#{OCTET}".freeze
|
|
21
|
+
GROUPS = "(#{HEX4}:){7,7}#{HEX4}" \
|
|
22
|
+
"|([0-9a-fA-F]{1,4}:){1,7}:" \
|
|
23
|
+
"|([0-9a-fA-F]{1,4}:){1,6}:#{HEX4}" \
|
|
24
|
+
"|([0-9a-fA-F]{1,4}:){1,5}(:#{HEX4}){1,2}" \
|
|
25
|
+
"|([0-9a-fA-F]{1,4}:){1,4}(:#{HEX4}){1,3}" \
|
|
26
|
+
"|([0-9a-fA-F]{1,4}:){1,3}(:#{HEX4}){1,4}" \
|
|
27
|
+
"|([0-9a-fA-F]{1,4}:){1,2}(:#{HEX4}){1,5}" \
|
|
28
|
+
"|#{HEX4}:((:#{HEX4}){1,6})" \
|
|
29
|
+
"|:((:#{HEX4}){1,7}|:)" \
|
|
30
|
+
"|[fF][eE]80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}" \
|
|
31
|
+
"|::([fF]{4}(:0{1,4}){0,1}:){0,1}#{EMBEDDED_V4}" \
|
|
32
|
+
"|([0-9a-fA-F]{1,4}:){1,4}:#{EMBEDDED_V4}".freeze
|
|
33
|
+
PATTERN = /\A(#{GROUPS})\z/
|
|
34
|
+
|
|
35
|
+
pattern PATTERN
|
|
36
|
+
|
|
37
|
+
def self.json_schema_format
|
|
38
|
+
"ipv6"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.default_xsd_type
|
|
42
|
+
"xs:string"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# Pattern machinery shared by the NIST Metaschema string types (#107).
|
|
7
|
+
# Each including class defines a PATTERN regexp (also declared as the
|
|
8
|
+
# type's `pattern` facet, feeding runtime validation and XSD
|
|
9
|
+
# restriction emission); values cast to strings and `valid?` checks
|
|
10
|
+
# full-match against it.
|
|
11
|
+
module MetaschemaPattern
|
|
12
|
+
def valid?(value)
|
|
13
|
+
return false if value.nil?
|
|
14
|
+
|
|
15
|
+
self::PATTERN.match?(value.to_s)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def cast(value, _options = {})
|
|
19
|
+
return nil if value.nil?
|
|
20
|
+
return value if Utils.uninitialized?(value)
|
|
21
|
+
return value if value.is_a?(::String)
|
|
22
|
+
|
|
23
|
+
value.to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def serialize(value)
|
|
27
|
+
return nil if value.nil?
|
|
28
|
+
|
|
29
|
+
value.to_s
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema non-negative-integer datatype (#107): an integer
|
|
7
|
+
# equal to or greater than 0 (xs:nonNegativeInteger).
|
|
8
|
+
#
|
|
9
|
+
# @example Using NonNegativeInteger type
|
|
10
|
+
# attribute :index, :non_negative_integer
|
|
11
|
+
class NonNegativeInteger < Integer
|
|
12
|
+
inclusive(min: 0)
|
|
13
|
+
def self.valid?(value)
|
|
14
|
+
cast_value = cast(value)
|
|
15
|
+
|
|
16
|
+
cast_value.is_a?(::Integer) && cast_value >= 0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.default_xsd_type
|
|
20
|
+
"xs:nonNegativeInteger"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema positive-integer datatype (#107): an integer
|
|
7
|
+
# greater than 0 (xs:positiveInteger).
|
|
8
|
+
#
|
|
9
|
+
# @example Using PositiveInteger type
|
|
10
|
+
# attribute :ordinal, :positive_integer
|
|
11
|
+
class PositiveInteger < Integer
|
|
12
|
+
inclusive(min: 1)
|
|
13
|
+
def self.valid?(value)
|
|
14
|
+
cast_value = cast(value)
|
|
15
|
+
|
|
16
|
+
cast_value.is_a?(::Integer) && cast_value >= 1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.default_xsd_type
|
|
20
|
+
"xs:positiveInteger"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema token datatype (#107): an XML NMTOKEN-like value —
|
|
7
|
+
# a letter or underscore followed by letters, numbers, and the
|
|
8
|
+
# punctuation `.-_` — per the metaschema-datatypes pattern.
|
|
9
|
+
#
|
|
10
|
+
# @example Using Token type
|
|
11
|
+
# attribute :label, :token
|
|
12
|
+
class Token < String
|
|
13
|
+
extend MetaschemaPattern
|
|
14
|
+
|
|
15
|
+
PATTERN = /\A(\p{L}|_)(\p{L}|\p{N}|[.\-_])*\z/
|
|
16
|
+
|
|
17
|
+
pattern PATTERN
|
|
18
|
+
|
|
19
|
+
def self.default_xsd_type
|
|
20
|
+
"xs:string"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema uri-reference datatype (#107): a relative or
|
|
7
|
+
# absolute URI reference — any non-empty string without outer
|
|
8
|
+
# whitespace, unlike :uri which requires a scheme.
|
|
9
|
+
#
|
|
10
|
+
# @example Using UriReference type
|
|
11
|
+
# attribute :link, :uri_reference
|
|
12
|
+
class UriReference < String
|
|
13
|
+
extend MetaschemaPattern
|
|
14
|
+
|
|
15
|
+
PATTERN = /\A\S(.*\S)?\z/
|
|
16
|
+
|
|
17
|
+
pattern PATTERN
|
|
18
|
+
|
|
19
|
+
def self.json_schema_format
|
|
20
|
+
"uri-reference"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.default_xsd_type
|
|
24
|
+
"xs:anyURI"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema uuid datatype (#107): an RFC4122 UUID, restricted
|
|
7
|
+
# to versions 4 and 5 and the standard variant, per the
|
|
8
|
+
# metaschema-datatypes pattern.
|
|
9
|
+
#
|
|
10
|
+
# @example Using Uuid type
|
|
11
|
+
# attribute :identifier, :uuid
|
|
12
|
+
class Uuid < String
|
|
13
|
+
extend MetaschemaPattern
|
|
14
|
+
|
|
15
|
+
PATTERN = %r{\A[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[45][0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}\z}
|
|
16
|
+
|
|
17
|
+
pattern PATTERN
|
|
18
|
+
|
|
19
|
+
def self.default_xsd_type
|
|
20
|
+
"xs:string"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -126,6 +126,15 @@ from: nil)
|
|
|
126
126
|
"conflicting `#{key}` facets: #{existing} and #{incoming}"
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
# The JSON Schema `format` keyword for this type, or nil. The NIST
|
|
130
|
+
# Metaschema types declare the format the metaschema JSON
|
|
131
|
+
# representations use (ipv4, ipv6, email, uri-reference, duration,
|
|
132
|
+
# date, date-time); core types default to nil and their schemas
|
|
133
|
+
# carry no format keyword.
|
|
134
|
+
def json_schema_format
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
129
138
|
# Constrain ordered values via min/maxInclusive.
|
|
130
139
|
def inclusive(min: nil, max: nil)
|
|
131
140
|
min = cast_facet_value(min)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lutaml
|
|
4
|
+
module Model
|
|
5
|
+
module Type
|
|
6
|
+
# NIST Metaschema year-month-duration datatype (#107): an ISO 8601
|
|
7
|
+
# duration expressed in years and months (no time components), per
|
|
8
|
+
# the xs:yearMonthDuration form — P1Y6M, -P9M, P10M.
|
|
9
|
+
#
|
|
10
|
+
# @example Using YearMonthDuration type
|
|
11
|
+
# attribute :term, :year_month_duration
|
|
12
|
+
class YearMonthDuration < Duration
|
|
13
|
+
PATTERN = /\A-?P(?:[0-9]+Y(?:[0-9]+M)?|[0-9]+M)\z/
|
|
14
|
+
|
|
15
|
+
def self.valid?(value)
|
|
16
|
+
value.is_a?(::String) ? value.match?(PATTERN) : !cast(value).nil?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.json_schema_format
|
|
20
|
+
"duration"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.default_xsd_type
|
|
24
|
+
"xs:yearMonthDuration"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/lutaml/model/type.rb
CHANGED
|
@@ -24,6 +24,20 @@ module Lutaml
|
|
|
24
24
|
autoload :QName, "#{__dir__}/type/qname"
|
|
25
25
|
autoload :Base64Binary, "#{__dir__}/type/base64_binary"
|
|
26
26
|
autoload :HexBinary, "#{__dir__}/type/hex_binary"
|
|
27
|
+
autoload :MetaschemaPattern, "#{__dir__}/type/metaschema_pattern"
|
|
28
|
+
autoload :EmailAddress, "#{__dir__}/type/email_address"
|
|
29
|
+
autoload :Hostname, "#{__dir__}/type/hostname"
|
|
30
|
+
autoload :IpV4Address, "#{__dir__}/type/ip_v4_address"
|
|
31
|
+
autoload :IpV6Address, "#{__dir__}/type/ip_v6_address"
|
|
32
|
+
autoload :Token, "#{__dir__}/type/token"
|
|
33
|
+
autoload :UriReference, "#{__dir__}/type/uri_reference"
|
|
34
|
+
autoload :Uuid, "#{__dir__}/type/uuid"
|
|
35
|
+
autoload :NonNegativeInteger, "#{__dir__}/type/non_negative_integer"
|
|
36
|
+
autoload :PositiveInteger, "#{__dir__}/type/positive_integer"
|
|
37
|
+
autoload :DateWithTimezone, "#{__dir__}/type/date_with_timezone"
|
|
38
|
+
autoload :DateTimeWithTimezone, "#{__dir__}/type/date_time_with_timezone"
|
|
39
|
+
autoload :DayTimeDuration, "#{__dir__}/type/day_time_duration"
|
|
40
|
+
autoload :YearMonthDuration, "#{__dir__}/type/year_month_duration"
|
|
27
41
|
|
|
28
42
|
# Error classes (defined in error/type/ but under Type namespace)
|
|
29
43
|
autoload :InvalidValueError, "#{__dir__}/error/type/invalid_value_error"
|
|
@@ -55,6 +69,19 @@ module Lutaml
|
|
|
55
69
|
qname: "Lutaml::Model::Type::QName",
|
|
56
70
|
base64_binary: "Lutaml::Model::Type::Base64Binary",
|
|
57
71
|
hex_binary: "Lutaml::Model::Type::HexBinary",
|
|
72
|
+
email_address: "Lutaml::Model::Type::EmailAddress",
|
|
73
|
+
hostname: "Lutaml::Model::Type::Hostname",
|
|
74
|
+
ip_v4_address: "Lutaml::Model::Type::IpV4Address",
|
|
75
|
+
ip_v6_address: "Lutaml::Model::Type::IpV6Address",
|
|
76
|
+
token: "Lutaml::Model::Type::Token",
|
|
77
|
+
uri_reference: "Lutaml::Model::Type::UriReference",
|
|
78
|
+
uuid: "Lutaml::Model::Type::Uuid",
|
|
79
|
+
non_negative_integer: "Lutaml::Model::Type::NonNegativeInteger",
|
|
80
|
+
positive_integer: "Lutaml::Model::Type::PositiveInteger",
|
|
81
|
+
date_with_timezone: "Lutaml::Model::Type::DateWithTimezone",
|
|
82
|
+
date_time_with_timezone: "Lutaml::Model::Type::DateTimeWithTimezone",
|
|
83
|
+
day_time_duration: "Lutaml::Model::Type::DayTimeDuration",
|
|
84
|
+
year_month_duration: "Lutaml::Model::Type::YearMonthDuration",
|
|
58
85
|
}.freeze
|
|
59
86
|
|
|
60
87
|
BUILTIN_CLASSES =
|
data/lib/lutaml/model/version.rb
CHANGED
data/lib/lutaml/model.rb
CHANGED
|
@@ -164,6 +164,8 @@ module Lutaml
|
|
|
164
164
|
"#{__dir__}/model/error/import_model_with_root_error"
|
|
165
165
|
autoload :UnsupportedVersionError,
|
|
166
166
|
"#{__dir__}/model/error/unsupported_version_error"
|
|
167
|
+
autoload :UnknownMappingError,
|
|
168
|
+
"#{__dir__}/model/error/unknown_mapping_error"
|
|
167
169
|
autoload :InvalidChoiceRangeError,
|
|
168
170
|
"#{__dir__}/model/error/invalid_choice_range_error"
|
|
169
171
|
autoload :UnknownSequenceMappingError,
|
|
@@ -144,18 +144,19 @@ module Lutaml
|
|
|
144
144
|
return root if root.is_a?(Lutaml::Xml::DataModel::XmlElement)
|
|
145
145
|
|
|
146
146
|
mapper_class = options[:mapper_class] || root.class
|
|
147
|
-
xml_mapping = mapper_class.mappings_for(:xml)
|
|
147
|
+
xml_mapping = options[:mappings] || mapper_class.mappings_for(:xml)
|
|
148
148
|
|
|
149
149
|
return nil if xml_mapping.raw_mapping&.custom_methods&.[](:to)
|
|
150
150
|
|
|
151
151
|
yield(root)
|
|
152
|
-
mapper_class.transformation_for(
|
|
153
|
-
|
|
152
|
+
mapper_class.transformation_for(
|
|
153
|
+
:xml, register, mapping: options[:mappings]
|
|
154
|
+
).transform(root, options)
|
|
154
155
|
end
|
|
155
156
|
|
|
156
157
|
def render_xml_element(xml, xml_element, original_model, options)
|
|
157
158
|
mapper_class = options[:mapper_class] || xml_element.class
|
|
158
|
-
mapping = mapper_class.mappings_for(:xml)
|
|
159
|
+
mapping = options[:mappings] || mapper_class.mappings_for(:xml)
|
|
159
160
|
plan = declaration_plan_for(
|
|
160
161
|
xml_element,
|
|
161
162
|
mapping,
|
|
@@ -171,7 +172,7 @@ module Lutaml
|
|
|
171
172
|
|
|
172
173
|
def render_legacy_model(xml, options)
|
|
173
174
|
mapper_class = options[:mapper_class] || root.class
|
|
174
|
-
xml_mapping = mapper_class.mappings_for(:xml)
|
|
175
|
+
xml_mapping = options[:mappings] || mapper_class.mappings_for(:xml)
|
|
175
176
|
plan = declaration_plan_for(root, xml_mapping, options, mapper_class)
|
|
176
177
|
|
|
177
178
|
build_element_with_plan(xml, root, plan, options)
|
|
@@ -191,7 +192,8 @@ module Lutaml
|
|
|
191
192
|
stored_plan = nil
|
|
192
193
|
|
|
193
194
|
if original_model
|
|
194
|
-
mapping_for_original = options[:
|
|
195
|
+
mapping_for_original = options[:mappings] ||
|
|
196
|
+
options[:mapper_class]&.mappings_for(:xml) ||
|
|
195
197
|
original_model.class.mappings_for(:xml)
|
|
196
198
|
original_ns_uris = collect_original_namespace_uris(
|
|
197
199
|
original_model, mapping_for_original
|