lutaml-model 0.8.68 → 0.8.70
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 +9 -8
- data/lib/lutaml/key_value/transformation/value_serializer.rb +26 -1
- data/lib/lutaml/model/render_policy.rb +26 -8
- data/lib/lutaml/model/serialize.rb +14 -6
- data/lib/lutaml/model/type/value.rb +9 -7
- data/lib/lutaml/model/utils.rb +4 -0
- data/lib/lutaml/model/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e3d309f15ab3866bd793f01385378fc7294127de341e8914a27838bfd6b956c
|
|
4
|
+
data.tar.gz: ac90f885dd393c008adb09187586ec2698d9e1f40a8f3fd9bb579ad12e776fe3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5911aff11ba087c00b6854ee18106df409516688b093e28149f6dbff8c31ea00d15cc82ff67cfe85fbb0c61d34d0d14849d43d1c4e2a114be41e244d293f5b1c
|
|
7
|
+
data.tar.gz: 91b861ce8a517ee382284684f2516493d96f5aef6a1588e9a01eefb1108e9fb86a37a91dbbda39cf02a0b38f32ce8f9603dc5498f2e7b4fec647d21bfaca6587
|
|
@@ -177,18 +177,21 @@ module Lutaml
|
|
|
177
177
|
.substitution_for(type).any?
|
|
178
178
|
|
|
179
179
|
if type.is_a?(Class) && type.include?(Lutaml::Model::Serialize)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
child_register = Lutaml::Model::Register
|
|
181
|
+
.resolve_for_child(type, register)
|
|
182
|
+
child_rows = kv_group_plan(type, format, child_register)
|
|
183
183
|
return false unless child_rows
|
|
184
184
|
|
|
185
185
|
rows ||= []
|
|
186
|
-
|
|
186
|
+
# child_register rides the row: the plan was BUILT under it,
|
|
187
|
+
# and rebuilding the resolution per model row per document
|
|
188
|
+
# cost a Concurrent::Map hop each time (TODO.perf 14).
|
|
189
|
+
rows << [rule, attr, :model, type, child_rows, child_register]
|
|
187
190
|
elsif type.is_a?(Class) && type < Lutaml::Model::Type::Value &&
|
|
188
191
|
!attr.value_policy.whole_value?(type) &&
|
|
189
192
|
!Lutaml::Model::Attribute.custom_from_probe?(type)
|
|
190
193
|
rows ||= []
|
|
191
|
-
rows << [rule, attr, :scalar, nil, nil]
|
|
194
|
+
rows << [rule, attr, :scalar, nil, nil, nil]
|
|
192
195
|
else
|
|
193
196
|
return false
|
|
194
197
|
end
|
|
@@ -208,7 +211,7 @@ module Lutaml
|
|
|
208
211
|
setters = []
|
|
209
212
|
children = []
|
|
210
213
|
absent = []
|
|
211
|
-
rows.each do |rule, attr, kind, type, child_rows|
|
|
214
|
+
rows.each do |rule, attr, kind, type, child_rows, child_register|
|
|
212
215
|
# One hash pass per row: fetch with a sentinel distinguishes
|
|
213
216
|
# absent keys from present-nil values (both read as nil), so
|
|
214
217
|
# the separate string_or_symbol_key? probe goes away.
|
|
@@ -223,8 +226,6 @@ module Lutaml
|
|
|
223
226
|
next
|
|
224
227
|
end
|
|
225
228
|
|
|
226
|
-
child_register = Lutaml::Model::Register.resolve_for_child(type,
|
|
227
|
-
register)
|
|
228
229
|
if attr.collection?
|
|
229
230
|
# A present-but-nil collection reaches the per-rule walk,
|
|
230
231
|
# which owns the sentinel interplay for that edge
|
|
@@ -151,8 +151,19 @@ model_class: nil)
|
|
|
151
151
|
return value.public_send(:"to_#{format}")
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
-
# Wrap value in type and call to_#{format}
|
|
155
154
|
if rule.attribute_type.is_a?(Class) && rule.attribute_type < Lutaml::Model::Type::Value
|
|
155
|
+
plan = serialize_plan(rule)
|
|
156
|
+
# Identity only under identity_fast_type's exact condition
|
|
157
|
+
# (no custom to AND no custom from): a custom from means the
|
|
158
|
+
# wrapper constructor normalizes the value, so the raw value
|
|
159
|
+
# must still pass through it.
|
|
160
|
+
if plan[:identity_type] && value.instance_of?(plan[:identity_type])
|
|
161
|
+
return value
|
|
162
|
+
end
|
|
163
|
+
if (to_fn = plan[:to_fn])
|
|
164
|
+
return to_fn.call(rule.attribute_type.new(value))
|
|
165
|
+
end
|
|
166
|
+
|
|
156
167
|
wrapped_value = rule.attribute_type.new(value)
|
|
157
168
|
wrapped_value.public_send(:"to_#{format}")
|
|
158
169
|
else
|
|
@@ -187,9 +198,23 @@ model_class: nil)
|
|
|
187
198
|
reference: reference_type?(rule),
|
|
188
199
|
union: ::Lutaml::Model::Type::Union.rule?(rule),
|
|
189
200
|
nested: nested_model?(rule),
|
|
201
|
+
# The custom to_<format> serializer, resolved once per rule:
|
|
202
|
+
# the default path is pure identity (to_<format> returns the
|
|
203
|
+
# wrapped .value), so values without a custom serializer skip
|
|
204
|
+
# the wrap-allocate-cast ceremony entirely, and custom ones
|
|
205
|
+
# skip the per-value registry lookup.
|
|
206
|
+
to_fn: custom_to_fn(rule),
|
|
190
207
|
}
|
|
191
208
|
end
|
|
192
209
|
|
|
210
|
+
def custom_to_fn(rule)
|
|
211
|
+
type = rule.attribute_type
|
|
212
|
+
return nil unless type.is_a?(::Class) && type < ::Lutaml::Model::Type::Value
|
|
213
|
+
|
|
214
|
+
::Lutaml::Model::Type::Value
|
|
215
|
+
.format_type_serializer_for(@format, type)&.fetch(:to, nil)
|
|
216
|
+
end
|
|
217
|
+
|
|
193
218
|
# Builtin scalars whose type registers no custom to_<format>
|
|
194
219
|
# serializer serialize as the value itself — Type::Value#to_<format>'s
|
|
195
220
|
# default — without the wrap-allocate-cast ceremony per field.
|
|
@@ -12,6 +12,14 @@ module Lutaml
|
|
|
12
12
|
# options are normalized into value_map entries at MappingRule construction
|
|
13
13
|
# time, so downstream code never needs to re-interpret them.
|
|
14
14
|
module RenderPolicy
|
|
15
|
+
# Isolated holder: host transformations freeze after compile; an
|
|
16
|
+
# ivar cache on them raises FrozenError (learned the hard way).
|
|
17
|
+
SKIP_PLANS = ::Class.new do
|
|
18
|
+
def self.cache
|
|
19
|
+
@cache ||= {}.compare_by_identity
|
|
20
|
+
end
|
|
21
|
+
end.cache
|
|
22
|
+
|
|
15
23
|
def self.derived_attribute_for?(context_obj, attr_name)
|
|
16
24
|
return false unless context_obj.is_a?(Lutaml::Model::Serialize) &&
|
|
17
25
|
context_obj.class.is_a?(Class) &&
|
|
@@ -52,16 +60,26 @@ module Lutaml
|
|
|
52
60
|
# @param context_obj [Object] The context object (model_instance or delegate_obj)
|
|
53
61
|
# @return [Boolean] true if should skip
|
|
54
62
|
def check_skip_logic?(value, rule, context_obj)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
# Rule-constant parts (attribute name, :to value map) are frozen
|
|
64
|
+
# per rule; recomputing them per value showed in the to_yaml
|
|
65
|
+
# wall profile. The host transformation freezes itself after
|
|
66
|
+
# compile, so the cache lives in an isolated holder (TypeProbeCache
|
|
67
|
+
# precedent): host object -> identity-keyed rule plans.
|
|
68
|
+
per_host = (SKIP_PLANS[self] ||= {}.compare_by_identity)
|
|
69
|
+
skip_plan = (per_host[rule] ||= [extract_attribute_name(rule), to_value_map(rule)])
|
|
70
|
+
attr_name, to_map = skip_plan
|
|
71
|
+
|
|
72
|
+
# Plain conditionals, not `when ->(v)` literals: each lambda
|
|
73
|
+
# literal in a case/when allocates on every call — two per
|
|
74
|
+
# value here, 16% of to_yaml's allocations in the object-mode
|
|
75
|
+
# profile.
|
|
76
|
+
if value.nil?
|
|
61
77
|
return to_map[:nil] == :omitted
|
|
62
|
-
|
|
78
|
+
end
|
|
79
|
+
if Lutaml::Model::Utils.empty?(value)
|
|
63
80
|
return to_map[:empty] == :omitted
|
|
64
|
-
|
|
81
|
+
end
|
|
82
|
+
if Lutaml::Model::Utils.uninitialized?(value)
|
|
65
83
|
return to_map[:omitted] == :omitted || to_map[:omitted].nil?
|
|
66
84
|
end
|
|
67
85
|
|
|
@@ -197,8 +197,11 @@ module Lutaml
|
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
def attr_value(attrs, name, attribute)
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
# The default is computed only on absence: it used to be
|
|
201
|
+
# evaluated eagerly as the fetch fallback on EVERY value,
|
|
202
|
+
# paying proc/static default dispatch per present attribute.
|
|
203
|
+
value = Utils.fetch_str_or_sym(attrs, name, Utils::MISSING)
|
|
204
|
+
value = attribute.default(lutaml_register, self) if value.equal?(Utils::MISSING)
|
|
202
205
|
attribute.cast_value(value, lutaml_register)
|
|
203
206
|
end
|
|
204
207
|
|
|
@@ -368,15 +371,20 @@ module Lutaml
|
|
|
368
371
|
value = self.class.apply_value_map(value, vmap, attr)
|
|
369
372
|
# Performance: Only call ensure_utf8 for string values
|
|
370
373
|
value = self.class.ensure_utf8(value) if value.is_a?(::String)
|
|
371
|
-
|
|
374
|
+
# Interpolating the writer symbol allocated a String per
|
|
375
|
+
# attribute per instance; the per-register writer table is
|
|
376
|
+
# interned once per class.
|
|
377
|
+
public_send(self.class.instantiate_writers(lutaml_register)[name], value)
|
|
372
378
|
using_default_for(name) if default
|
|
373
379
|
end
|
|
374
380
|
end
|
|
375
381
|
|
|
376
382
|
def determine_value(attrs, name, attr)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
383
|
+
# One hash pass: a sentinel fetch distinguishes absent keys
|
|
384
|
+
# from present-nil values, replacing two key probes plus the
|
|
385
|
+
# re-fetch inside attr_value (four hash ops per attribute).
|
|
386
|
+
value = Utils.fetch_str_or_sym(attrs, name, Utils::MISSING)
|
|
387
|
+
return attr.cast_value(value, lutaml_register) unless value.equal?(Utils::MISSING)
|
|
380
388
|
|
|
381
389
|
resolved = attr.default_value(lutaml_register, self)
|
|
382
390
|
return Lutaml::Model::UninitializedClass.instance if Lutaml::Model::Utils.uninitialized?(resolved)
|
|
@@ -67,12 +67,14 @@ from: nil)
|
|
|
67
67
|
# @param type_class [Class] the type class to look up
|
|
68
68
|
# @return [Hash, nil] { to: Proc, from: Proc } or nil
|
|
69
69
|
def format_type_serializer_for(format, type_class)
|
|
70
|
-
# Hot path:
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
# Hot path: memoized per format -> class with NO key
|
|
71
|
+
# allocation — the previous [format, class] array key
|
|
72
|
+
# allocated on every lookup (2k arrays per 200 serializes in
|
|
73
|
+
# the object-mode profile). Identity-keyed nested maps; the
|
|
74
|
+
# hierarchy walk below only runs on a miss, and format
|
|
75
|
+
# serializers register at load time, so a hit is stable.
|
|
76
|
+
per_format = (SERIALIZER_LOOKUP_CACHE[format] ||= {}.compare_by_identity)
|
|
77
|
+
return per_format[type_class] if per_format.key?(type_class)
|
|
76
78
|
|
|
77
79
|
klass = type_class
|
|
78
80
|
found = nil
|
|
@@ -85,7 +87,7 @@ from: nil)
|
|
|
85
87
|
|
|
86
88
|
klass = klass.superclass
|
|
87
89
|
end
|
|
88
|
-
|
|
90
|
+
per_format[type_class] = found
|
|
89
91
|
found
|
|
90
92
|
end
|
|
91
93
|
|
data/lib/lutaml/model/utils.rb
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Model
|
|
5
5
|
module Utils
|
|
6
|
+
# Fetch sentinel distinguishing absent keys from present-nil
|
|
7
|
+
# values; shared by the deserialization fast paths.
|
|
8
|
+
MISSING = Object.new.freeze
|
|
9
|
+
|
|
6
10
|
class << self
|
|
7
11
|
UNINITIALIZED = Lutaml::Model::UninitializedClass.instance
|
|
8
12
|
|
data/lib/lutaml/model/version.rb
CHANGED