julewire-core 1.0.0 → 1.0.1
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/CHANGELOG.md +11 -0
- data/docs/advanced-configuration.md +2 -1
- data/docs/context-and-propagation.md +6 -4
- data/docs/contracts.md +2 -1
- data/docs/extensions-and-api.md +9 -6
- data/docs/internals.md +3 -3
- data/docs/outputs-and-lifecycle.md +5 -2
- data/docs/records-and-data-policy.md +2 -2
- data/lib/julewire/core/cli/log_formats/record_decoder.rb +1 -1
- data/lib/julewire/core/context_store.rb +11 -6
- data/lib/julewire/core/destinations/destination.rb +3 -3
- data/lib/julewire/core/destinations/synchronized_output.rb +51 -15
- data/lib/julewire/core/diagnostics/doctor.rb +11 -11
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +3 -1
- data/lib/julewire/core/execution/handle.rb +2 -0
- data/lib/julewire/core/execution/lineage.rb +10 -6
- data/lib/julewire/core/execution/scope.rb +4 -4
- data/lib/julewire/core/execution/scope_fields.rb +2 -2
- data/lib/julewire/core/facade_methods.rb +2 -2
- data/lib/julewire/core/fields/field_set.rb +32 -6
- data/lib/julewire/core/fields/field_stack.rb +40 -14
- data/lib/julewire/core/fields/internal.rb +41 -13
- data/lib/julewire/core/fields/stack_set.rb +2 -2
- data/lib/julewire/core/integration/destination_health.rb +13 -2
- data/lib/julewire/core/propagation/carrier.rb +63 -9
- data/lib/julewire/core/propagation.rb +3 -2
- data/lib/julewire/core/records/draft.rb +12 -6
- data/lib/julewire/core/records/record.rb +43 -13
- data/lib/julewire/core/runtime.rb +30 -11
- data/lib/julewire/core/serialization/bounded_transform.rb +11 -0
- data/lib/julewire/core/serialization/bounded_traversal.rb +33 -27
- data/lib/julewire/core/serialization/serializer.rb +1 -1
- data/lib/julewire/core/serialization/truncation_metadata.rb +142 -0
- data/lib/julewire/core/serialization/value_copy.rb +292 -51
- data/lib/julewire/core/testing/contracts/integration.rb +1 -1
- data/lib/julewire/core/version.rb +1 -1
- metadata +2 -1
|
@@ -3,30 +3,206 @@
|
|
|
3
3
|
module Julewire
|
|
4
4
|
module Core
|
|
5
5
|
module Serialization
|
|
6
|
+
module ValueCopyTruncation
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def validate_optional_limit(value, name:)
|
|
10
|
+
return unless value
|
|
11
|
+
|
|
12
|
+
Validation.validate_integer_limit!(value, name: name)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def record_hash_truncation(fields, key, truncated)
|
|
16
|
+
return fields unless truncated
|
|
17
|
+
|
|
18
|
+
append_truncation_field(fields, key.to_s)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def finish_hash(result, fields)
|
|
22
|
+
add_truncation_metadata!(result, fields)
|
|
23
|
+
finish_container(result, fields)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def finish_array(result, fields)
|
|
27
|
+
if @track_truncation && fields
|
|
28
|
+
result << freeze_container({ Serializer::TRUNCATION_METADATA_KEY.to_sym => truncation_metadata(fields) })
|
|
29
|
+
end
|
|
30
|
+
finish_container(result, fields)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add_truncation_metadata!(result, fields)
|
|
34
|
+
return unless @track_truncation && fields
|
|
35
|
+
|
|
36
|
+
result[Serializer::TRUNCATION_METADATA_KEY.to_sym] = truncation_metadata(fields)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def finish_container(result, fields)
|
|
40
|
+
value = freeze_container(result)
|
|
41
|
+
fields ? mark_truncated(value) : clear_truncated(value)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def truncation_metadata(fields)
|
|
45
|
+
TruncationMetadata.build(
|
|
46
|
+
fields,
|
|
47
|
+
key_style: :symbol,
|
|
48
|
+
compact_limits: true,
|
|
49
|
+
freeze_values: @freeze_values,
|
|
50
|
+
max_array_items: @max_array_items,
|
|
51
|
+
max_depth: @max_depth,
|
|
52
|
+
max_hash_keys: @max_hash_keys,
|
|
53
|
+
max_string_bytes: @max_string_bytes
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def mark_truncated(value)
|
|
58
|
+
@last_truncated = true
|
|
59
|
+
value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def clear_truncated(value)
|
|
63
|
+
@last_truncated = false
|
|
64
|
+
value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def consume_truncated
|
|
68
|
+
truncated = @last_truncated
|
|
69
|
+
@last_truncated = false
|
|
70
|
+
truncated
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def append_truncation_field(fields, field)
|
|
74
|
+
TruncationMetadata.append_field(fields, field)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
private_constant :ValueCopyTruncation
|
|
78
|
+
|
|
79
|
+
module ValueCopyCache
|
|
80
|
+
POOL_KEY = :julewire_core_value_copy_pool
|
|
81
|
+
private_constant :POOL_KEY
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def cached_copier(compact_empty:, freeze_values:, max_array_items:, max_depth:, max_hash_keys:,
|
|
86
|
+
max_string_bytes:, preserve_truncation_metadata:, symbolize_keys:)
|
|
87
|
+
options = copier_options(
|
|
88
|
+
compact_empty: compact_empty,
|
|
89
|
+
freeze_values: freeze_values,
|
|
90
|
+
max_array_items: max_array_items,
|
|
91
|
+
max_depth: max_depth,
|
|
92
|
+
max_hash_keys: max_hash_keys,
|
|
93
|
+
max_string_bytes: max_string_bytes,
|
|
94
|
+
preserve_truncation_metadata: preserve_truncation_metadata,
|
|
95
|
+
symbolize_keys: symbolize_keys
|
|
96
|
+
)
|
|
97
|
+
return new(**options) unless cacheable_options?(options)
|
|
98
|
+
|
|
99
|
+
reusable_copier(options)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def reusable_copier(options)
|
|
103
|
+
# One copier per thread/options avoids per-record walker allocation.
|
|
104
|
+
pool = Thread.current.thread_variable_get(POOL_KEY)
|
|
105
|
+
unless pool
|
|
106
|
+
pool = {}
|
|
107
|
+
Thread.current.thread_variable_set(POOL_KEY, pool)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
bucket = cache_bucket(
|
|
111
|
+
pool,
|
|
112
|
+
compact_empty: options.fetch(:compact_empty),
|
|
113
|
+
freeze_values: options.fetch(:freeze_values),
|
|
114
|
+
max_array_items: options.fetch(:max_array_items),
|
|
115
|
+
max_depth: options.fetch(:max_depth),
|
|
116
|
+
max_hash_keys: options.fetch(:max_hash_keys),
|
|
117
|
+
preserve_truncation_metadata: options.fetch(:preserve_truncation_metadata),
|
|
118
|
+
symbolize_keys: options.fetch(:symbolize_keys)
|
|
119
|
+
)
|
|
120
|
+
bucket[options.fetch(:max_string_bytes)] ||= new(**options)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def copier_options(compact_empty:, freeze_values:, max_array_items:, max_depth:, max_hash_keys:,
|
|
124
|
+
max_string_bytes:, preserve_truncation_metadata:, symbolize_keys:)
|
|
125
|
+
{
|
|
126
|
+
compact_empty: compact_empty,
|
|
127
|
+
freeze_values: freeze_values,
|
|
128
|
+
max_array_items: max_array_items,
|
|
129
|
+
max_depth: max_depth,
|
|
130
|
+
max_hash_keys: max_hash_keys,
|
|
131
|
+
max_string_bytes: max_string_bytes,
|
|
132
|
+
preserve_truncation_metadata: preserve_truncation_metadata,
|
|
133
|
+
symbolize_keys: symbolize_keys
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def cache_bucket(pool, compact_empty:, freeze_values:, max_array_items:, max_depth:, max_hash_keys:,
|
|
138
|
+
preserve_truncation_metadata:, symbolize_keys:)
|
|
139
|
+
flags = cache_flags(
|
|
140
|
+
compact_empty: compact_empty,
|
|
141
|
+
freeze_values: freeze_values,
|
|
142
|
+
preserve_truncation_metadata: preserve_truncation_metadata,
|
|
143
|
+
symbolize_keys: symbolize_keys
|
|
144
|
+
)
|
|
145
|
+
by_depth = (pool[flags] ||= {})
|
|
146
|
+
by_array = (by_depth[max_depth] ||= {})
|
|
147
|
+
by_hash = (by_array[max_array_items] ||= {})
|
|
148
|
+
by_hash[max_hash_keys] ||= {}
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def cache_flags(compact_empty:, freeze_values:, preserve_truncation_metadata:, symbolize_keys:)
|
|
152
|
+
flags = 0
|
|
153
|
+
flags |= 1 if compact_empty
|
|
154
|
+
flags |= 2 if freeze_values
|
|
155
|
+
flags |= 4 if symbolize_keys
|
|
156
|
+
flags |= 8 if preserve_truncation_metadata
|
|
157
|
+
flags
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def cacheable_options?(options)
|
|
161
|
+
# Only default ingress bounds use the thread-local pool; custom limits instantiate ad hoc.
|
|
162
|
+
options.fetch(:max_depth) == Core::NORMALIZATION_MAX_DEPTH &&
|
|
163
|
+
[nil, Serializer::DEFAULT_MAX_ARRAY_ITEMS].include?(options.fetch(:max_array_items)) &&
|
|
164
|
+
[nil, Serializer::DEFAULT_MAX_HASH_KEYS].include?(options.fetch(:max_hash_keys)) &&
|
|
165
|
+
[nil, Serializer::DEFAULT_MAX_STRING_BYTES].include?(options.fetch(:max_string_bytes))
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
private_constant :ValueCopyCache
|
|
169
|
+
|
|
6
170
|
class ValueCopy
|
|
7
171
|
include ValueTraversal
|
|
172
|
+
include ValueCopyTruncation
|
|
8
173
|
|
|
9
174
|
CIRCULAR_REFERENCE = Core::CIRCULAR_REFERENCE
|
|
10
175
|
EMPTY_ARRAY = [].freeze
|
|
11
176
|
EMPTY_HASH = {}.freeze
|
|
12
|
-
|
|
13
|
-
private_constant :EMPTY_ARRAY, :EMPTY_HASH, :
|
|
177
|
+
RESERVED_KEYS = [Serializer::TRUNCATION_METADATA_KEY, Serializer::TRUNCATION_METADATA_KEY.to_sym].freeze
|
|
178
|
+
private_constant :EMPTY_ARRAY, :EMPTY_HASH, :RESERVED_KEYS
|
|
14
179
|
|
|
15
180
|
class << self
|
|
16
|
-
|
|
181
|
+
include ValueCopyCache
|
|
182
|
+
|
|
183
|
+
def call( # rubocop:disable Metrics/ParameterLists
|
|
17
184
|
value,
|
|
18
185
|
compact_empty: false,
|
|
19
186
|
freeze_values: false,
|
|
187
|
+
max_array_items: nil,
|
|
20
188
|
max_depth: Core::NORMALIZATION_MAX_DEPTH,
|
|
189
|
+
max_hash_keys: nil,
|
|
190
|
+
max_string_bytes: nil,
|
|
191
|
+
preserve_truncation_metadata: false,
|
|
21
192
|
symbolize_keys: false
|
|
22
193
|
)
|
|
23
|
-
|
|
194
|
+
needs_string_limit = value.is_a?(String) && max_string_bytes
|
|
195
|
+
return copy_leaf(value, freeze_values: freeze_values) unless container?(value) || needs_string_limit
|
|
24
196
|
|
|
25
197
|
copy_with(
|
|
26
198
|
cached_copier(
|
|
27
199
|
compact_empty: compact_empty,
|
|
28
200
|
freeze_values: freeze_values,
|
|
201
|
+
max_array_items: max_array_items,
|
|
29
202
|
max_depth: max_depth,
|
|
203
|
+
max_hash_keys: max_hash_keys,
|
|
204
|
+
max_string_bytes: max_string_bytes,
|
|
205
|
+
preserve_truncation_metadata: preserve_truncation_metadata,
|
|
30
206
|
symbolize_keys: symbolize_keys
|
|
31
207
|
),
|
|
32
208
|
value
|
|
@@ -41,44 +217,17 @@ module Julewire
|
|
|
41
217
|
|
|
42
218
|
def container?(value) = value.is_a?(Hash) || value.is_a?(Array)
|
|
43
219
|
|
|
44
|
-
def cached_copier(compact_empty:, freeze_values:, max_depth:, symbolize_keys:)
|
|
45
|
-
# One copier per thread/options avoids per-record walker allocation.
|
|
46
|
-
pool = Thread.current.thread_variable_get(POOL_KEY)
|
|
47
|
-
unless pool
|
|
48
|
-
pool = {}
|
|
49
|
-
Thread.current.thread_variable_set(POOL_KEY, pool)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
key = cache_key(
|
|
53
|
-
compact_empty: compact_empty,
|
|
54
|
-
freeze_values: freeze_values,
|
|
55
|
-
max_depth: max_depth,
|
|
56
|
-
symbolize_keys: symbolize_keys
|
|
57
|
-
)
|
|
58
|
-
pool[key] ||= new(
|
|
59
|
-
compact_empty: compact_empty,
|
|
60
|
-
freeze_values: freeze_values,
|
|
61
|
-
max_depth: max_depth,
|
|
62
|
-
symbolize_keys: symbolize_keys
|
|
63
|
-
)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def cache_key(compact_empty:, freeze_values:, max_depth:, symbolize_keys:)
|
|
67
|
-
depth_key = max_depth || -1
|
|
68
|
-
flags = 0
|
|
69
|
-
flags |= 1 if compact_empty
|
|
70
|
-
flags |= 2 if freeze_values
|
|
71
|
-
flags |= 4 if symbolize_keys
|
|
72
|
-
(depth_key << 3) | flags
|
|
73
|
-
end
|
|
74
|
-
|
|
75
220
|
def copy_with(copier, value)
|
|
76
221
|
return copier.call_reusable(value) unless copier.in_use?
|
|
77
222
|
|
|
78
223
|
new(
|
|
79
224
|
compact_empty: copier.compact_empty,
|
|
80
225
|
freeze_values: copier.freeze_values,
|
|
226
|
+
max_array_items: copier.max_array_items,
|
|
81
227
|
max_depth: copier.max_depth,
|
|
228
|
+
max_hash_keys: copier.max_hash_keys,
|
|
229
|
+
max_string_bytes: copier.max_string_bytes,
|
|
230
|
+
preserve_truncation_metadata: copier.preserve_truncation_metadata,
|
|
82
231
|
symbolize_keys: copier.symbolize_keys
|
|
83
232
|
).call(value)
|
|
84
233
|
end
|
|
@@ -103,18 +252,29 @@ module Julewire
|
|
|
103
252
|
end
|
|
104
253
|
end
|
|
105
254
|
|
|
106
|
-
attr_reader :compact_empty, :freeze_values, :max_depth, :
|
|
255
|
+
attr_reader :compact_empty, :freeze_values, :max_array_items, :max_depth, :max_hash_keys, :max_string_bytes,
|
|
256
|
+
:preserve_truncation_metadata, :symbolize_keys
|
|
107
257
|
|
|
108
|
-
def initialize(compact_empty:, freeze_values:, max_depth:,
|
|
258
|
+
def initialize(compact_empty:, freeze_values:, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:,
|
|
259
|
+
preserve_truncation_metadata:, symbolize_keys:)
|
|
109
260
|
@compact_empty = compact_empty
|
|
110
261
|
@freeze_values = freeze_values
|
|
262
|
+
@max_array_items = validate_optional_limit(max_array_items, name: :max_array_items)
|
|
111
263
|
@max_depth = max_depth
|
|
264
|
+
@max_hash_keys = validate_optional_limit(max_hash_keys, name: :max_hash_keys)
|
|
265
|
+
@max_string_bytes = validate_optional_limit(max_string_bytes, name: :max_string_bytes)
|
|
266
|
+
@preserve_truncation_metadata = preserve_truncation_metadata
|
|
112
267
|
@symbolize_keys = symbolize_keys
|
|
113
268
|
@in_use = false
|
|
269
|
+
@last_truncated = false
|
|
270
|
+
@track_truncation = !!(@max_array_items || @max_hash_keys || @max_string_bytes)
|
|
114
271
|
end
|
|
115
272
|
|
|
116
273
|
def call(value)
|
|
274
|
+
@last_truncated = false
|
|
117
275
|
traverse(value) { |root, depth| copy_value(root, depth) }
|
|
276
|
+
ensure
|
|
277
|
+
@last_truncated = false
|
|
118
278
|
end
|
|
119
279
|
|
|
120
280
|
def call_reusable(value)
|
|
@@ -137,7 +297,7 @@ module Julewire
|
|
|
137
297
|
end
|
|
138
298
|
|
|
139
299
|
def copy_container(value, depth)
|
|
140
|
-
return copy_string(Serializer::MAX_DEPTH_VALUE) if depth_limited?(depth)
|
|
300
|
+
return mark_truncated(copy_string(Serializer::MAX_DEPTH_VALUE)) if depth_limited?(depth)
|
|
141
301
|
return frozen_empty_container(value) if @freeze_values && value.empty?
|
|
142
302
|
|
|
143
303
|
with_traversal_container(value, CIRCULAR_REFERENCE) do
|
|
@@ -154,43 +314,124 @@ module Julewire
|
|
|
154
314
|
end
|
|
155
315
|
|
|
156
316
|
def copy_hash(value, depth)
|
|
317
|
+
fields = nil
|
|
157
318
|
result = {}
|
|
319
|
+
visited = 0
|
|
158
320
|
value.each do |key, item|
|
|
321
|
+
if hash_limit_reached?(visited)
|
|
322
|
+
fields = append_truncation_field(fields, "hash_keys")
|
|
323
|
+
break
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
visited += 1
|
|
327
|
+
# Raw-empty values still spend work budget. The limit protects traversal work, not output size.
|
|
159
328
|
next if @compact_empty && self.class.omitted_empty?(item)
|
|
160
329
|
|
|
161
|
-
|
|
162
|
-
|
|
330
|
+
fields = copy_hash_entry(result, fields, key, item, depth)
|
|
331
|
+
end
|
|
332
|
+
finish_hash(result, fields)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def hash_limit_reached?(visited)
|
|
336
|
+
@max_hash_keys && visited >= @max_hash_keys
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def copy_hash_entry(result, fields, key, item, depth)
|
|
340
|
+
return copy_truncation_metadata_entry(result, fields, key, item, depth) if reserved_truncation_key?(key)
|
|
341
|
+
|
|
342
|
+
copied = copy_value(item, depth + 1)
|
|
343
|
+
child_truncated = consume_truncated
|
|
344
|
+
return fields if @compact_empty && self.class.omitted_empty?(copied)
|
|
163
345
|
|
|
164
|
-
|
|
346
|
+
copied_key = copy_key(key)
|
|
347
|
+
key_truncated = consume_truncated
|
|
348
|
+
result[copied_key] = copied
|
|
349
|
+
record_hash_truncation(fields, copied_key, key_truncated || child_truncated)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def copy_truncation_metadata_entry(result, fields, key, item, depth)
|
|
353
|
+
unless @preserve_truncation_metadata &&
|
|
354
|
+
allowed_truncation_metadata_key?(key) &&
|
|
355
|
+
TruncationMetadata.valid?(item, max_fields: truncation_metadata_field_limit)
|
|
356
|
+
raise_reserved_key!(key)
|
|
165
357
|
end
|
|
166
|
-
|
|
358
|
+
|
|
359
|
+
result[copy_truncation_metadata_key(key)] = copy_value(item, depth + 1)
|
|
360
|
+
consume_truncated
|
|
361
|
+
fields
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def truncation_metadata_field_limit
|
|
365
|
+
@max_array_items || Serializer::DEFAULT_MAX_ARRAY_ITEMS
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def allowed_truncation_metadata_key?(key)
|
|
369
|
+
key.is_a?(Symbol) || @symbolize_keys
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def reserved_truncation_key?(key)
|
|
373
|
+
key == Serializer::TRUNCATION_METADATA_KEY || key == Serializer::TRUNCATION_METADATA_KEY.to_sym
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def copy_truncation_metadata_key(key)
|
|
377
|
+
@symbolize_keys && key.is_a?(String) ? key.to_sym : key
|
|
167
378
|
end
|
|
168
379
|
|
|
169
380
|
def copy_array(value, depth)
|
|
381
|
+
fields = nil
|
|
170
382
|
result = []
|
|
383
|
+
visited = 0
|
|
171
384
|
value.each do |item|
|
|
172
|
-
|
|
385
|
+
if array_limit_reached?(visited)
|
|
386
|
+
fields = append_truncation_field(fields, "array_items")
|
|
387
|
+
break
|
|
388
|
+
end
|
|
173
389
|
|
|
174
|
-
|
|
175
|
-
next if @compact_empty && self.class.omitted_empty?(
|
|
390
|
+
visited += 1
|
|
391
|
+
next if @compact_empty && self.class.omitted_empty?(item)
|
|
176
392
|
|
|
177
|
-
result
|
|
393
|
+
fields = copy_array_item(result, fields, item, depth)
|
|
178
394
|
end
|
|
179
|
-
|
|
395
|
+
finish_array(result, fields)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def array_limit_reached?(visited)
|
|
399
|
+
@max_array_items && visited >= @max_array_items
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def copy_array_item(result, fields, item, depth)
|
|
403
|
+
copied = copy_value(item, depth + 1)
|
|
404
|
+
child_truncated = consume_truncated
|
|
405
|
+
return fields if @compact_empty && self.class.omitted_empty?(copied)
|
|
406
|
+
|
|
407
|
+
result << copied
|
|
408
|
+
child_truncated ? append_truncation_field(fields, "array_item_values") : fields
|
|
180
409
|
end
|
|
181
410
|
|
|
182
411
|
def copy_key(key)
|
|
183
|
-
|
|
184
|
-
|
|
412
|
+
copied = key.is_a?(String) ? copy_string(key) : key
|
|
413
|
+
copied = copied.to_sym if @symbolize_keys && copied.is_a?(String)
|
|
414
|
+
raise_reserved_key!(copied)
|
|
415
|
+
|
|
416
|
+
copied
|
|
417
|
+
end
|
|
185
418
|
|
|
186
|
-
|
|
419
|
+
def raise_reserved_key!(key)
|
|
420
|
+
return unless RESERVED_KEYS.include?(key)
|
|
421
|
+
|
|
422
|
+
raise ArgumentError, "#{Serializer::TRUNCATION_METADATA_KEY} is reserved for Julewire truncation metadata"
|
|
187
423
|
end
|
|
188
424
|
|
|
189
425
|
def copy_string(value)
|
|
190
426
|
return value unless value.is_a?(String)
|
|
191
427
|
|
|
428
|
+
if @max_string_bytes && value.bytesize > @max_string_bytes
|
|
429
|
+
copy = "#{value.byteslice(0, @max_string_bytes).scrub("?")}#{Serializer::TRUNCATED_SUFFIX}"
|
|
430
|
+
return mark_truncated(freeze_container(copy))
|
|
431
|
+
end
|
|
432
|
+
|
|
192
433
|
copy = value.frozen? ? value : value.dup
|
|
193
|
-
|
|
434
|
+
clear_truncated(freeze_container(copy))
|
|
194
435
|
end
|
|
195
436
|
|
|
196
437
|
def copy_time(value)
|
|
@@ -76,7 +76,7 @@ module Julewire
|
|
|
76
76
|
|
|
77
77
|
assert_equal "[FI...[Truncated]", result.fetch(:secret)
|
|
78
78
|
assert_equal "abc...[Truncated]", result.fetch(:long)
|
|
79
|
-
assert_equal ["array_items"], result.dig(:list, 1, marker_key,
|
|
79
|
+
assert_equal ["array_items"], result.dig(:list, 1, marker_key, :truncated_fields)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def assert_julewire_integration_failure_contract(integration:, component:, exercise:)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: julewire-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Grebennik
|
|
@@ -187,6 +187,7 @@ files:
|
|
|
187
187
|
- lib/julewire/core/serialization/serializer.rb
|
|
188
188
|
- lib/julewire/core/serialization/serializer_pool.rb
|
|
189
189
|
- lib/julewire/core/serialization/text_encoder.rb
|
|
190
|
+
- lib/julewire/core/serialization/truncation_metadata.rb
|
|
190
191
|
- lib/julewire/core/serialization/value_copy.rb
|
|
191
192
|
- lib/julewire/core/serialization/value_traversal.rb
|
|
192
193
|
- lib/julewire/core/testing.rb
|