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
|
@@ -22,16 +22,13 @@ module Julewire
|
|
|
22
22
|
max_hash_keys: DEFAULT_MAX_HASH_KEYS,
|
|
23
23
|
max_string_bytes: DEFAULT_MAX_STRING_BYTES
|
|
24
24
|
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"max_string_bytes" => max_string_bytes
|
|
33
|
-
}
|
|
34
|
-
}
|
|
25
|
+
TruncationMetadata.build(
|
|
26
|
+
fields,
|
|
27
|
+
max_array_items: max_array_items,
|
|
28
|
+
max_depth: max_depth,
|
|
29
|
+
max_hash_keys: max_hash_keys,
|
|
30
|
+
max_string_bytes: max_string_bytes
|
|
31
|
+
)
|
|
35
32
|
end
|
|
36
33
|
end
|
|
37
34
|
|
|
@@ -110,15 +107,17 @@ module Julewire
|
|
|
110
107
|
fields = nil
|
|
111
108
|
result = {}
|
|
112
109
|
track_paths = @track_paths
|
|
110
|
+
visited = 0
|
|
113
111
|
value.each do |raw_key, item|
|
|
114
|
-
if
|
|
112
|
+
if visited >= @max_hash_keys
|
|
115
113
|
fields = append_truncation_field(fields, "hash_keys")
|
|
116
114
|
break
|
|
117
115
|
end
|
|
118
116
|
|
|
117
|
+
visited += 1
|
|
119
118
|
child = walk_value(item, depth + 1, raw_key, track_paths ? path_for(path, raw_key) : nil)
|
|
120
119
|
child_truncated = consume_truncated
|
|
121
|
-
key = key_value(raw_key)
|
|
120
|
+
key = key_value(raw_key, depth, path)
|
|
122
121
|
key_truncated = consume_truncated
|
|
123
122
|
result[key] = child
|
|
124
123
|
fields = record_hash_truncation(fields, raw_key, key, key_truncated, child_truncated)
|
|
@@ -130,19 +129,23 @@ module Julewire
|
|
|
130
129
|
fields = nil
|
|
131
130
|
result = {}
|
|
132
131
|
track_paths = @track_paths
|
|
132
|
+
visited = 0
|
|
133
133
|
value.each do |raw_key, item|
|
|
134
|
+
# The hash cap bounds visited input entries, not final output keys, so
|
|
135
|
+
# serialized-key collisions and compacted entries cannot hide work.
|
|
136
|
+
if visited >= @max_hash_keys
|
|
137
|
+
fields = append_truncation_field(fields, "hash_keys")
|
|
138
|
+
break
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
visited += 1
|
|
134
142
|
next if raw_omitted_value?(item)
|
|
135
143
|
|
|
136
144
|
child = walk_value(item, depth + 1, raw_key, track_paths ? path_for(path, raw_key) : nil)
|
|
137
145
|
child_truncated = consume_truncated
|
|
138
146
|
next if omitted_value?(child)
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
fields = append_truncation_field(fields, "hash_keys")
|
|
142
|
-
break
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
key = key_value(raw_key)
|
|
148
|
+
key = key_value(raw_key, depth, path)
|
|
146
149
|
key_truncated = consume_truncated
|
|
147
150
|
result[key] = child
|
|
148
151
|
fields = record_hash_truncation(fields, raw_key, key, key_truncated, child_truncated)
|
|
@@ -159,12 +162,14 @@ module Julewire
|
|
|
159
162
|
def walk_full_array(value, depth, path)
|
|
160
163
|
fields = nil
|
|
161
164
|
result = []
|
|
165
|
+
visited = 0
|
|
162
166
|
value.each do |item|
|
|
163
|
-
if
|
|
167
|
+
if visited >= @max_array_items
|
|
164
168
|
fields = append_truncation_field(fields, "array_items")
|
|
165
169
|
break
|
|
166
170
|
end
|
|
167
171
|
|
|
172
|
+
visited += 1
|
|
168
173
|
child = walk_value(item, depth + 1, nil, path)
|
|
169
174
|
child_truncated = consume_truncated
|
|
170
175
|
result << child
|
|
@@ -176,18 +181,20 @@ module Julewire
|
|
|
176
181
|
def walk_compact_array(value, depth, path)
|
|
177
182
|
fields = nil
|
|
178
183
|
result = []
|
|
184
|
+
visited = 0
|
|
179
185
|
value.each do |item|
|
|
186
|
+
if visited >= @max_array_items
|
|
187
|
+
fields = append_truncation_field(fields, "array_items")
|
|
188
|
+
break
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
visited += 1
|
|
180
192
|
next if raw_omitted_value?(item)
|
|
181
193
|
|
|
182
194
|
child = walk_value(item, depth + 1, nil, path)
|
|
183
195
|
child_truncated = consume_truncated
|
|
184
196
|
next if omitted_value?(child)
|
|
185
197
|
|
|
186
|
-
if result.length >= @max_array_items
|
|
187
|
-
fields = append_truncation_field(fields, "array_items")
|
|
188
|
-
break
|
|
189
|
-
end
|
|
190
|
-
|
|
191
198
|
result << child
|
|
192
199
|
fields = append_truncation_field(fields, "array_items") if child_truncated
|
|
193
200
|
end
|
|
@@ -198,7 +205,7 @@ module Julewire
|
|
|
198
205
|
|
|
199
206
|
def raw_omitted_value?(_value) = false
|
|
200
207
|
|
|
201
|
-
def key_value(key)
|
|
208
|
+
def key_value(key, _depth = nil, _path = nil)
|
|
202
209
|
clear_truncated(key.is_a?(String) ? copy_string(key) : key)
|
|
203
210
|
end
|
|
204
211
|
|
|
@@ -259,8 +266,7 @@ module Julewire
|
|
|
259
266
|
end
|
|
260
267
|
|
|
261
268
|
def append_truncation_field(fields, field)
|
|
262
|
-
(fields
|
|
263
|
-
fields
|
|
269
|
+
TruncationMetadata.append_field(fields, field)
|
|
264
270
|
end
|
|
265
271
|
|
|
266
272
|
def path_for(parent_path, key)
|
|
@@ -152,7 +152,7 @@ module Julewire
|
|
|
152
152
|
|
|
153
153
|
def raw_omitted_value?(value) = DeepCompactEmpty.omitted?(value)
|
|
154
154
|
|
|
155
|
-
def key_value(key) = serialize_key(key)
|
|
155
|
+
def key_value(key, _depth = nil, _path = nil) = serialize_key(key)
|
|
156
156
|
|
|
157
157
|
def error_value(error) = clear_truncated(unserializable_marker(error))
|
|
158
158
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Core
|
|
5
|
+
module Serialization
|
|
6
|
+
module TruncationMetadata
|
|
7
|
+
KEYS = {
|
|
8
|
+
string: {
|
|
9
|
+
truncated: "truncated",
|
|
10
|
+
truncated_fields: "truncated_fields",
|
|
11
|
+
limits: "limits",
|
|
12
|
+
max_array_items: "max_array_items",
|
|
13
|
+
max_depth: "max_depth",
|
|
14
|
+
max_hash_keys: "max_hash_keys",
|
|
15
|
+
max_string_bytes: "max_string_bytes"
|
|
16
|
+
}.freeze,
|
|
17
|
+
symbol: {
|
|
18
|
+
truncated: :truncated,
|
|
19
|
+
truncated_fields: :truncated_fields,
|
|
20
|
+
limits: :limits,
|
|
21
|
+
max_array_items: :max_array_items,
|
|
22
|
+
max_depth: :max_depth,
|
|
23
|
+
max_hash_keys: :max_hash_keys,
|
|
24
|
+
max_string_bytes: :max_string_bytes
|
|
25
|
+
}.freeze
|
|
26
|
+
}.freeze
|
|
27
|
+
METADATA_KEYS = KEYS.fetch(:symbol).values_at(:truncated, :truncated_fields, :limits).freeze
|
|
28
|
+
METADATA_KEY_NAMES = METADATA_KEYS.map(&:to_s).freeze
|
|
29
|
+
LIMIT_KEYS = KEYS.fetch(:symbol).values_at(
|
|
30
|
+
:max_array_items,
|
|
31
|
+
:max_depth,
|
|
32
|
+
:max_hash_keys,
|
|
33
|
+
:max_string_bytes
|
|
34
|
+
).freeze
|
|
35
|
+
LIMIT_KEY_NAMES = LIMIT_KEYS.map(&:to_s).freeze
|
|
36
|
+
[KEYS, METADATA_KEYS, METADATA_KEY_NAMES, LIMIT_KEYS, LIMIT_KEY_NAMES].each do |constant|
|
|
37
|
+
::Ractor.make_shareable(constant) if defined?(::Ractor) && ::Ractor.respond_to?(:make_shareable)
|
|
38
|
+
end
|
|
39
|
+
private_constant :KEYS, :METADATA_KEYS, :METADATA_KEY_NAMES, :LIMIT_KEYS, :LIMIT_KEY_NAMES
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
def build(fields, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:, key_style: :string,
|
|
43
|
+
compact_limits: false, freeze_values: false)
|
|
44
|
+
keys = KEYS.fetch(key_style)
|
|
45
|
+
limits = limits_hash(
|
|
46
|
+
keys,
|
|
47
|
+
max_array_items: max_array_items,
|
|
48
|
+
max_depth: max_depth,
|
|
49
|
+
max_hash_keys: max_hash_keys,
|
|
50
|
+
max_string_bytes: max_string_bytes,
|
|
51
|
+
compact_limits: compact_limits
|
|
52
|
+
)
|
|
53
|
+
metadata = {
|
|
54
|
+
keys.fetch(:truncated) => true,
|
|
55
|
+
keys.fetch(:truncated_fields) => field_list(fields),
|
|
56
|
+
keys.fetch(:limits) => limits
|
|
57
|
+
}
|
|
58
|
+
freeze_values ? deep_freeze(metadata, keys) : metadata
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def append_field(fields, field)
|
|
62
|
+
fields ||= []
|
|
63
|
+
fields << field unless fields.include?(field)
|
|
64
|
+
fields
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def valid?(value, max_fields: nil)
|
|
68
|
+
return false unless value.is_a?(Hash)
|
|
69
|
+
return false unless valid_top_level_keys?(value)
|
|
70
|
+
return false unless fetch_key(value, :truncated) == true
|
|
71
|
+
|
|
72
|
+
fields = fetch_key(value, :truncated_fields)
|
|
73
|
+
limits = fetch_key(value, :limits)
|
|
74
|
+
valid_fields?(fields, max_fields: max_fields) && valid_limits?(limits)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def field_list(fields)
|
|
80
|
+
Array(fields).uniq
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def limits_hash(keys, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:, compact_limits:)
|
|
84
|
+
limits = {
|
|
85
|
+
keys.fetch(:max_array_items) => max_array_items,
|
|
86
|
+
keys.fetch(:max_depth) => max_depth,
|
|
87
|
+
keys.fetch(:max_hash_keys) => max_hash_keys,
|
|
88
|
+
keys.fetch(:max_string_bytes) => max_string_bytes
|
|
89
|
+
}
|
|
90
|
+
compact_limits ? limits.compact : limits
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def deep_freeze(metadata, keys)
|
|
94
|
+
metadata.fetch(keys.fetch(:truncated_fields)).each(&:freeze)
|
|
95
|
+
metadata.fetch(keys.fetch(:truncated_fields)).freeze
|
|
96
|
+
metadata.fetch(keys.fetch(:limits)).freeze
|
|
97
|
+
metadata.freeze
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def fetch_key(value, key)
|
|
101
|
+
return value[key] if value.key?(key)
|
|
102
|
+
|
|
103
|
+
value[key.to_s]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def valid_top_level_keys?(value)
|
|
107
|
+
return false if value.length > METADATA_KEYS.length * 2
|
|
108
|
+
|
|
109
|
+
value.keys.all? { known_key?(it, METADATA_KEYS, METADATA_KEY_NAMES) } &&
|
|
110
|
+
METADATA_KEYS.all? { value.key?(it) || value.key?(it.to_s) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def valid_fields?(fields, max_fields:)
|
|
114
|
+
return false unless fields.is_a?(Array)
|
|
115
|
+
|
|
116
|
+
seen = 0
|
|
117
|
+
fields.each do |field|
|
|
118
|
+
seen += 1
|
|
119
|
+
return false if max_fields && seen > max_fields
|
|
120
|
+
return false unless field.is_a?(String) || field.is_a?(Symbol)
|
|
121
|
+
end
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def valid_limits?(limits)
|
|
126
|
+
return false unless limits.is_a?(Hash)
|
|
127
|
+
return false if limits.length > LIMIT_KEYS.length * 2
|
|
128
|
+
|
|
129
|
+
limits.all? do |key, value|
|
|
130
|
+
known_key?(key, LIMIT_KEYS, LIMIT_KEY_NAMES) && (value.nil? || value.is_a?(Integer))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def known_key?(key, symbol_keys, string_keys)
|
|
135
|
+
symbol_keys.include?(key) || string_keys.include?(key)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
private_constant :TruncationMetadata
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|