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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +60 -0
  3. data/docs/_guides/keyvalue-serialization.adoc +75 -0
  4. data/docs/_pages/value_types.adoc +102 -0
  5. data/lib/compat/opal/lutaml_model_boot.rb +15 -0
  6. data/lib/lutaml/key_value/transform.rb +238 -30
  7. data/lib/lutaml/model/error/unknown_mapping_error.rb +18 -0
  8. data/lib/lutaml/model/schema/generator/property.rb +60 -9
  9. data/lib/lutaml/model/serialize/format_conversion.rb +115 -2
  10. data/lib/lutaml/model/serialize/transformation_builder.rb +4 -3
  11. data/lib/lutaml/model/transform.rb +29 -4
  12. data/lib/lutaml/model/transformation_registry.rb +7 -5
  13. data/lib/lutaml/model/type/date_time_with_timezone.rb +28 -0
  14. data/lib/lutaml/model/type/date_with_timezone.rb +28 -0
  15. data/lib/lutaml/model/type/day_time_duration.rb +35 -0
  16. data/lib/lutaml/model/type/email_address.rb +28 -0
  17. data/lib/lutaml/model/type/hostname.rb +30 -0
  18. data/lib/lutaml/model/type/ip_v4_address.rb +27 -0
  19. data/lib/lutaml/model/type/ip_v6_address.rb +47 -0
  20. data/lib/lutaml/model/type/metaschema_pattern.rb +34 -0
  21. data/lib/lutaml/model/type/non_negative_integer.rb +25 -0
  22. data/lib/lutaml/model/type/positive_integer.rb +25 -0
  23. data/lib/lutaml/model/type/token.rb +25 -0
  24. data/lib/lutaml/model/type/uri_reference.rb +29 -0
  25. data/lib/lutaml/model/type/uuid.rb +25 -0
  26. data/lib/lutaml/model/type/value.rb +9 -0
  27. data/lib/lutaml/model/type/year_month_duration.rb +29 -0
  28. data/lib/lutaml/model/type.rb +27 -0
  29. data/lib/lutaml/model/version.rb +1 -1
  30. data/lib/lutaml/model.rb +2 -0
  31. data/lib/lutaml/xml/adapter/xml_serializer.rb +8 -6
  32. data/lib/lutaml/xml/model_transform.rb +8 -4
  33. data/lib/lutaml/xml/schema/xsd_schema.rb +30 -9
  34. data/lib/lutaml/xml/transformation.rb +3 -2
  35. data/spec/lutaml/key_value/child_map_plan_spec.rb +169 -0
  36. data/spec/lutaml/model/metaschema_types_spec.rb +367 -0
  37. data/spec/lutaml/model/serialization_profiles_spec.rb +276 -0
  38. 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.126
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-22 00:00:00.000000000 Z
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