lutaml-model 0.8.42 → 0.8.44
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/docs/_guides/advanced-mapping.adoc +66 -0
- data/docs/_tutorials/polymorphic-models-tutorial.adoc +2 -0
- data/lib/lutaml/key_value/mapping.rb +3 -1
- data/lib/lutaml/key_value/mapping_rule.rb +4 -1
- data/lib/lutaml/key_value/transform.rb +2 -0
- data/lib/lutaml/key_value/transformation/rule_compiler.rb +3 -0
- data/lib/lutaml/model/collection_handler.rb +11 -1
- data/lib/lutaml/model/mapping/mapping_rule.rb +23 -1
- data/lib/lutaml/model/serialize/initialization.rb +7 -2
- data/lib/lutaml/model/serialize.rb +12 -3
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/adapter_element.rb +2 -1
- data/lib/lutaml/xml/element.rb +8 -2
- data/lib/lutaml/xml/mapping.rb +20 -1
- data/lib/lutaml/xml/mapping_rule.rb +13 -1
- data/lib/lutaml/xml/model_transform.rb +69 -5
- data/lib/lutaml/xml/parse_session.rb +33 -2
- data/lib/lutaml/xml/transformation/element_builder.rb +25 -6
- data/lib/lutaml/xml/transformation/order_reconciler.rb +16 -1
- data/lib/lutaml/xml/transformation/ordered_applier.rb +73 -2
- data/lib/lutaml/xml/transformation/rule_compiler.rb +2 -0
- data/lib/lutaml/xml/xml_element.rb +20 -1
- data/spec/lutaml/key_value/transformation/rule_compiler_spec.rb +16 -6
- data/spec/lutaml/key_value/transformation_spec.rb +4 -1
- data/spec/lutaml/model/attributes_memo_clear_race_spec.rb +90 -0
- data/spec/lutaml/model/hydrate_only_mapping_spec.rb +47 -0
- data/spec/lutaml/xml/when_attribute_ordered_spec.rb +148 -0
- data/spec/lutaml/xml/when_attribute_spec.rb +225 -0
- metadata +6 -2
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# lutaml-model#88: attribute-value dispatch on a shared wire name.
|
|
6
|
+
# `map_element "component", to: :guidance, when_attribute: { "type" =>
|
|
7
|
+
# "guidance" }` selects only the occurrences whose sibling attribute carries
|
|
8
|
+
# the expected value, and re-emits the discriminator on serialization.
|
|
9
|
+
class WhenAttrComponent < Lutaml::Model::Serializable
|
|
10
|
+
attribute :text, :string
|
|
11
|
+
|
|
12
|
+
xml do
|
|
13
|
+
element "component"
|
|
14
|
+
map_element "text", to: :text
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class WhenAttrRequirement < Lutaml::Model::Serializable
|
|
19
|
+
attribute :guidance, WhenAttrComponent, collection: true
|
|
20
|
+
attribute :purpose, WhenAttrComponent, collection: true
|
|
21
|
+
attribute :test_method, WhenAttrComponent, collection: true
|
|
22
|
+
|
|
23
|
+
xml do
|
|
24
|
+
element "requirement"
|
|
25
|
+
map_element "component", to: :guidance,
|
|
26
|
+
when_attribute: { "type" => "guidance" }
|
|
27
|
+
map_element "component", to: :purpose,
|
|
28
|
+
when_attribute: { "type" => "purpose" }
|
|
29
|
+
map_element "component", to: :test_method,
|
|
30
|
+
when_attribute: { type: "test-method" }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
RSpec.describe "when_attribute discriminator mappings" do
|
|
35
|
+
before do
|
|
36
|
+
stub_const("WhenAttr::Component", WhenAttrComponent)
|
|
37
|
+
stub_const("WhenAttr::Requirement", WhenAttrRequirement)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
let(:xml) do
|
|
41
|
+
<<~XML
|
|
42
|
+
<requirement>
|
|
43
|
+
<component type="guidance"><text>g1</text></component>
|
|
44
|
+
<component type="purpose"><text>p1</text></component>
|
|
45
|
+
<component type="guidance"><text>g2</text></component>
|
|
46
|
+
<component type="test-method"><text>t1</text></component>
|
|
47
|
+
</requirement>
|
|
48
|
+
XML
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "dispatches occurrences by the discriminator value" do
|
|
52
|
+
req = WhenAttr::Requirement.from_xml(xml)
|
|
53
|
+
|
|
54
|
+
expect(req.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
55
|
+
expect(req.purpose.map(&:text)).to eq(["p1"])
|
|
56
|
+
expect(req.test_method.map(&:text)).to eq(["t1"])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "keeps document order within each target attribute" do
|
|
60
|
+
req = WhenAttr::Requirement.from_xml(xml)
|
|
61
|
+
expect(req.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "drops occurrences whose discriminator is not covered" do
|
|
65
|
+
doc = '<requirement><component type="unknown"><text>x</text></component></requirement>'
|
|
66
|
+
req = WhenAttr::Requirement.from_xml(doc)
|
|
67
|
+
|
|
68
|
+
expect(req.guidance).to be_empty
|
|
69
|
+
expect(req.purpose).to be_empty
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "re-emits the discriminator attribute on serialization" do
|
|
73
|
+
round = WhenAttr::Requirement.from_xml(WhenAttr::Requirement.from_xml(xml).to_xml)
|
|
74
|
+
|
|
75
|
+
expect(round.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
76
|
+
expect(round.purpose.map(&:text)).to eq(["p1"])
|
|
77
|
+
expect(round.test_method.map(&:text)).to eq(["t1"])
|
|
78
|
+
|
|
79
|
+
serialized = WhenAttr::Requirement.from_xml(xml).to_xml
|
|
80
|
+
expect(serialized).to include('type="guidance"')
|
|
81
|
+
expect(serialized).to include('type="purpose"')
|
|
82
|
+
expect(serialized).to include('type="test-method"')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "does not emit a discriminator for ordinary rules" do
|
|
86
|
+
plain = Class.new(Lutaml::Model::Serializable) do
|
|
87
|
+
attribute :text, :string
|
|
88
|
+
|
|
89
|
+
xml do
|
|
90
|
+
element "plain"
|
|
91
|
+
map_element "text", to: :text
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
expect(plain.from_xml("<plain><text>t</text></plain>").to_xml)
|
|
96
|
+
.not_to include("type=")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "survives deep_dup of mappings" do
|
|
100
|
+
mapping = WhenAttr::Requirement.mappings_for(:xml)
|
|
101
|
+
duped = mapping.deep_dup.mappings.find do |r|
|
|
102
|
+
r.to == :guidance
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
expect(duped.when_attribute).to eq("type" => "guidance")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "rejects non-string/symbol discriminator shapes" do
|
|
109
|
+
expect do
|
|
110
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
111
|
+
attribute :x, :string
|
|
112
|
+
|
|
113
|
+
xml do
|
|
114
|
+
element "x"
|
|
115
|
+
map_element "x", to: :x, when_attribute: { "type" => 42 }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end.to raise_error(Lutaml::Model::IncorrectMappingArgumentsError, /when_attribute/)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "is rejected on attribute mappings" do
|
|
122
|
+
expect do
|
|
123
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
124
|
+
attribute :x, :string
|
|
125
|
+
|
|
126
|
+
xml do
|
|
127
|
+
element "x"
|
|
128
|
+
map_attribute "x", to: :x, when_attribute: { "type" => "guidance" }
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end.to raise_error(ArgumentError)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Orthogonality guard: `when_attribute` partitions occurrences across
|
|
135
|
+
# attributes; `polymorphic` dispatches the class of each hydrated item.
|
|
136
|
+
# Different axes over the same discriminator concept — they compose on
|
|
137
|
+
# one rule rather than substitute for each other.
|
|
138
|
+
describe "vs polymorphic dispatch" do
|
|
139
|
+
before do
|
|
140
|
+
stub_const("AxisComp", Class.new(Lutaml::Model::Serializable) do
|
|
141
|
+
attribute :text, :string
|
|
142
|
+
end)
|
|
143
|
+
stub_const("AxisGuidanceComp", Class.new(AxisComp))
|
|
144
|
+
stub_const("AxisPurposeComp", Class.new(AxisComp))
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
let(:axes_xml) do
|
|
148
|
+
<<~XML
|
|
149
|
+
<req>
|
|
150
|
+
<component type="guidance"><text>g1</text></component>
|
|
151
|
+
<component type="purpose"><text>p1</text></component>
|
|
152
|
+
<component type="guidance"><text>g2</text></component>
|
|
153
|
+
</req>
|
|
154
|
+
XML
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "polymorphic keeps one attribute and dispatches classes" do
|
|
158
|
+
poly = Class.new(Lutaml::Model::Serializable) do
|
|
159
|
+
attribute :components, AxisComp, collection: true
|
|
160
|
+
|
|
161
|
+
xml do
|
|
162
|
+
element "req"
|
|
163
|
+
map_element "component", to: :components, polymorphic: {
|
|
164
|
+
attribute: "type",
|
|
165
|
+
class_map: {
|
|
166
|
+
"guidance" => "AxisGuidanceComp",
|
|
167
|
+
"purpose" => "AxisPurposeComp",
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
parsed = poly.from_xml(axes_xml)
|
|
174
|
+
expect(parsed.components.map(&:class))
|
|
175
|
+
.to eq([AxisGuidanceComp, AxisPurposeComp, AxisGuidanceComp])
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "when_attribute keeps one class and partitions attributes" do
|
|
179
|
+
partitioned = Class.new(Lutaml::Model::Serializable) do
|
|
180
|
+
attribute :guidance, AxisComp, collection: true
|
|
181
|
+
attribute :purpose, AxisComp, collection: true
|
|
182
|
+
|
|
183
|
+
xml do
|
|
184
|
+
element "req"
|
|
185
|
+
map_element "component", to: :guidance,
|
|
186
|
+
when_attribute: { "type" => "guidance" }
|
|
187
|
+
map_element "component", to: :purpose,
|
|
188
|
+
when_attribute: { "type" => "purpose" }
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
parsed = partitioned.from_xml(axes_xml)
|
|
193
|
+
expect(parsed.guidance.map(&:text)).to eq(%w[g1 g2])
|
|
194
|
+
expect(parsed.purpose.map(&:text)).to eq(["p1"])
|
|
195
|
+
expect(parsed.guidance.first.class).to eq(AxisComp)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it "composes: partition and class dispatch on the same rule" do
|
|
199
|
+
both = Class.new(Lutaml::Model::Serializable) do
|
|
200
|
+
attribute :guidance, AxisComp, collection: true
|
|
201
|
+
attribute :purpose, AxisComp, collection: true
|
|
202
|
+
|
|
203
|
+
xml do
|
|
204
|
+
element "req"
|
|
205
|
+
map_element "component", to: :guidance,
|
|
206
|
+
when_attribute: { "type" => "guidance" },
|
|
207
|
+
polymorphic: {
|
|
208
|
+
attribute: "type",
|
|
209
|
+
class_map: { "guidance" => "AxisGuidanceComp" },
|
|
210
|
+
}
|
|
211
|
+
map_element "component", to: :purpose,
|
|
212
|
+
when_attribute: { "type" => "purpose" },
|
|
213
|
+
polymorphic: {
|
|
214
|
+
attribute: "type",
|
|
215
|
+
class_map: { "purpose" => "AxisPurposeComp" },
|
|
216
|
+
}
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
parsed = both.from_xml(axes_xml)
|
|
221
|
+
expect(parsed.guidance.map(&:class)).to eq([AxisGuidanceComp, AxisGuidanceComp])
|
|
222
|
+
expect(parsed.purpose.map(&:class)).to eq([AxisPurposeComp])
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
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.44
|
|
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-09-
|
|
11
|
+
date: 2026-09-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -1695,6 +1695,7 @@ files:
|
|
|
1695
1695
|
- spec/lutaml/model/attribute_form_default_spec.rb
|
|
1696
1696
|
- spec/lutaml/model/attribute_restriction_spec.rb
|
|
1697
1697
|
- spec/lutaml/model/attribute_spec.rb
|
|
1698
|
+
- spec/lutaml/model/attributes_memo_clear_race_spec.rb
|
|
1698
1699
|
- spec/lutaml/model/boot_manifest_spec.rb
|
|
1699
1700
|
- spec/lutaml/model/cached_type_resolver_spec.rb
|
|
1700
1701
|
- spec/lutaml/model/cardinality_strict_spec.rb
|
|
@@ -1737,6 +1738,7 @@ files:
|
|
|
1737
1738
|
- spec/lutaml/model/global_register_spec.rb
|
|
1738
1739
|
- spec/lutaml/model/group_spec.rb
|
|
1739
1740
|
- spec/lutaml/model/hash/adapter_spec.rb
|
|
1741
|
+
- spec/lutaml/model/hydrate_only_mapping_spec.rb
|
|
1740
1742
|
- spec/lutaml/model/import_registry_spec.rb
|
|
1741
1743
|
- spec/lutaml/model/included_spec.rb
|
|
1742
1744
|
- spec/lutaml/model/inheritance_spec.rb
|
|
@@ -2004,6 +2006,8 @@ files:
|
|
|
2004
2006
|
- spec/lutaml/xml/validate_xml_with_constructs_spec.rb
|
|
2005
2007
|
- spec/lutaml/xml/validate_xml_with_spec.rb
|
|
2006
2008
|
- spec/lutaml/xml/w3c_types_spec.rb
|
|
2009
|
+
- spec/lutaml/xml/when_attribute_ordered_spec.rb
|
|
2010
|
+
- spec/lutaml/xml/when_attribute_spec.rb
|
|
2007
2011
|
- spec/lutaml/xml/xml_adapter_spec.rb
|
|
2008
2012
|
- spec/lutaml/xml/xml_declaration_spec.rb
|
|
2009
2013
|
- spec/lutaml/xml/xml_element_guard_spec.rb
|