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
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Serialization mapping profiles (#176): multiple serialization models of
|
|
6
|
+
# the same format over one data model. A named mapping is a complete
|
|
7
|
+
# projection; selection is per call via `mapping:`; the base mapping
|
|
8
|
+
# stays the default. Runtime-constructed mapping objects wire with
|
|
9
|
+
# `mapping :name, mapping`.
|
|
10
|
+
module SerializationProfilesSpec
|
|
11
|
+
class Person < Lutaml::Model::Serializable
|
|
12
|
+
attribute :name, :string
|
|
13
|
+
attribute :email, :string
|
|
14
|
+
attribute :internal_id, :string
|
|
15
|
+
|
|
16
|
+
json do
|
|
17
|
+
map "name", to: :name
|
|
18
|
+
map "email", to: :email
|
|
19
|
+
map "internal_id", to: :internal_id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
json :compact do
|
|
23
|
+
map "name", to: :name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
yaml do
|
|
27
|
+
map "name", to: :name
|
|
28
|
+
map "email", to: :email
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
yaml :public do
|
|
32
|
+
map "full_name", to: :name
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Badge < Lutaml::Model::Serializable
|
|
37
|
+
attribute :code, :string
|
|
38
|
+
|
|
39
|
+
json do
|
|
40
|
+
map "code", to: :code
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Employee < Lutaml::Model::Serializable
|
|
45
|
+
attribute :name, :string
|
|
46
|
+
attribute :badge, Badge
|
|
47
|
+
|
|
48
|
+
json do
|
|
49
|
+
map "name", to: :name
|
|
50
|
+
map "badge", to: :badge
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
json :slim do
|
|
54
|
+
map "name", to: :name
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Collecting < Lutaml::Model::Serializable
|
|
59
|
+
attribute :name, :string
|
|
60
|
+
attribute :extras, :hash
|
|
61
|
+
|
|
62
|
+
json do
|
|
63
|
+
map "name", to: :name
|
|
64
|
+
map_unmapped to: :extras
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
json :only_name do
|
|
68
|
+
map "name", to: :name
|
|
69
|
+
map_unmapped to: :extras
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
hsh :only_name do
|
|
73
|
+
map "name", to: :name
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class Record < Lutaml::Model::Serializable
|
|
78
|
+
attribute :name, :string
|
|
79
|
+
attribute :email, :string
|
|
80
|
+
|
|
81
|
+
xml do
|
|
82
|
+
root "record"
|
|
83
|
+
map_element "name", to: :name
|
|
84
|
+
map_element "email", to: :email
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
xml :registry do
|
|
88
|
+
root "registry-entry"
|
|
89
|
+
map_attribute "name", to: :name
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
RSpec.describe "Serialization mapping profiles" do
|
|
94
|
+
let(:person) do
|
|
95
|
+
Person.new(name: "Ed", email: "ed@example.com", internal_id: "42")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "serializes with the base mapping when no profile is selected" do
|
|
99
|
+
expect(person.to_json).to eq(
|
|
100
|
+
'{"name":"Ed","email":"ed@example.com","internal_id":"42"}',
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "serializes through a declared named mapping" do
|
|
105
|
+
expect(person.to_json(mapping: :compact)).to eq('{"name":"Ed"}')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "parses through the same named mapping symmetrically" do
|
|
109
|
+
model = Person.from_json('{"name":"Ed"}', mapping: :compact)
|
|
110
|
+
|
|
111
|
+
expect(model.name).to eq("Ed")
|
|
112
|
+
expect(model.email).to be_nil
|
|
113
|
+
expect(model.internal_id).to be_nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "keeps the base mapping isolated from profile calls" do
|
|
117
|
+
3.times do
|
|
118
|
+
person.to_json(mapping: :compact)
|
|
119
|
+
expect(person.to_json).to include("internal_id")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "supports named mappings on other formats independently" do
|
|
124
|
+
expect(person.to_yaml(mapping: :public)).to eq("---\nfull_name: Ed\n")
|
|
125
|
+
expect(person.to_yaml).to eq("---\nname: Ed\nemail: ed@example.com\n")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "raises UnknownMappingError for an undeclared profile name" do
|
|
129
|
+
expect { person.to_json(mapping: :nope) }
|
|
130
|
+
.to raise_error(Lutaml::Model::UnknownMappingError, /:nope/)
|
|
131
|
+
expect { Person.from_json("{}", mapping: :nope) }
|
|
132
|
+
.to raise_error(Lutaml::Model::UnknownMappingError)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "selects through a mapping instance directly" do
|
|
136
|
+
mapping = Lutaml::Model::Config.mappings_class_for(:json).new
|
|
137
|
+
mapping.map "the_name", to: :name
|
|
138
|
+
|
|
139
|
+
expect(person.to_json(mapping: mapping)).to eq('{"the_name":"Ed"}')
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "rejects a mapping instance of another format" do
|
|
143
|
+
yaml_mapping = Lutaml::Model::Config.mappings_class_for(:yaml).new
|
|
144
|
+
|
|
145
|
+
expect { person.to_json(mapping: yaml_mapping) }
|
|
146
|
+
.to raise_error(ArgumentError, /must be a Lutaml::Json::Adapter::Mapping/)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "wires a runtime-constructed mapping under a name" do
|
|
150
|
+
mapping = Lutaml::Model::Config.mappings_class_for(:yaml).new
|
|
151
|
+
mapping.map "contact", to: :email
|
|
152
|
+
Person.mapping :contact, mapping
|
|
153
|
+
|
|
154
|
+
expect(person.to_yaml(mapping: :contact)).to eq("---\ncontact: ed@example.com\n")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "infers the format of a wired mapping and validates its class" do
|
|
158
|
+
json_mapping = Lutaml::Model::Config.mappings_class_for(:json).new
|
|
159
|
+
json_mapping.map "n", to: :name
|
|
160
|
+
Person.mapping :short, json_mapping
|
|
161
|
+
|
|
162
|
+
expect(person.to_json(mapping: :short)).to eq('{"n":"Ed"}')
|
|
163
|
+
expect { Person.mapping :bad, json_mapping, format: :yaml }
|
|
164
|
+
.to raise_error(ArgumentError, /mapping class/)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it "removes a wired mapping with a nil mapping object" do
|
|
168
|
+
mapping = Lutaml::Model::Config.mappings_class_for(:json).new
|
|
169
|
+
mapping.map "n", to: :name
|
|
170
|
+
Person.mapping :temp, mapping
|
|
171
|
+
expect(person.to_json(mapping: :temp)).to eq('{"n":"Ed"}')
|
|
172
|
+
|
|
173
|
+
Person.mapping :temp, nil, format: :json
|
|
174
|
+
expect(Person.serialization_mappings(:json)).not_to have_key(:temp)
|
|
175
|
+
expect { person.to_json(mapping: :temp) }
|
|
176
|
+
.to raise_error(Lutaml::Model::UnknownMappingError)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "lists declared profiles through serialization_mappings" do
|
|
180
|
+
stub_const("IsoOnly", Class.new(Lutaml::Model::Serializable) do
|
|
181
|
+
attribute :name, :string
|
|
182
|
+
|
|
183
|
+
json do
|
|
184
|
+
map "name", to: :name
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
json :iso do
|
|
188
|
+
map "iso_name", to: :name
|
|
189
|
+
end
|
|
190
|
+
end)
|
|
191
|
+
|
|
192
|
+
expect(IsoOnly.serialization_mappings(:json).keys)
|
|
193
|
+
.to contain_exactly(:iso)
|
|
194
|
+
expect(IsoOnly.serialization_mappings(:toml)).to eq({})
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "replaces a profile on re-declaration" do
|
|
198
|
+
Person.json :compact do
|
|
199
|
+
map "codename", to: :name
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
expect(person.to_json(mapping: :compact)).to eq('{"codename":"Ed"}')
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "composes only: with a selected profile" do
|
|
206
|
+
expect(person.to_json(mapping: :compact, except: [:name])).to eq("{}")
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "keeps child models on their own base mapping" do
|
|
210
|
+
employee = Employee.new(name: "Ed", badge: Badge.new(code: "B1"))
|
|
211
|
+
|
|
212
|
+
expect(employee.to_json(mapping: :slim)).to eq('{"name":"Ed"}')
|
|
213
|
+
expect(employee.to_json).to eq(
|
|
214
|
+
'{"name":"Ed","badge":{"code":"B1"}}',
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it "collects map_unmapped keys per profile" do
|
|
219
|
+
model = Collecting.from_json(
|
|
220
|
+
'{"name":"Ed","surprise":1}', mapping: :only_name
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
expect(model.extras).to eq("surprise" => 1)
|
|
224
|
+
|
|
225
|
+
collected = Collecting.from_json('{"name":"Ed","surprise":1}')
|
|
226
|
+
|
|
227
|
+
expect(collected.extras).to eq("surprise" => 1)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it "serializes a named XML profile with its own root and mappings" do
|
|
231
|
+
record = Record.new(name: "Ed", email: "ed@example.com")
|
|
232
|
+
|
|
233
|
+
expect(record.to_xml(mapping: :registry).strip)
|
|
234
|
+
.to eq("<registry-entry name=\"Ed\"/>")
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it "parses a named XML profile symmetrically" do
|
|
238
|
+
model = Record.from_xml("<registry-entry name=\"Ed\"/>",
|
|
239
|
+
mapping: :registry)
|
|
240
|
+
|
|
241
|
+
expect(model.name).to eq("Ed")
|
|
242
|
+
expect(model.email).to be_uninitialized
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it "keeps the base XML mapping isolated from profile calls" do
|
|
246
|
+
record = Record.new(name: "Ed", email: "ed@example.com")
|
|
247
|
+
base = record.to_xml
|
|
248
|
+
|
|
249
|
+
record.to_xml(mapping: :registry)
|
|
250
|
+
expect(record.to_xml).to eq(base)
|
|
251
|
+
expect(base).to include("<name>Ed</name>")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it "generates the schema of the selected XML profile" do
|
|
255
|
+
base = Lutaml::Model::Schema.to_xsd(Record)
|
|
256
|
+
profile = Lutaml::Model::Schema.to_xsd(Record, mapping: :registry)
|
|
257
|
+
|
|
258
|
+
expect(base).to include('name="record"')
|
|
259
|
+
expect(base).to include("email")
|
|
260
|
+
expect(profile).to include('name="registry-entry"')
|
|
261
|
+
expect(profile).to include('name="name"')
|
|
262
|
+
expect(profile).not_to include("email")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it "names the hash DSL in the unknown-profile error" do
|
|
266
|
+
expect { Collecting.new.to_hash(mapping: :nope) }
|
|
267
|
+
.to raise_error(Lutaml::Model::UnknownMappingError, /hsh :nope/)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "round-trips a profile over the hash format" do
|
|
271
|
+
model = Collecting.new(name: "Ed")
|
|
272
|
+
|
|
273
|
+
expect(model.to_hash(mapping: :only_name)).to eq("name" => "Ed")
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.128
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -497,6 +497,7 @@ files:
|
|
|
497
497
|
- lib/lutaml/model/error/union_schema_unsupported_error.rb
|
|
498
498
|
- lib/lutaml/model/error/unknown_adapter_type_error.rb
|
|
499
499
|
- lib/lutaml/model/error/unknown_discriminator_error.rb
|
|
500
|
+
- lib/lutaml/model/error/unknown_mapping_error.rb
|
|
500
501
|
- lib/lutaml/model/error/unknown_sequence_mapping_error.rb
|
|
501
502
|
- lib/lutaml/model/error/unknown_type_error.rb
|
|
502
503
|
- lib/lutaml/model/error/unresolvable_type_error.rb
|
|
@@ -644,21 +645,35 @@ files:
|
|
|
644
645
|
- lib/lutaml/model/type/boolean.rb
|
|
645
646
|
- lib/lutaml/model/type/date.rb
|
|
646
647
|
- lib/lutaml/model/type/date_time.rb
|
|
648
|
+
- lib/lutaml/model/type/date_time_with_timezone.rb
|
|
649
|
+
- lib/lutaml/model/type/date_with_timezone.rb
|
|
650
|
+
- lib/lutaml/model/type/day_time_duration.rb
|
|
647
651
|
- lib/lutaml/model/type/decimal.rb
|
|
648
652
|
- lib/lutaml/model/type/duration.rb
|
|
653
|
+
- lib/lutaml/model/type/email_address.rb
|
|
649
654
|
- lib/lutaml/model/type/float.rb
|
|
650
655
|
- lib/lutaml/model/type/hash.rb
|
|
651
656
|
- lib/lutaml/model/type/hex_binary.rb
|
|
657
|
+
- lib/lutaml/model/type/hostname.rb
|
|
652
658
|
- lib/lutaml/model/type/integer.rb
|
|
659
|
+
- lib/lutaml/model/type/ip_v4_address.rb
|
|
660
|
+
- lib/lutaml/model/type/ip_v6_address.rb
|
|
661
|
+
- lib/lutaml/model/type/metaschema_pattern.rb
|
|
662
|
+
- lib/lutaml/model/type/non_negative_integer.rb
|
|
663
|
+
- lib/lutaml/model/type/positive_integer.rb
|
|
653
664
|
- lib/lutaml/model/type/qname.rb
|
|
654
665
|
- lib/lutaml/model/type/reference.rb
|
|
655
666
|
- lib/lutaml/model/type/string.rb
|
|
656
667
|
- lib/lutaml/model/type/symbol.rb
|
|
657
668
|
- lib/lutaml/model/type/time.rb
|
|
658
669
|
- lib/lutaml/model/type/time_without_date.rb
|
|
670
|
+
- lib/lutaml/model/type/token.rb
|
|
659
671
|
- lib/lutaml/model/type/uninitialized_class_guard.rb
|
|
660
672
|
- lib/lutaml/model/type/uri.rb
|
|
673
|
+
- lib/lutaml/model/type/uri_reference.rb
|
|
674
|
+
- lib/lutaml/model/type/uuid.rb
|
|
661
675
|
- lib/lutaml/model/type/value.rb
|
|
676
|
+
- lib/lutaml/model/type/year_month_duration.rb
|
|
662
677
|
- lib/lutaml/model/type_context.rb
|
|
663
678
|
- lib/lutaml/model/type_registry.rb
|
|
664
679
|
- lib/lutaml/model/type_resolver.rb
|
|
@@ -1696,6 +1711,7 @@ files:
|
|
|
1696
1711
|
- spec/lutaml/json/yeptris_adapter_spec.rb
|
|
1697
1712
|
- spec/lutaml/jsonld/adapter_spec.rb
|
|
1698
1713
|
- spec/lutaml/key_value/adapter/toml/teptris_adapter_spec.rb
|
|
1714
|
+
- spec/lutaml/key_value/child_map_plan_spec.rb
|
|
1699
1715
|
- spec/lutaml/key_value/group_instantiate_spec.rb
|
|
1700
1716
|
- spec/lutaml/key_value/subclass_remap_spec.rb
|
|
1701
1717
|
- spec/lutaml/key_value/transformation/collection_serializer_spec.rb
|
|
@@ -1785,6 +1801,7 @@ files:
|
|
|
1785
1801
|
- spec/lutaml/model/map_unmapped_spec.rb
|
|
1786
1802
|
- spec/lutaml/model/mapping_rule_perf_guard_spec.rb
|
|
1787
1803
|
- spec/lutaml/model/mapping_type_validation_spec.rb
|
|
1804
|
+
- spec/lutaml/model/metaschema_types_spec.rb
|
|
1788
1805
|
- spec/lutaml/model/mixed_content_spec.rb
|
|
1789
1806
|
- spec/lutaml/model/model_import_reentry_spec.rb
|
|
1790
1807
|
- spec/lutaml/model/model_transformer_spec.rb
|
|
@@ -1827,6 +1844,7 @@ files:
|
|
|
1827
1844
|
- spec/lutaml/model/serializable_instantiate_spec.rb
|
|
1828
1845
|
- spec/lutaml/model/serializable_spec.rb
|
|
1829
1846
|
- spec/lutaml/model/serializable_validation_spec.rb
|
|
1847
|
+
- spec/lutaml/model/serialization_profiles_spec.rb
|
|
1830
1848
|
- spec/lutaml/model/serialize/attribute_definition_block_form_spec.rb
|
|
1831
1849
|
- spec/lutaml/model/serialize/builder_spec.rb
|
|
1832
1850
|
- spec/lutaml/model/serialize_perf_guard_spec.rb
|