lutaml-model 0.8.134 → 0.8.136
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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78585d088d67236f3378f1860c5916e37a1bad8bde58bb48954fe8057ada72b9
|
|
4
|
+
data.tar.gz: afbf8b41d1b733685ad4d066c07e5217eff0674a1eb66bbbc75e6d9cdd2bf31e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c2b9f8b00bfcc541d7ccebc179c9d0ce13fc530c594d605d7c4aeafd5dc1eb386c253eebdcc597a3257be3978e1fe74b8ba19b9bc05053d45a4662bd0bae7c63
|
|
7
|
+
data.tar.gz: 5e739f42a165ca2280473a03addd0418061f584e28d1f0d8fc7a015e0855a30e56dab47b47024bbc3fdc4ded50927654e2659a5d00ea5bd68b6c997cfca54224
|
|
@@ -28,6 +28,10 @@ NATIVE_ONLY_PATTERNS = [
|
|
|
28
28
|
%r{/schema_builder/(nokogiri|oga)\b},
|
|
29
29
|
%r{/schema/builder/(nokogiri|oga)\b},
|
|
30
30
|
%r{\Alutaml/model/cli\z},
|
|
31
|
+
# leptris is an FFI wrapper over a native library — Opal::Builder dies
|
|
32
|
+
# on its requires (and FFI itself does not exist under Opal); the
|
|
33
|
+
# adapter probe degrades to moxml at runtime (#1008).
|
|
34
|
+
%r{\Alutaml/xml/leptris},
|
|
31
35
|
].freeze
|
|
32
36
|
|
|
33
37
|
# Match `autoload :Name, "...anything..."` where the string argument may
|
|
@@ -148,7 +148,6 @@ require "lutaml/xml/format"
|
|
|
148
148
|
require "lutaml/xml/format_chooser"
|
|
149
149
|
require "lutaml/xml/hoisting_algorithm"
|
|
150
150
|
require "lutaml/xml/html_entities"
|
|
151
|
-
require "lutaml/xml/leptris"
|
|
152
151
|
require "lutaml/xml/listener"
|
|
153
152
|
require "lutaml/xml/mapping"
|
|
154
153
|
require "lutaml/xml/mapping_rule"
|
|
@@ -399,12 +398,6 @@ require "lutaml/xml/error/namespace_mismatch_error"
|
|
|
399
398
|
require "lutaml/xml/error/schema_validation_error"
|
|
400
399
|
require "lutaml/xml/error/xml_configuration_error"
|
|
401
400
|
require "lutaml/xml/error/xml_error"
|
|
402
|
-
require "lutaml/xml/leptris/native_emission"
|
|
403
|
-
require "lutaml/xml/leptris/plan_compiler"
|
|
404
|
-
require "lutaml/xml/leptris/plan_hydrator"
|
|
405
|
-
require "lutaml/xml/leptris/plan_order"
|
|
406
|
-
require "lutaml/xml/leptris/plan_serializer"
|
|
407
|
-
require "lutaml/xml/leptris/plan_walk"
|
|
408
401
|
require "lutaml/xml/oga/element"
|
|
409
402
|
require "lutaml/xml/ox/element"
|
|
410
403
|
require "lutaml/xml/rexml/element"
|
data/lib/lutaml/model/union.rb
CHANGED
|
@@ -98,7 +98,26 @@ module Lutaml
|
|
|
98
98
|
return [member, casted] unless casted == :no_match
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
widest_model_member(value, members, format, register) ||
|
|
102
|
+
lenient_model_member(value, members, format, register)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# No member maps every key, so the hash carries keys no rule claims
|
|
106
|
+
# (extension keywords, forward-compatible vocabularies): the model
|
|
107
|
+
# member mapping the most of its keys, the earlier one on a tie, as
|
|
108
|
+
# the keys no rule claims are ignored outside a union (#987).
|
|
109
|
+
def widest_model_member(value, members, format, register)
|
|
110
|
+
keys = input_keys(value, format)
|
|
111
|
+
return nil if keys.nil? || keys.empty?
|
|
112
|
+
|
|
113
|
+
counts = members.select { |member| model_member?(member) }.to_h do |member|
|
|
114
|
+
field_names = member_field_names(member, format, register)
|
|
115
|
+
[member, keys.count { |key| field_names.include?(key) }]
|
|
116
|
+
end
|
|
117
|
+
member, count = counts.max_by.with_index { |(_, mapped), index| [mapped, -index] }
|
|
118
|
+
return nil if member.nil? || count.zero?
|
|
119
|
+
|
|
120
|
+
[member, build_model(member, value, format, register)]
|
|
102
121
|
end
|
|
103
122
|
|
|
104
123
|
# With exactly one model member, a Hash that also carries unmapped
|
|
@@ -170,14 +189,12 @@ module Lutaml
|
|
|
170
189
|
member.is_a?(::Class) && member.include?(Serialize)
|
|
171
190
|
end
|
|
172
191
|
|
|
173
|
-
# Structural selection:
|
|
174
|
-
# member's mapped fields
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
# existing member instance conforms as-is, keeping conformance
|
|
180
|
-
# idempotent — values can re-enter casting (e.g. a value
|
|
192
|
+
# Structural (key-coverage) selection: every input key maps to one of
|
|
193
|
+
# the member's mapped fields, which is what tells members sharing a
|
|
194
|
+
# key apart. A hash no member covers falls to widest_model_member.
|
|
195
|
+
# Once selected, field values cast leniently via the member's normal
|
|
196
|
+
# path. An existing member instance conforms as-is, keeping
|
|
197
|
+
# conformance idempotent — values can re-enter casting (e.g. a value
|
|
181
198
|
# transformer builds the member before the cast runs).
|
|
182
199
|
def conform_model(member, value, format, register)
|
|
183
200
|
return value if value.is_a?(member)
|
|
@@ -186,13 +203,17 @@ module Lutaml
|
|
|
186
203
|
return :no_match if keys.nil? || keys.empty?
|
|
187
204
|
|
|
188
205
|
field_names = member_field_names(member, format, register)
|
|
189
|
-
return :no_match unless keys.
|
|
206
|
+
return :no_match unless keys.all? do |key|
|
|
190
207
|
field_names.include?(key)
|
|
191
208
|
end
|
|
192
209
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
210
|
+
build_model(member, value, format, register)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Plain-Ruby assignment (format nil) builds straight from the
|
|
214
|
+
# attribute hash, mirroring how non-union model attributes accept
|
|
215
|
+
# `Model.new(child: { ... })`; a format builds via the mappings.
|
|
216
|
+
def build_model(member, value, format, register)
|
|
196
217
|
return member.new(value) if format.nil?
|
|
197
218
|
|
|
198
219
|
member.apply_mappings(value, format, register: register)
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -710,6 +710,76 @@ RSpec.describe "Union-typed attributes (issue #190)" do
|
|
|
710
710
|
end
|
|
711
711
|
end
|
|
712
712
|
|
|
713
|
+
describe "model members that share a key" do
|
|
714
|
+
# Two members both map "source"; only their other keys tell them
|
|
715
|
+
# apart. The member mapping every key wins over an earlier one that
|
|
716
|
+
# maps only the shared key, as before #987; with an extension key
|
|
717
|
+
# neither maps every key, so the member mapping the most keys wins.
|
|
718
|
+
let(:report) do
|
|
719
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
720
|
+
attribute :source, :string
|
|
721
|
+
attribute :issues, :string, collection: true
|
|
722
|
+
key_value do
|
|
723
|
+
map "source", to: :source
|
|
724
|
+
map "issues", to: :issues
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
let(:conversion) do
|
|
730
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
731
|
+
attribute :source, :string
|
|
732
|
+
attribute :target, :string
|
|
733
|
+
key_value do
|
|
734
|
+
map "source", to: :source
|
|
735
|
+
map "target", to: :target
|
|
736
|
+
end
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
let(:holder) do
|
|
741
|
+
members = [report, conversion]
|
|
742
|
+
Class.new(Lutaml::Model::Serializable) do
|
|
743
|
+
attribute :result, members
|
|
744
|
+
key_value { map "result", to: :result }
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
before do
|
|
749
|
+
stub_const("SharedKeyReport", report)
|
|
750
|
+
stub_const("SharedKeyConversion", conversion)
|
|
751
|
+
stub_const("SharedKeyHolder", holder)
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
it "selects the later member whose fields cover every key" do
|
|
755
|
+
parsed = holder.from_json('{"result":{"source":"a.emf","target":"svg"}}')
|
|
756
|
+
|
|
757
|
+
expect(parsed.result).to be_a(conversion)
|
|
758
|
+
expect(parsed.result.target).to eq("svg")
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
it "keeps the earlier member when it covers every key" do
|
|
762
|
+
parsed = holder.from_json('{"result":{"source":"a.emf","issues":["m"]}}')
|
|
763
|
+
|
|
764
|
+
expect(parsed.result).to be_a(report)
|
|
765
|
+
expect(parsed.result.issues).to eq(["m"])
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
it "selects the member mapping the most keys when an extension key is present" do
|
|
769
|
+
parsed = holder.from_json(
|
|
770
|
+
'{"result":{"source":"a.emf","target":"svg","x-ext":1}}',
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
expect(parsed.result).to be_a(conversion)
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
it "keeps declared order when members map equally many keys" do
|
|
777
|
+
parsed = holder.from_json('{"result":{"source":"a.emf","x-ext":1}}')
|
|
778
|
+
|
|
779
|
+
expect(parsed.result).to be_a(report)
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
713
783
|
describe "value transformers (cast idempotence)" do
|
|
714
784
|
it "keeps a model member built by transform: before the cast runs" do
|
|
715
785
|
reading = UnionAttributeSpec::TransformedReading.from_yaml(<<~YAML)
|