julewire-core 1.0.1 → 1.1.0
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 +18 -0
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +20 -0
- data/docs/contracts.md +23 -49
- data/docs/extensions-and-api.md +78 -82
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +21 -20
- data/docs/outputs-and-lifecycle.md +10 -6
- data/docs/records-and-data-policy.md +36 -5
- data/lib/julewire/core/cli/doctor.rb +20 -16
- data/lib/julewire/core/cli/line_helpers.rb +23 -12
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
- data/lib/julewire/core/cli/log_formats.rb +9 -9
- data/lib/julewire/core/cli/tail.rb +19 -14
- data/lib/julewire/core/cli/transcode.rb +4 -5
- data/lib/julewire/core/cli.rb +1 -1
- data/lib/julewire/core/configuration.rb +1 -1
- data/lib/julewire/core/context_store.rb +51 -87
- data/lib/julewire/core/destinations/chaos_output.rb +2 -2
- data/lib/julewire/core/destinations/collection.rb +8 -16
- data/lib/julewire/core/destinations/definition.rb +1 -9
- data/lib/julewire/core/destinations/destination.rb +13 -63
- data/lib/julewire/core/destinations/processor_handling.rb +78 -0
- data/lib/julewire/core/destinations/registry.rb +1 -1
- data/lib/julewire/core/destinations/sink.rb +0 -1
- data/lib/julewire/core/destinations/synchronized_output.rb +14 -13
- data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
- data/lib/julewire/core/destinations/write_step.rb +5 -5
- data/lib/julewire/core/destinations.rb +0 -7
- data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
- data/lib/julewire/core/diagnostics/doctor.rb +16 -9
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +6 -2
- data/lib/julewire/core/diagnostics/health.rb +41 -57
- data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
- data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
- data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
- data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
- data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
- data/lib/julewire/core/diagnostics/tail.rb +18 -22
- data/lib/julewire/core/execution/boundary.rb +2 -8
- data/lib/julewire/core/execution/handle.rb +12 -17
- data/lib/julewire/core/execution/lineage.rb +16 -56
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +15 -20
- data/lib/julewire/core/execution/scope_fields.rb +3 -12
- data/lib/julewire/core/execution/scope_identity.rb +1 -9
- data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
- data/lib/julewire/core/execution/summary_state.rb +16 -19
- data/lib/julewire/core/execution/view.rb +8 -17
- data/lib/julewire/core/facade_methods.rb +67 -65
- data/lib/julewire/core/fields/attribute_keys.rb +0 -2
- data/lib/julewire/core/fields/bags.rb +23 -27
- data/lib/julewire/core/fields/field_set.rb +29 -19
- data/lib/julewire/core/fields/field_stack.rb +68 -134
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +19 -38
- data/lib/julewire/core/fields/lookup.rb +9 -13
- data/lib/julewire/core/fields/section_proxy.rb +1 -9
- data/lib/julewire/core/fields/stack_set.rb +7 -12
- data/lib/julewire/core/fields/static_labels.rb +1 -1
- data/lib/julewire/core/integration/configurable.rb +4 -3
- data/lib/julewire/core/integration/destination_health.rb +17 -7
- data/lib/julewire/core/integration/event_subscriber.rb +1 -1
- data/lib/julewire/core/integration/facade.rb +12 -15
- data/lib/julewire/core/integration/fork_hooks.rb +12 -36
- data/lib/julewire/core/integration/health.rb +0 -3
- data/lib/julewire/core/integration/ivar_state.rb +0 -5
- data/lib/julewire/core/integration/protocol.rb +20 -0
- data/lib/julewire/core/integration/settings.rb +4 -4
- data/lib/julewire/core/integration/subscriber_install.rb +2 -2
- data/lib/julewire/core/integration/subscription.rb +3 -2
- data/lib/julewire/core/integration/values.rb +0 -17
- data/lib/julewire/core/local_storage.rb +3 -19
- data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
- data/lib/julewire/core/processing/level_threshold.rb +7 -14
- data/lib/julewire/core/processing/match.rb +6 -4
- data/lib/julewire/core/processing/pipeline.rb +46 -40
- data/lib/julewire/core/processing/processor_chain.rb +22 -11
- data/lib/julewire/core/processing/processor_registry.rb +3 -4
- data/lib/julewire/core/processing/record_field_transform.rb +0 -4
- data/lib/julewire/core/processing/sampling.rb +1 -3
- data/lib/julewire/core/processing.rb +2 -2
- data/lib/julewire/core/propagation/carrier.rb +14 -14
- data/lib/julewire/core/propagation.rb +15 -2
- data/lib/julewire/core/records/build_input.rb +75 -0
- data/lib/julewire/core/records/console_formatter.rb +6 -6
- data/lib/julewire/core/records/deconstruct.rb +1 -1
- data/lib/julewire/core/records/display_message.rb +2 -4
- data/lib/julewire/core/records/draft.rb +125 -262
- data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
- data/lib/julewire/core/records/metadata.rb +4 -3
- data/lib/julewire/core/records/public_projection.rb +2 -2
- data/lib/julewire/core/records/raw_input.rb +1 -1
- data/lib/julewire/core/records/record.rb +19 -37
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +67 -71
- data/lib/julewire/core/runtime_registry.rb +20 -15
- data/lib/julewire/core/runtime_state.rb +5 -3
- data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
- data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
- data/lib/julewire/core/serialization/bounded_transform.rb +2 -12
- data/lib/julewire/core/serialization/bounded_traversal.rb +49 -66
- data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
- data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
- data/lib/julewire/core/serialization/exception_shape.rb +7 -6
- data/lib/julewire/core/serialization/json_encoder.rb +10 -13
- data/lib/julewire/core/serialization/serializer.rb +23 -50
- data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
- data/lib/julewire/core/serialization/text_encoder.rb +5 -5
- data/lib/julewire/core/serialization/truncation_metadata.rb +63 -48
- data/lib/julewire/core/serialization/value_copy.rb +60 -227
- data/lib/julewire/core/serialization/value_traversal.rb +10 -127
- data/lib/julewire/core/testing.rb +6 -56
- data/lib/julewire/core/validation.rb +2 -3
- data/lib/julewire/core/version.rb +1 -1
- data/lib/julewire/core.rb +2 -7
- metadata +6 -19
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
- data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
- data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
- data/lib/julewire/core/testing/chaos/destination.rb +0 -55
- data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
- data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
- data/lib/julewire/core/testing/chaos.rb +0 -80
- data/lib/julewire/core/testing/contracts/component.rb +0 -162
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
- data/lib/julewire/core/testing/contracts/integration.rb +0 -166
- data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
- data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
- data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
- data/lib/julewire/core/testing/contracts/wire.rb +0 -60
- data/lib/julewire/core/testing/contracts.rb +0 -24
- data/lib/julewire/core/testing/coverage.rb +0 -58
- data/lib/julewire/core/testing/test_reports.rb +0 -78
|
@@ -35,12 +35,11 @@ module Julewire
|
|
|
35
35
|
@prepare_values = !@transform.nil?
|
|
36
36
|
@track_paths = @prepare_values && !track_paths.equal?(false)
|
|
37
37
|
end
|
|
38
|
+
private_class_method :new
|
|
38
39
|
|
|
39
40
|
def call(value)
|
|
40
41
|
@root = value
|
|
41
42
|
walk(value)
|
|
42
|
-
ensure
|
|
43
|
-
@root = nil
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
private
|
|
@@ -50,16 +49,7 @@ module Julewire
|
|
|
50
49
|
transformed.equal?(CONTINUE) ? value : transformed
|
|
51
50
|
end
|
|
52
51
|
|
|
53
|
-
def truncation_metadata(fields)
|
|
54
|
-
TruncationMetadata.build(
|
|
55
|
-
fields,
|
|
56
|
-
key_style: :symbol,
|
|
57
|
-
max_array_items: @max_array_items,
|
|
58
|
-
max_depth: @max_depth,
|
|
59
|
-
max_hash_keys: @max_hash_keys,
|
|
60
|
-
max_string_bytes: @max_string_bytes
|
|
61
|
-
)
|
|
62
|
-
end
|
|
52
|
+
def truncation_metadata(fields) = super(fields, key_style: :symbol)
|
|
63
53
|
end
|
|
64
54
|
end
|
|
65
55
|
end
|
|
@@ -20,10 +20,12 @@ module Julewire
|
|
|
20
20
|
max_array_items: DEFAULT_MAX_ARRAY_ITEMS,
|
|
21
21
|
max_depth: DEFAULT_MAX_DEPTH,
|
|
22
22
|
max_hash_keys: DEFAULT_MAX_HASH_KEYS,
|
|
23
|
-
max_string_bytes: DEFAULT_MAX_STRING_BYTES
|
|
23
|
+
max_string_bytes: DEFAULT_MAX_STRING_BYTES,
|
|
24
|
+
key_style: :string
|
|
24
25
|
)
|
|
25
26
|
TruncationMetadata.build(
|
|
26
27
|
fields,
|
|
28
|
+
key_style: key_style,
|
|
27
29
|
max_array_items: max_array_items,
|
|
28
30
|
max_depth: max_depth,
|
|
29
31
|
max_hash_keys: max_hash_keys,
|
|
@@ -40,57 +42,52 @@ module Julewire
|
|
|
40
42
|
@max_hash_keys = Validation.validate_integer_limit!(max_hash_keys, name: :max_hash_keys)
|
|
41
43
|
@max_string_bytes = Validation.validate_integer_limit!(max_string_bytes, name: :max_string_bytes)
|
|
42
44
|
@truncation_key = truncation_key
|
|
43
|
-
@last_truncated = false
|
|
44
|
-
@compact_empty = false
|
|
45
|
-
@prepare_values = false
|
|
46
|
-
@track_paths = false
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
private
|
|
50
48
|
|
|
51
49
|
def walk(value)
|
|
52
|
-
@last_truncated = false
|
|
53
50
|
traverse(value) { |root, depth| walk_value(root, depth, nil, nil) }
|
|
54
51
|
ensure
|
|
55
52
|
@last_truncated = false
|
|
56
53
|
end
|
|
57
54
|
|
|
58
55
|
def walk_value(value, depth, key, path)
|
|
59
|
-
@last_truncated = false
|
|
60
56
|
value = prepare_value(value, depth, key, path) if @prepare_values
|
|
61
|
-
return max_depth_value if depth
|
|
57
|
+
return max_depth_value if depth == @max_depth
|
|
62
58
|
return walk_container(value, depth, path) if value.is_a?(Array) || hash_like?(value)
|
|
63
59
|
|
|
64
|
-
scalar_value(value, depth
|
|
60
|
+
scalar_value(value, depth)
|
|
65
61
|
rescue StandardError => e
|
|
66
|
-
@last_truncated = false
|
|
67
62
|
error_value(e)
|
|
68
63
|
end
|
|
69
64
|
|
|
70
|
-
def prepare_value(value, _depth, _key, _path) = value
|
|
71
|
-
|
|
72
65
|
def hash_like?(value) = value.is_a?(Hash)
|
|
73
66
|
|
|
74
|
-
def scalar_value(value, _depth
|
|
75
|
-
value.is_a?(String) ? string_value(value) :
|
|
67
|
+
def scalar_value(value, _depth)
|
|
68
|
+
value.is_a?(String) ? string_value(value) : value
|
|
76
69
|
end
|
|
77
70
|
|
|
78
71
|
# Transform-stage errors must bubble so processors can fail closed.
|
|
79
|
-
def error_value(
|
|
80
|
-
raise
|
|
72
|
+
def error_value(_error)
|
|
73
|
+
raise
|
|
81
74
|
end
|
|
82
75
|
|
|
83
76
|
def walk_container(value, depth, path)
|
|
84
|
-
|
|
77
|
+
seen = traversal_seen
|
|
78
|
+
return circular_value if seen.include?(value)
|
|
85
79
|
|
|
86
|
-
|
|
80
|
+
seen.add(value)
|
|
81
|
+
begin
|
|
87
82
|
value.is_a?(Array) ? walk_array(value, depth, path) : walk_hash(value, depth, path)
|
|
83
|
+
ensure
|
|
84
|
+
seen.delete(value)
|
|
88
85
|
end
|
|
89
86
|
end
|
|
90
87
|
|
|
91
88
|
def circular_value
|
|
92
89
|
@last_truncated = true
|
|
93
|
-
|
|
90
|
+
CIRCULAR_REFERENCE
|
|
94
91
|
end
|
|
95
92
|
|
|
96
93
|
def max_depth_value
|
|
@@ -98,7 +95,7 @@ module Julewire
|
|
|
98
95
|
end
|
|
99
96
|
|
|
100
97
|
def walk_hash(value, depth, path)
|
|
101
|
-
return walk_compact_hash(value, depth
|
|
98
|
+
return walk_compact_hash(value, depth) if @compact_empty
|
|
102
99
|
|
|
103
100
|
walk_full_hash(value, depth, path)
|
|
104
101
|
end
|
|
@@ -109,31 +106,29 @@ module Julewire
|
|
|
109
106
|
track_paths = @track_paths
|
|
110
107
|
visited = 0
|
|
111
108
|
value.each do |raw_key, item|
|
|
112
|
-
if visited
|
|
109
|
+
if visited == @max_hash_keys
|
|
113
110
|
fields = append_truncation_field(fields, "hash_keys")
|
|
114
111
|
break
|
|
115
112
|
end
|
|
116
113
|
|
|
117
114
|
visited += 1
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
key = key_value(raw_key
|
|
121
|
-
key_truncated = consume_truncated
|
|
115
|
+
child_path = path_for(path, raw_key) if track_paths
|
|
116
|
+
child = walk_value(item, depth + 1, raw_key, child_path)
|
|
117
|
+
key = key_value(raw_key)
|
|
122
118
|
result[key] = child
|
|
123
|
-
fields = record_hash_truncation(fields,
|
|
119
|
+
fields = record_hash_truncation(fields, key, consume_truncated)
|
|
124
120
|
end
|
|
125
121
|
finish_hash(result, fields)
|
|
126
122
|
end
|
|
127
123
|
|
|
128
|
-
def walk_compact_hash(value, depth
|
|
124
|
+
def walk_compact_hash(value, depth)
|
|
129
125
|
fields = nil
|
|
130
126
|
result = {}
|
|
131
|
-
track_paths = @track_paths
|
|
132
127
|
visited = 0
|
|
133
128
|
value.each do |raw_key, item|
|
|
134
129
|
# The hash cap bounds visited input entries, not final output keys, so
|
|
135
130
|
# serialized-key collisions and compacted entries cannot hide work.
|
|
136
|
-
if visited
|
|
131
|
+
if visited == @max_hash_keys
|
|
137
132
|
fields = append_truncation_field(fields, "hash_keys")
|
|
138
133
|
break
|
|
139
134
|
end
|
|
@@ -141,31 +136,29 @@ module Julewire
|
|
|
141
136
|
visited += 1
|
|
142
137
|
next if raw_omitted_value?(item)
|
|
143
138
|
|
|
144
|
-
child = walk_value(item, depth + 1,
|
|
145
|
-
child_truncated = consume_truncated
|
|
139
|
+
child = walk_value(item, depth + 1, nil, nil)
|
|
146
140
|
next if omitted_value?(child)
|
|
147
141
|
|
|
148
|
-
key = key_value(raw_key
|
|
149
|
-
key_truncated = consume_truncated
|
|
142
|
+
key = key_value(raw_key)
|
|
150
143
|
result[key] = child
|
|
151
|
-
fields = record_hash_truncation(fields,
|
|
144
|
+
fields = record_hash_truncation(fields, key, consume_truncated)
|
|
152
145
|
end
|
|
153
146
|
finish_hash(result, fields)
|
|
154
147
|
end
|
|
155
148
|
|
|
156
149
|
def walk_array(value, depth, path)
|
|
157
|
-
return walk_compact_array(value, depth
|
|
150
|
+
return walk_compact_array(value, depth) if @compact_empty
|
|
158
151
|
|
|
159
152
|
walk_full_array(value, depth, path)
|
|
160
153
|
end
|
|
161
154
|
|
|
162
155
|
def walk_full_array(value, depth, path)
|
|
163
|
-
|
|
156
|
+
truncated = false
|
|
164
157
|
result = []
|
|
165
158
|
visited = 0
|
|
166
159
|
value.each do |item|
|
|
167
|
-
if visited
|
|
168
|
-
|
|
160
|
+
if visited == @max_array_items
|
|
161
|
+
truncated = true
|
|
169
162
|
break
|
|
170
163
|
end
|
|
171
164
|
|
|
@@ -173,65 +166,60 @@ module Julewire
|
|
|
173
166
|
child = walk_value(item, depth + 1, nil, path)
|
|
174
167
|
child_truncated = consume_truncated
|
|
175
168
|
result << child
|
|
176
|
-
|
|
169
|
+
truncated = true if child_truncated
|
|
177
170
|
end
|
|
178
|
-
finish_array(result,
|
|
171
|
+
finish_array(result, truncated && "array_items")
|
|
179
172
|
end
|
|
180
173
|
|
|
181
|
-
def walk_compact_array(value, depth
|
|
182
|
-
|
|
174
|
+
def walk_compact_array(value, depth)
|
|
175
|
+
truncated = false
|
|
183
176
|
result = []
|
|
184
177
|
visited = 0
|
|
185
178
|
value.each do |item|
|
|
186
|
-
if visited
|
|
187
|
-
|
|
179
|
+
if visited == @max_array_items
|
|
180
|
+
truncated = true
|
|
188
181
|
break
|
|
189
182
|
end
|
|
190
183
|
|
|
191
184
|
visited += 1
|
|
192
185
|
next if raw_omitted_value?(item)
|
|
193
186
|
|
|
194
|
-
child = walk_value(item, depth + 1, nil,
|
|
187
|
+
child = walk_value(item, depth + 1, nil, nil)
|
|
195
188
|
child_truncated = consume_truncated
|
|
196
189
|
next if omitted_value?(child)
|
|
197
190
|
|
|
198
191
|
result << child
|
|
199
|
-
|
|
192
|
+
truncated = true if child_truncated
|
|
200
193
|
end
|
|
201
|
-
finish_array(result,
|
|
194
|
+
finish_array(result, truncated && "array_items")
|
|
202
195
|
end
|
|
203
196
|
|
|
204
|
-
def
|
|
205
|
-
|
|
206
|
-
def raw_omitted_value?(_value) = false
|
|
207
|
-
|
|
208
|
-
def key_value(key, _depth = nil, _path = nil)
|
|
209
|
-
clear_truncated(key.is_a?(String) ? copy_string(key) : key)
|
|
210
|
-
end
|
|
197
|
+
def key_value(key) = key
|
|
211
198
|
|
|
212
|
-
def record_hash_truncation(fields,
|
|
213
|
-
return fields unless
|
|
199
|
+
def record_hash_truncation(fields, key, truncated)
|
|
200
|
+
return fields unless truncated
|
|
214
201
|
|
|
215
|
-
append_truncation_field(fields,
|
|
202
|
+
append_truncation_field(fields, key.to_s)
|
|
216
203
|
end
|
|
217
204
|
|
|
218
205
|
def finish_hash(result, fields)
|
|
219
|
-
return
|
|
206
|
+
return result unless fields
|
|
220
207
|
|
|
221
208
|
result[@truncation_key] = truncation_metadata(fields) if @truncation_key
|
|
222
209
|
mark_truncated(result)
|
|
223
210
|
end
|
|
224
211
|
|
|
225
212
|
def finish_array(result, fields)
|
|
226
|
-
return
|
|
213
|
+
return result unless fields
|
|
227
214
|
|
|
228
215
|
result << { @truncation_key => truncation_metadata(fields) } if @truncation_key
|
|
229
216
|
mark_truncated(result)
|
|
230
217
|
end
|
|
231
218
|
|
|
232
|
-
def truncation_metadata(fields)
|
|
219
|
+
def truncation_metadata(fields, key_style: :string)
|
|
233
220
|
self.class.truncation_metadata(
|
|
234
221
|
fields,
|
|
222
|
+
key_style: key_style,
|
|
235
223
|
max_array_items: @max_array_items,
|
|
236
224
|
max_depth: @max_depth,
|
|
237
225
|
max_hash_keys: @max_hash_keys,
|
|
@@ -240,7 +228,7 @@ module Julewire
|
|
|
240
228
|
end
|
|
241
229
|
|
|
242
230
|
def string_value(value)
|
|
243
|
-
return
|
|
231
|
+
return copy_string(value) if value.bytesize <= @max_string_bytes
|
|
244
232
|
|
|
245
233
|
mark_truncated("#{value.byteslice(0, @max_string_bytes).scrub("?")}#{TRUNCATED_SUFFIX}")
|
|
246
234
|
end
|
|
@@ -250,11 +238,6 @@ module Julewire
|
|
|
250
238
|
value
|
|
251
239
|
end
|
|
252
240
|
|
|
253
|
-
def clear_truncated(value)
|
|
254
|
-
@last_truncated = false
|
|
255
|
-
value
|
|
256
|
-
end
|
|
257
|
-
|
|
258
241
|
def consume_truncated
|
|
259
242
|
truncated = @last_truncated
|
|
260
243
|
@last_truncated = false
|
|
@@ -27,23 +27,21 @@ module Julewire
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def compact_value!(value)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
if value.is_a?(Hash)
|
|
31
|
+
compact_hash!(value)
|
|
32
|
+
elsif value.is_a?(Array)
|
|
33
|
+
compact_array!(value)
|
|
34
|
+
else
|
|
35
|
+
value
|
|
36
|
+
end
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def compact_hash!(value)
|
|
37
40
|
with_traversal_container(value, value) do
|
|
38
41
|
value.each do |key, item|
|
|
39
42
|
compacted = compact_value!(item)
|
|
40
|
-
if self.class.omitted?(compacted)
|
|
41
|
-
value.delete(key)
|
|
42
|
-
elsif !compacted.equal?(item)
|
|
43
|
-
value[key] = compacted
|
|
44
|
-
end
|
|
43
|
+
value.delete(key) if self.class.omitted?(compacted)
|
|
45
44
|
end
|
|
46
|
-
value
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
|
|
@@ -57,7 +55,7 @@ module Julewire
|
|
|
57
55
|
value[index] = compacted
|
|
58
56
|
index += 1
|
|
59
57
|
end
|
|
60
|
-
value.slice!(index, value.length
|
|
58
|
+
value.slice!(index, value.length)
|
|
61
59
|
value
|
|
62
60
|
end
|
|
63
61
|
end
|
|
@@ -7,9 +7,46 @@ module Julewire
|
|
|
7
7
|
include ValueTraversal
|
|
8
8
|
|
|
9
9
|
class << self
|
|
10
|
-
def call(value, max_depth:
|
|
10
|
+
def call(value, max_depth: NORMALIZATION_MAX_DEPTH, trust_frozen: false, validate_symbol_keys: false)
|
|
11
|
+
validate_symbol_keys(value) if validate_symbol_keys
|
|
11
12
|
new(max_depth, trust_frozen: trust_frozen).call(value)
|
|
12
13
|
end
|
|
14
|
+
|
|
15
|
+
def validate_symbol_keys(value)
|
|
16
|
+
# WeakMap is identity-keyed and accepts new keys while enumerating,
|
|
17
|
+
# so reachable containers form one finite work inventory without a
|
|
18
|
+
# separate cycle-skip branch.
|
|
19
|
+
containers = ObjectSpace::WeakMap.new
|
|
20
|
+
containers[value] = nil if container?(value)
|
|
21
|
+
containers.each_key do |current|
|
|
22
|
+
if current.is_a?(Hash)
|
|
23
|
+
current.each do |key, item|
|
|
24
|
+
validate_symbol_key!(key)
|
|
25
|
+
containers[item] = nil if container?(item)
|
|
26
|
+
end
|
|
27
|
+
else
|
|
28
|
+
current.each { containers[it] = nil if container?(it) }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate_symbol_hash(value)
|
|
35
|
+
raise TypeError, "owned data must be a Hash" unless value.is_a?(Hash)
|
|
36
|
+
|
|
37
|
+
validate_symbol_keys(value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def container?(value)
|
|
43
|
+
value.is_a?(Hash) || value.is_a?(Array)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_symbol_key!(key)
|
|
47
|
+
raise TypeError, Fields::Internal::RECORD_STRING_KEY_ERROR if key.is_a?(String)
|
|
48
|
+
raise TypeError, Fields::Internal::RECORD_SYMBOL_KEY_ERROR unless key.instance_of?(Symbol)
|
|
49
|
+
end
|
|
13
50
|
end
|
|
14
51
|
|
|
15
52
|
def initialize(max_depth, trust_frozen:)
|
|
@@ -24,7 +61,7 @@ module Julewire
|
|
|
24
61
|
private
|
|
25
62
|
|
|
26
63
|
def freeze_value(value, depth)
|
|
27
|
-
|
|
64
|
+
value.freeze if value.is_a?(String)
|
|
28
65
|
return value if @trust_frozen && value.frozen?
|
|
29
66
|
return freeze_container(value, depth) if value.is_a?(Hash) || value.is_a?(Array)
|
|
30
67
|
|
|
@@ -32,7 +69,7 @@ module Julewire
|
|
|
32
69
|
end
|
|
33
70
|
|
|
34
71
|
def freeze_container(value, depth)
|
|
35
|
-
return Serializer::MAX_DEPTH_VALUE
|
|
72
|
+
return Serializer::MAX_DEPTH_VALUE if depth_limited?(depth)
|
|
36
73
|
|
|
37
74
|
with_traversal_container(value, value) do
|
|
38
75
|
value.is_a?(Hash) ? freeze_hash(value, depth) : freeze_array(value, depth)
|
|
@@ -40,11 +77,13 @@ module Julewire
|
|
|
40
77
|
end
|
|
41
78
|
|
|
42
79
|
def depth_limited?(depth)
|
|
43
|
-
|
|
80
|
+
depth == @max_depth
|
|
44
81
|
end
|
|
45
82
|
|
|
46
83
|
def freeze_hash(value, depth)
|
|
47
|
-
value.each
|
|
84
|
+
value.each do |key, item|
|
|
85
|
+
freeze_child(value, key, item, depth)
|
|
86
|
+
end
|
|
48
87
|
value.freeze
|
|
49
88
|
end
|
|
50
89
|
|
|
@@ -55,7 +94,7 @@ module Julewire
|
|
|
55
94
|
|
|
56
95
|
def freeze_child(value, key, item, depth)
|
|
57
96
|
frozen = freeze_value(item, depth + 1)
|
|
58
|
-
value[key] = frozen unless value.frozen?
|
|
97
|
+
value[key] = frozen unless value.frozen?
|
|
59
98
|
end
|
|
60
99
|
end
|
|
61
100
|
end
|
|
@@ -9,7 +9,6 @@ module Julewire
|
|
|
9
9
|
raise TypeError, "value must be a String" unless value.is_a?(String)
|
|
10
10
|
|
|
11
11
|
return value if valid_utf8?(value) || valid_ascii_only?(value)
|
|
12
|
-
return value.scrub("?") if utf8?(value)
|
|
13
12
|
|
|
14
13
|
encode_utf8(value)
|
|
15
14
|
rescue EncodingError
|
|
@@ -23,7 +22,7 @@ module Julewire
|
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def valid_ascii_only?(value)
|
|
26
|
-
value.ascii_only?
|
|
25
|
+
value.ascii_only?
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def utf8?(value)
|
|
@@ -31,7 +30,7 @@ module Julewire
|
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def encode_utf8(value)
|
|
34
|
-
value.encode(Encoding::UTF_8, invalid: :replace,
|
|
33
|
+
value.encode(Encoding::UTF_8, invalid: :replace, replace: "?")
|
|
35
34
|
end
|
|
36
35
|
end
|
|
37
36
|
end
|
|
@@ -9,7 +9,7 @@ module Julewire
|
|
|
9
9
|
DEFAULT_MAX_CAUSE_DEPTH = 5
|
|
10
10
|
|
|
11
11
|
class << self
|
|
12
|
-
def call(error, max_backtrace_lines:
|
|
12
|
+
def call(error, max_backtrace_lines: MAX_BACKTRACE_LINES, max_cause_depth: DEFAULT_MAX_CAUSE_DEPTH)
|
|
13
13
|
new(
|
|
14
14
|
max_backtrace_lines: max_backtrace_lines,
|
|
15
15
|
max_cause_depth: max_cause_depth
|
|
@@ -32,7 +32,7 @@ module Julewire
|
|
|
32
32
|
def shape_exception(error, depth)
|
|
33
33
|
return error unless error.is_a?(Exception)
|
|
34
34
|
|
|
35
|
-
with_traversal_container(error,
|
|
35
|
+
with_traversal_container(error, CIRCULAR_REFERENCE) do
|
|
36
36
|
exception_hash(error, depth)
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -50,7 +50,7 @@ module Julewire
|
|
|
50
50
|
cause = exception_cause(error)
|
|
51
51
|
next unless cause
|
|
52
52
|
|
|
53
|
-
if depth
|
|
53
|
+
if depth == @max_cause_depth
|
|
54
54
|
result[:cause_truncated] = true
|
|
55
55
|
else
|
|
56
56
|
result[:cause] = shape_exception(cause, depth + 1)
|
|
@@ -59,20 +59,21 @@ module Julewire
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def class_name(error)
|
|
62
|
-
error.class.
|
|
62
|
+
error.class.to_s
|
|
63
63
|
rescue StandardError
|
|
64
64
|
"Exception"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def error_message(error)
|
|
68
68
|
message = error.message
|
|
69
|
-
message.
|
|
69
|
+
message.instance_of?(String) ? message.dup : message.to_s
|
|
70
70
|
rescue StandardError
|
|
71
71
|
"[Unavailable]"
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def backtrace(error)
|
|
75
|
-
@backtrace_limiter.call(backtrace:
|
|
75
|
+
limited = @backtrace_limiter.call(backtrace: Fields::FieldSet.deep_dup(error.backtrace))
|
|
76
|
+
limited.fetch(:backtrace)
|
|
76
77
|
rescue StandardError
|
|
77
78
|
nil
|
|
78
79
|
end
|
|
@@ -13,7 +13,7 @@ module Julewire
|
|
|
13
13
|
max_array_items: Serializer::DEFAULT_MAX_ARRAY_ITEMS,
|
|
14
14
|
max_hash_keys: Serializer::DEFAULT_MAX_HASH_KEYS,
|
|
15
15
|
compact_empty: true,
|
|
16
|
-
max_backtrace_lines:
|
|
16
|
+
max_backtrace_lines: MAX_BACKTRACE_LINES,
|
|
17
17
|
append_newline: true
|
|
18
18
|
)
|
|
19
19
|
@max_depth = max_depth
|
|
@@ -23,6 +23,7 @@ module Julewire
|
|
|
23
23
|
@compact_empty = compact_empty
|
|
24
24
|
@max_backtrace_lines = max_backtrace_lines
|
|
25
25
|
@line_suffix = append_newline ? "\n" : ""
|
|
26
|
+
# `append_newline` is suffix-only; it does not change serializer shape.
|
|
26
27
|
@serializer_key = [
|
|
27
28
|
@max_depth,
|
|
28
29
|
@max_string_bytes,
|
|
@@ -30,11 +31,11 @@ module Julewire
|
|
|
30
31
|
@max_hash_keys,
|
|
31
32
|
@compact_empty,
|
|
32
33
|
@max_backtrace_lines
|
|
33
|
-
]
|
|
34
|
+
]
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def call(payload)
|
|
37
|
-
JSON.generate(serialized_payload(payload)
|
|
38
|
+
JSON.generate(serialized_payload(payload)).tap do |json|
|
|
38
39
|
json << @line_suffix
|
|
39
40
|
end
|
|
40
41
|
end
|
|
@@ -42,14 +43,11 @@ module Julewire
|
|
|
42
43
|
private
|
|
43
44
|
|
|
44
45
|
def serialized_payload(payload)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def cached_serializer
|
|
52
|
-
SerializerPool.serializer(:julewire_core_json_encoder_serializers, @serializer_key) { build_serializer }
|
|
46
|
+
SerializerPool.serialize(
|
|
47
|
+
:julewire_core_json_encoder_serializers,
|
|
48
|
+
@serializer_key,
|
|
49
|
+
payload
|
|
50
|
+
) { build_serializer }
|
|
53
51
|
end
|
|
54
52
|
|
|
55
53
|
def build_serializer
|
|
@@ -59,8 +57,7 @@ module Julewire
|
|
|
59
57
|
max_array_items: @max_array_items,
|
|
60
58
|
max_hash_keys: @max_hash_keys,
|
|
61
59
|
compact_empty: @compact_empty,
|
|
62
|
-
max_backtrace_lines: @max_backtrace_lines
|
|
63
|
-
copy_strings: false
|
|
60
|
+
max_backtrace_lines: @max_backtrace_lines
|
|
64
61
|
)
|
|
65
62
|
end
|
|
66
63
|
end
|