lutaml-model 0.8.131 → 0.8.133
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/lib/lutaml/key_value/transform.rb +23 -6
- data/lib/lutaml/key_value/transformation/value_serializer.rb +14 -2
- data/lib/lutaml/key_value/transformation.rb +13 -3
- data/lib/lutaml/model/transform.rb +78 -25
- data/lib/lutaml/model/type/hash.rb +20 -5
- data/lib/lutaml/model/type_context.rb +9 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/xml/serialization/instance_methods.rb +6 -1
- data/spec/lutaml/model/mixed_content_spec.rb +32 -0
- data/spec/lutaml/model/type/hash_spec.rb +39 -19
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ea94bb734406bacfb047edb9c580a3f224f19c497b4f506440da60d381cd1bf
|
|
4
|
+
data.tar.gz: 96f7c71f51a0ec8b863eec77b894ec611ff3a4e8b59354ec9b477e6470258852
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e61d4a02ebb21a794de64a0301a3489b045b8562ce8e1a8f3522b227bea1632882e7e09d1ce6fe18352d41c8fe62d2a5d9652b315dda76a220cbd30fe15f3cf
|
|
7
|
+
data.tar.gz: 60c206b080775425586980a1f3b484db2280be9e9b73bc365237210f64c90fc3faa80f1f9e9a42e94259a9a83ba1edab1ceecc25bb804147ecd3e31ead97c15d
|
|
@@ -346,15 +346,21 @@ child_mappings)
|
|
|
346
346
|
# path. Returns nil (caller falls back) on any shape the plan
|
|
347
347
|
# does not cover, e.g. a non-Hash item where a model was expected.
|
|
348
348
|
def kv_group_build(rows, doc, format, register, options)
|
|
349
|
-
|
|
349
|
+
# Positional values, not per-row [name, value] pairs: rows are a
|
|
350
|
+
# fixed per-class list, so the writer to apply lives at the row's
|
|
351
|
+
# index and the pair arrays were the lane's biggest allocation
|
|
352
|
+
# (~6 arrays per node on hydrate-heavy corpora).
|
|
353
|
+
values = Array.new(rows.size, MISSING)
|
|
350
354
|
children = []
|
|
351
355
|
# Lazy: the present-key hot path never pushes, and this runs
|
|
352
356
|
# once per hydrated model instance.
|
|
353
357
|
absent = nil
|
|
354
358
|
unmapped = nil
|
|
359
|
+
i = 0
|
|
355
360
|
rows.each do |rule, attr, kind, type, child_rows, child_register|
|
|
356
361
|
if kind == :unmapped
|
|
357
362
|
unmapped = [type, child_rows]
|
|
363
|
+
i += 1
|
|
358
364
|
next
|
|
359
365
|
end
|
|
360
366
|
|
|
@@ -364,6 +370,7 @@ child_mappings)
|
|
|
364
370
|
v = Lutaml::Model::Utils.fetch_str_or_sym(doc, rule.name, MISSING)
|
|
365
371
|
if v.equal?(MISSING)
|
|
366
372
|
(absent ||= []) << rule
|
|
373
|
+
i += 1
|
|
367
374
|
next
|
|
368
375
|
end
|
|
369
376
|
|
|
@@ -389,13 +396,15 @@ child_mappings)
|
|
|
389
396
|
built << self.class.data_to_model(type.attr_type,
|
|
390
397
|
child_hash, format, { mappings: type.child_rules })
|
|
391
398
|
end
|
|
392
|
-
|
|
399
|
+
values[i] = built
|
|
393
400
|
children.concat(built)
|
|
401
|
+
i += 1
|
|
394
402
|
next
|
|
395
403
|
end
|
|
396
404
|
|
|
397
405
|
if kind == :scalar
|
|
398
|
-
|
|
406
|
+
values[i] = v
|
|
407
|
+
i += 1
|
|
399
408
|
next
|
|
400
409
|
end
|
|
401
410
|
|
|
@@ -417,7 +426,7 @@ child_hash, format, { mappings: type.child_rules })
|
|
|
417
426
|
|
|
418
427
|
built << child
|
|
419
428
|
end
|
|
420
|
-
|
|
429
|
+
values[i] = built
|
|
421
430
|
children.concat(built)
|
|
422
431
|
else
|
|
423
432
|
return nil unless v.is_a?(::Hash)
|
|
@@ -427,12 +436,20 @@ child_hash, format, { mappings: type.child_rules })
|
|
|
427
436
|
options)
|
|
428
437
|
return nil if child.nil?
|
|
429
438
|
|
|
430
|
-
|
|
439
|
+
values[i] = child
|
|
431
440
|
children << child
|
|
432
441
|
end
|
|
442
|
+
i += 1
|
|
433
443
|
end
|
|
434
444
|
instance = model_class.allocate_for_walk(register)
|
|
435
|
-
|
|
445
|
+
i = 0
|
|
446
|
+
rows.each do |rule, _attr, kind, *_rest|
|
|
447
|
+
v = values[i]
|
|
448
|
+
i += 1
|
|
449
|
+
next if v.equal?(MISSING) || kind == :unmapped
|
|
450
|
+
|
|
451
|
+
instance.public_send(rule.kv_setter, v)
|
|
452
|
+
end
|
|
436
453
|
absent&.each do |rule|
|
|
437
454
|
process_mapping_rule(doc, instance, format, rule, options, nil)
|
|
438
455
|
end
|
|
@@ -43,6 +43,9 @@ model_class: nil)
|
|
|
43
43
|
@register_id = register_id
|
|
44
44
|
@transformation_factory = transformation_factory
|
|
45
45
|
@model_class = model_class
|
|
46
|
+
# Pre-declared: the serializer may be frozen after setup, and
|
|
47
|
+
# the type-transformation memo mutates this hash in place.
|
|
48
|
+
@lutaml_type_transformations = {}
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
# Serialize a value for an item (handles nested models and primitives).
|
|
@@ -117,10 +120,10 @@ model_class: nil)
|
|
|
117
120
|
|
|
118
121
|
# Get or create child transformation
|
|
119
122
|
child_transformation = if uses_polymorphism
|
|
120
|
-
|
|
123
|
+
transformation_for_type(actual_type)
|
|
121
124
|
else
|
|
122
125
|
rule.child_transformation ||
|
|
123
|
-
|
|
126
|
+
transformation_for_type(rule.attribute_type)
|
|
124
127
|
end
|
|
125
128
|
|
|
126
129
|
if child_transformation
|
|
@@ -296,6 +299,15 @@ model_class: nil)
|
|
|
296
299
|
transformation_factory.call(type_class)
|
|
297
300
|
end
|
|
298
301
|
|
|
302
|
+
# Memoized on this object: the factory binds this object's
|
|
303
|
+
# mapping/format/register, so the memo is register-safe, and
|
|
304
|
+
# building one per serialized node is a Transformation per
|
|
305
|
+
# nested model per instance.
|
|
306
|
+
def transformation_for_type(type_class)
|
|
307
|
+
(@lutaml_type_transformations ||= {})[type_class] ||=
|
|
308
|
+
create_transformation(type_class)
|
|
309
|
+
end
|
|
310
|
+
|
|
299
311
|
# Transform a nested model using the child transformation.
|
|
300
312
|
#
|
|
301
313
|
# @param value [Object] The model instance
|
|
@@ -63,6 +63,9 @@ module Lutaml
|
|
|
63
63
|
|
|
64
64
|
# Set up serializers before calling super
|
|
65
65
|
# The super will call compile_rules and freeze
|
|
66
|
+
# Pre-declared: the transformation is frozen after compile, and
|
|
67
|
+
# the type-transformation memo mutates this hash in place.
|
|
68
|
+
@lutaml_type_transformations = {}
|
|
66
69
|
@rule_compiler = RuleCompiler.new(
|
|
67
70
|
model_class: model_class,
|
|
68
71
|
register_id: register_id,
|
|
@@ -1034,6 +1037,15 @@ child_mappings, options)
|
|
|
1034
1037
|
# @param value [Object] The value
|
|
1035
1038
|
# @param options [Hash] Options
|
|
1036
1039
|
# @return [Object] The value (could be Hash, primitive, or KeyValueElement)
|
|
1040
|
+
# Child transformations memoized on this transformation instance:
|
|
1041
|
+
# the factory binds this instance's mapping/format/register, so
|
|
1042
|
+
# the memo is register-safe, and building one per serialized node
|
|
1043
|
+
# is a Transformation per nested model per instance.
|
|
1044
|
+
def transformation_for_type(type_class)
|
|
1045
|
+
(@lutaml_type_transformations ||= {})[type_class] ||=
|
|
1046
|
+
build_child_transformation(type_class)
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1037
1049
|
def create_value_for_item(rule, value, options)
|
|
1038
1050
|
return nil if value.nil?
|
|
1039
1051
|
|
|
@@ -1098,9 +1110,7 @@ child_mappings, options)
|
|
|
1098
1110
|
if !child_transformation || uses_polymorphism
|
|
1099
1111
|
# For polymorphic types, use the actual runtime type's mappings
|
|
1100
1112
|
type_for_mapping = uses_polymorphism ? actual_type : rule.attribute_type
|
|
1101
|
-
|
|
1102
|
-
child_transformation = self.class.new(type_for_mapping, mapping,
|
|
1103
|
-
format, register)
|
|
1113
|
+
child_transformation = transformation_for_type(type_for_mapping)
|
|
1104
1114
|
end
|
|
1105
1115
|
|
|
1106
1116
|
if child_transformation
|
|
@@ -3,12 +3,81 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Model
|
|
5
5
|
class Transform
|
|
6
|
-
@transform_cache = {}
|
|
7
|
-
|
|
8
6
|
# Maximum number of cached Transform instances before eviction.
|
|
9
7
|
# Covers most OOXML/ISO schemas with ~1000+ classes and multiple registers.
|
|
10
8
|
MAX_CACHE_SIZE = 2048
|
|
11
9
|
|
|
10
|
+
# The (lane, context, register) memo lives in a shared constant,
|
|
11
|
+
# not in class-level ivars: KeyValue::Transform subclasses this
|
|
12
|
+
# class, and inherited class methods run with the subclass as
|
|
13
|
+
# self — ivar state forked per subclass, and clear_cache! on the
|
|
14
|
+
# base class never reached the subclass's copy (stale compiled
|
|
15
|
+
# plans across register resets). The lane is part of the key:
|
|
16
|
+
# each transform class compiles format-specific rules, so a KV
|
|
17
|
+
# transform and the base one must never answer for each other.
|
|
18
|
+
class TransformCache
|
|
19
|
+
attr_reader :size
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@entries = {}
|
|
23
|
+
@size = 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Nested memo, not an array key: the hot path is one lookup per
|
|
27
|
+
# parsed node and the key array was that lookup's only
|
|
28
|
+
# allocation (60k arrays on a 6k-node corpus).
|
|
29
|
+
def fetch_or_store(lane, context, register)
|
|
30
|
+
by_context = @entries[lane]
|
|
31
|
+
by_register = by_context&.[](context)
|
|
32
|
+
return by_register[register] if by_register&.key?(register)
|
|
33
|
+
|
|
34
|
+
by_register = ((@entries[lane] ||= {})[context] ||= {})
|
|
35
|
+
transform = yield
|
|
36
|
+
by_register[register] = transform
|
|
37
|
+
@size += 1
|
|
38
|
+
transform
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def evict_half_if_full
|
|
42
|
+
return unless @size >= MAX_CACHE_SIZE
|
|
43
|
+
|
|
44
|
+
drop = @size / 2
|
|
45
|
+
@entries.each do |lane, by_context|
|
|
46
|
+
by_context.each do |ctx, by_register|
|
|
47
|
+
break if drop <= 0
|
|
48
|
+
|
|
49
|
+
drop -= by_register.size
|
|
50
|
+
by_context.delete(ctx)
|
|
51
|
+
end
|
|
52
|
+
@entries.delete(lane) if by_context.empty?
|
|
53
|
+
break if drop <= 0
|
|
54
|
+
end
|
|
55
|
+
@size = @entries.sum { |_, by_context| by_context.sum { |_, br| br.size } }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def invalidate(context, register = nil)
|
|
59
|
+
@entries.each_value do |by_context|
|
|
60
|
+
if register
|
|
61
|
+
by_register = by_context[context]
|
|
62
|
+
next unless by_register&.delete(register)
|
|
63
|
+
|
|
64
|
+
@size -= 1
|
|
65
|
+
by_context.delete(context) if by_register.empty?
|
|
66
|
+
else
|
|
67
|
+
removed = by_context.delete(context)
|
|
68
|
+
@size -= removed.size if removed
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def clear
|
|
74
|
+
@entries.clear
|
|
75
|
+
@size = 0
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
TRANSFORM_CACHE = TransformCache.new
|
|
80
|
+
|
|
12
81
|
def self.data_to_model(context, data, format, options = {})
|
|
13
82
|
register = options[:register] || Lutaml::Model::Config.default_register
|
|
14
83
|
transform = cached_transform(context, register, options[:mappings])
|
|
@@ -28,13 +97,10 @@ module Lutaml
|
|
|
28
97
|
def self.cached_transform(context, register, mappings = nil)
|
|
29
98
|
return override_transform(context, register, mappings) if mappings
|
|
30
99
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
evict_if_needed if @transform_cache.size >= MAX_CACHE_SIZE
|
|
37
|
-
@transform_cache[cache_key] = new(context, register)
|
|
100
|
+
TRANSFORM_CACHE.evict_half_if_full
|
|
101
|
+
TRANSFORM_CACHE.fetch_or_store(self, context, register) do
|
|
102
|
+
new(context, register)
|
|
103
|
+
end
|
|
38
104
|
end
|
|
39
105
|
|
|
40
106
|
def self.override_transform(context, register, mappings)
|
|
@@ -54,30 +120,17 @@ module Lutaml
|
|
|
54
120
|
private_class_method :evict_override_if_needed
|
|
55
121
|
|
|
56
122
|
def self.clear_cache!
|
|
57
|
-
|
|
123
|
+
TRANSFORM_CACHE.clear
|
|
58
124
|
@override_transform_cache&.clear
|
|
59
125
|
end
|
|
60
126
|
|
|
61
127
|
def self.cache_size
|
|
62
|
-
|
|
128
|
+
TRANSFORM_CACHE.size
|
|
63
129
|
end
|
|
64
130
|
|
|
65
131
|
def self.invalidate_for(context, register = nil)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if register
|
|
69
|
-
@transform_cache.delete([context, register])
|
|
70
|
-
else
|
|
71
|
-
@transform_cache.reject! { |(ctx, _reg)| ctx == context }
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def self.evict_if_needed
|
|
76
|
-
# Evict oldest half of entries when cache is full
|
|
77
|
-
keys_to_remove = @transform_cache.keys.first(@transform_cache.size / 2)
|
|
78
|
-
keys_to_remove.each { |k| @transform_cache.delete(k) }
|
|
132
|
+
TRANSFORM_CACHE.invalidate(context, register)
|
|
79
133
|
end
|
|
80
|
-
private_class_method :evict_if_needed
|
|
81
134
|
|
|
82
135
|
attr_reader :context, :lutaml_register
|
|
83
136
|
|
|
@@ -18,22 +18,37 @@ module Lutaml
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def self.normalize_hash(hash)
|
|
21
|
-
|
|
21
|
+
xml = hash.is_a?(Lutaml::Model::MappingHash)
|
|
22
|
+
return hash["text"] if text_node?(hash)
|
|
22
23
|
|
|
23
|
-
hash = hash.to_h if
|
|
24
|
+
hash = hash.to_h if xml
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
normalized = hash.transform_values do |value|
|
|
26
27
|
normalize_value(value)
|
|
27
28
|
end
|
|
29
|
+
xml ? (normalized["elements"] || normalized) : normalized
|
|
30
|
+
end
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
# {"text" => string} is the XML text-node convention and
|
|
33
|
+
# collapses to the string. Any other "text" key — structured
|
|
34
|
+
# values, or appearing alongside other keys in non-XML data —
|
|
35
|
+
# is ordinary data and must round-trip verbatim
|
|
36
|
+
# (lutaml/lutaml-model#1013).
|
|
37
|
+
def self.text_node?(hash)
|
|
38
|
+
# ::String — unqualified String resolves to Type::String here
|
|
39
|
+
hash.is_a?(::Hash) && hash.keys == ["text"] &&
|
|
40
|
+
hash["text"].is_a?(::String)
|
|
30
41
|
end
|
|
31
42
|
|
|
32
43
|
def self.normalize_value(value)
|
|
33
44
|
return value unless value.is_a?(::Hash)
|
|
34
45
|
|
|
35
46
|
nested = normalize_hash(value)
|
|
36
|
-
|
|
47
|
+
if value.is_a?(Lutaml::Model::MappingHash) && nested.is_a?(::Hash)
|
|
48
|
+
nested.except("text")
|
|
49
|
+
else
|
|
50
|
+
nested
|
|
51
|
+
end
|
|
37
52
|
end
|
|
38
53
|
|
|
39
54
|
def self.serialize(value)
|
|
@@ -38,6 +38,8 @@ module Lutaml
|
|
|
38
38
|
# # Has :custom, falls back to default for built-in types
|
|
39
39
|
#
|
|
40
40
|
class TypeContext
|
|
41
|
+
EMPTY_SUBSTITUTIONS = [].freeze
|
|
42
|
+
|
|
41
43
|
# @return [Symbol] The context identifier
|
|
42
44
|
attr_reader :id
|
|
43
45
|
|
|
@@ -204,6 +206,13 @@ module Lutaml
|
|
|
204
206
|
# @param from_type [Class, Symbol, String] The type to find substitutions for
|
|
205
207
|
# @return [Array<TypeSubstitution>] Matching substitutions
|
|
206
208
|
def substitution_for(from_type)
|
|
209
|
+
# The no-substitution context is the common case, and this
|
|
210
|
+
# lookup runs per nested model per serialized instance — the
|
|
211
|
+
# select's result array was the allocation (#KV-to profile
|
|
212
|
+
# 2026-10-26). The frozen shared array answers readers only;
|
|
213
|
+
# every consumer here selects/any?/eaches it.
|
|
214
|
+
return EMPTY_SUBSTITUTIONS if substitutions.empty?
|
|
215
|
+
|
|
207
216
|
from_type_class = from_type.is_a?(Class) ? from_type : nil
|
|
208
217
|
|
|
209
218
|
substitutions.select do |sub|
|
data/lib/lutaml/model/version.rb
CHANGED
|
@@ -178,8 +178,13 @@ module Lutaml
|
|
|
178
178
|
elsif val.is_a?(Lutaml::Model::Serializable)
|
|
179
179
|
collection_indices[attr_name] += 1
|
|
180
180
|
collection_indices[attr_name] == 1 ? val : nil
|
|
181
|
+
else
|
|
182
|
+
# Scalar mapped elements (e.g. a string-valued
|
|
183
|
+
# <main>) are part of the mixed content too.
|
|
184
|
+
collection_indices[attr_name] += 1
|
|
185
|
+
val
|
|
181
186
|
end
|
|
182
|
-
yield(obj)
|
|
187
|
+
yield(obj) unless obj.nil? || (obj.is_a?(String) && obj.empty?)
|
|
183
188
|
end
|
|
184
189
|
end
|
|
185
190
|
|
|
@@ -1449,4 +1449,36 @@ RSpec.describe "MixedContent" do
|
|
|
1449
1449
|
described_class
|
|
1450
1450
|
end
|
|
1451
1451
|
end
|
|
1452
|
+
|
|
1453
|
+
describe "each_mixed_content with string-valued mapped elements" do
|
|
1454
|
+
it "yields scalar mapped elements, not only Arrays and Serializable children" do
|
|
1455
|
+
xml = "<title-wrap><main>Test Document</main></title-wrap>"
|
|
1456
|
+
model = MixedContentSpec::TitleWrap.from_xml(xml)
|
|
1457
|
+
|
|
1458
|
+
expect(model.main).to eq("Test Document")
|
|
1459
|
+
|
|
1460
|
+
yielded = model.each_mixed_content.to_a
|
|
1461
|
+
expect(yielded).to include("Test Document")
|
|
1462
|
+
end
|
|
1463
|
+
|
|
1464
|
+
it "skips empty scalar values" do
|
|
1465
|
+
xml = "<title-wrap><main></main></title-wrap>"
|
|
1466
|
+
model = MixedContentSpec::TitleWrap.from_xml(xml)
|
|
1467
|
+
|
|
1468
|
+
yielded = model.each_mixed_content.to_a
|
|
1469
|
+
expect(yielded).not_to include("")
|
|
1470
|
+
end
|
|
1471
|
+
end
|
|
1472
|
+
end
|
|
1473
|
+
|
|
1474
|
+
module MixedContentSpec
|
|
1475
|
+
class TitleWrap < Lutaml::Model::Serializable
|
|
1476
|
+
attribute :main, :string
|
|
1477
|
+
|
|
1478
|
+
xml do
|
|
1479
|
+
element "title-wrap"
|
|
1480
|
+
mixed_content
|
|
1481
|
+
map_element "main", to: :main
|
|
1482
|
+
end
|
|
1483
|
+
end
|
|
1452
1484
|
end
|
|
@@ -6,11 +6,30 @@ RSpec.describe Lutaml::Model::Type::Hash do
|
|
|
6
6
|
expect(described_class.cast(nil)).to be_nil
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
it "returns text content from
|
|
10
|
-
hash =
|
|
9
|
+
it "returns text content from a MappingHash with only text key" do
|
|
10
|
+
hash = Lutaml::Model::MappingHash.new
|
|
11
|
+
hash["text"] = "content"
|
|
11
12
|
expect(described_class.cast(hash)).to eq "content"
|
|
12
13
|
end
|
|
13
14
|
|
|
15
|
+
# lutaml/lutaml-model#1013: a "text" key holding structured data
|
|
16
|
+
# (or appearing alongside other keys in non-XML data) is ordinary
|
|
17
|
+
# data and must round-trip verbatim
|
|
18
|
+
it "preserves a structured text key from a plain hash" do
|
|
19
|
+
hash = { "text" => { "type" => "string", "description" => "y" } }
|
|
20
|
+
expect(described_class.cast(hash)).to eq hash
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "preserves text alongside other keys in a plain hash" do
|
|
24
|
+
hash = { "text" => "content", "other" => "value" }
|
|
25
|
+
expect(described_class.cast(hash)).to eq hash
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "preserves an elements key from a plain hash" do
|
|
29
|
+
hash = { "elements" => [{ "a" => 1 }] }
|
|
30
|
+
expect(described_class.cast(hash)).to eq hash
|
|
31
|
+
end
|
|
32
|
+
|
|
14
33
|
it "normalizes MappingHash to regular Hash" do
|
|
15
34
|
mapping_hash = Lutaml::Model::MappingHash.new
|
|
16
35
|
mapping_hash["key"] = "value"
|
|
@@ -18,28 +37,29 @@ RSpec.describe Lutaml::Model::Type::Hash do
|
|
|
18
37
|
end
|
|
19
38
|
|
|
20
39
|
context "with nested hashes" do
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
let(:expected_hash) do
|
|
35
|
-
{
|
|
40
|
+
it "filters out text keys from nested MappingHashes" do
|
|
41
|
+
child1 = Lutaml::Model::MappingHash.new
|
|
42
|
+
child1["text"] = "content1"
|
|
43
|
+
child1["other"] = "value1"
|
|
44
|
+
child2 = Lutaml::Model::MappingHash.new
|
|
45
|
+
child2["text"] = "content2"
|
|
46
|
+
child2["other"] = "value2"
|
|
47
|
+
input = Lutaml::Model::MappingHash.new
|
|
48
|
+
input["key1"] = child1
|
|
49
|
+
input["key2"] = child2
|
|
50
|
+
expected = {
|
|
36
51
|
"key1" => { "other" => "value1" },
|
|
37
52
|
"key2" => { "other" => "value2" },
|
|
38
53
|
}
|
|
54
|
+
expect(described_class.cast(input)).to eq(expected)
|
|
39
55
|
end
|
|
40
56
|
|
|
41
|
-
it "
|
|
42
|
-
|
|
57
|
+
it "preserves text keys in nested plain hashes" do
|
|
58
|
+
input = {
|
|
59
|
+
"key1" => { "text" => "content1", "other" => "value1" },
|
|
60
|
+
"key2" => { "text" => "content2", "other" => "value2" },
|
|
61
|
+
}
|
|
62
|
+
expect(described_class.cast(input)).to eq(input)
|
|
43
63
|
end
|
|
44
64
|
end
|
|
45
65
|
|
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.133
|
|
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-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|