julewire-core 1.0.0 → 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 +29 -0
- data/docs/advanced-configuration.md +2 -1
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +26 -4
- data/docs/contracts.md +25 -50
- data/docs/extensions-and-api.md +87 -88
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +24 -23
- data/docs/outputs-and-lifecycle.md +15 -8
- data/docs/records-and-data-policy.md +38 -7
- 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/record_decoder.rb +1 -1
- 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 +56 -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 +16 -66
- 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 +54 -17
- 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 +22 -15
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +9 -3
- 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 +14 -17
- data/lib/julewire/core/execution/lineage.rb +26 -62
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +17 -22
- data/lib/julewire/core/execution/scope_fields.rb +4 -13
- 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 +52 -16
- data/lib/julewire/core/fields/field_stack.rb +69 -109
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +35 -26
- 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 +9 -14
- 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 +28 -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 +73 -19
- data/lib/julewire/core/propagation.rb +18 -4
- 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 +127 -258
- 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 +30 -18
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +87 -72
- 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 +3 -2
- data/lib/julewire/core/serialization/bounded_traversal.rb +72 -83
- 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 +22 -49
- 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 +157 -0
- data/lib/julewire/core/serialization/value_copy.rb +183 -109
- 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 +7 -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
|
@@ -3,129 +3,123 @@
|
|
|
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 finish_hash(result, fields)
|
|
16
|
+
add_truncation_metadata!(result, fields)
|
|
17
|
+
finish_container(result, fields)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def finish_array(result, fields)
|
|
21
|
+
if @track_truncation && fields
|
|
22
|
+
result << freeze_container({ Serializer::TRUNCATION_METADATA_KEY.to_sym => truncation_metadata(fields) })
|
|
23
|
+
end
|
|
24
|
+
finish_container(result, fields)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_truncation_metadata!(result, fields)
|
|
28
|
+
return unless @track_truncation && fields
|
|
29
|
+
|
|
30
|
+
result[Serializer::TRUNCATION_METADATA_KEY.to_sym] = truncation_metadata(fields)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def finish_container(result, fields)
|
|
34
|
+
value = freeze_container(result)
|
|
35
|
+
fields ? mark_truncated(value) : value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def truncation_metadata(fields)
|
|
39
|
+
TruncationMetadata.build(
|
|
40
|
+
fields,
|
|
41
|
+
key_style: :symbol,
|
|
42
|
+
compact_limits: true,
|
|
43
|
+
freeze_values: @freeze_values,
|
|
44
|
+
max_array_items: @max_array_items,
|
|
45
|
+
max_depth: @max_depth,
|
|
46
|
+
max_hash_keys: @max_hash_keys,
|
|
47
|
+
max_string_bytes: @max_string_bytes
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def mark_truncated(value)
|
|
52
|
+
@last_truncated = true
|
|
53
|
+
value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def consume_truncated
|
|
57
|
+
truncated = @last_truncated
|
|
58
|
+
@last_truncated = false
|
|
59
|
+
truncated
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def append_truncation_field(fields, field)
|
|
63
|
+
TruncationMetadata.append_field(fields, field)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
private_constant :ValueCopyTruncation
|
|
67
|
+
|
|
6
68
|
class ValueCopy
|
|
7
69
|
include ValueTraversal
|
|
70
|
+
include ValueCopyTruncation
|
|
8
71
|
|
|
9
72
|
CIRCULAR_REFERENCE = Core::CIRCULAR_REFERENCE
|
|
10
73
|
EMPTY_ARRAY = [].freeze
|
|
11
74
|
EMPTY_HASH = {}.freeze
|
|
12
|
-
|
|
13
|
-
private_constant :EMPTY_ARRAY, :EMPTY_HASH, :POOL_KEY
|
|
75
|
+
private_constant :EMPTY_ARRAY, :EMPTY_HASH
|
|
14
76
|
|
|
15
77
|
class << self
|
|
16
|
-
def call(
|
|
78
|
+
def call( # rubocop:disable Metrics/ParameterLists
|
|
17
79
|
value,
|
|
18
80
|
compact_empty: false,
|
|
19
81
|
freeze_values: false,
|
|
20
|
-
|
|
82
|
+
max_array_items: nil,
|
|
83
|
+
max_depth: NORMALIZATION_MAX_DEPTH,
|
|
84
|
+
max_hash_keys: nil,
|
|
85
|
+
max_string_bytes: nil,
|
|
86
|
+
preserve_truncation_metadata: false,
|
|
21
87
|
symbolize_keys: false
|
|
22
88
|
)
|
|
23
|
-
return copy_leaf(value, freeze_values: freeze_values) unless container?(value)
|
|
24
|
-
|
|
25
|
-
copy_with(
|
|
26
|
-
cached_copier(
|
|
27
|
-
compact_empty: compact_empty,
|
|
28
|
-
freeze_values: freeze_values,
|
|
29
|
-
max_depth: max_depth,
|
|
30
|
-
symbolize_keys: symbolize_keys
|
|
31
|
-
),
|
|
32
|
-
value
|
|
33
|
-
)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def omitted_empty?(value)
|
|
37
|
-
value.nil? || (value.is_a?(Hash) && value.empty?) || (value.is_a?(Array) && value.empty?)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def container?(value) = value.is_a?(Hash) || value.is_a?(Array)
|
|
43
|
-
|
|
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
|
-
def copy_with(copier, value)
|
|
76
|
-
return copier.call_reusable(value) unless copier.in_use?
|
|
77
|
-
|
|
78
89
|
new(
|
|
79
|
-
compact_empty
|
|
80
|
-
freeze_values
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
compact_empty:,
|
|
91
|
+
freeze_values:,
|
|
92
|
+
max_array_items:,
|
|
93
|
+
max_depth:,
|
|
94
|
+
max_hash_keys:,
|
|
95
|
+
max_string_bytes:,
|
|
96
|
+
preserve_truncation_metadata:,
|
|
97
|
+
symbolize_keys:
|
|
83
98
|
).call(value)
|
|
84
99
|
end
|
|
85
100
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
return copy_time(value, freeze_values: freeze_values) if value.is_a?(Time)
|
|
89
|
-
|
|
90
|
-
value
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def copy_string(value, freeze_values:)
|
|
94
|
-
copy = value.frozen? ? value : value.dup
|
|
95
|
-
freeze_values ? copy.freeze : copy
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def copy_time(value, freeze_values:)
|
|
99
|
-
return value unless freeze_values
|
|
100
|
-
return value if value.frozen?
|
|
101
|
-
|
|
102
|
-
value.dup.freeze
|
|
101
|
+
def omitted_empty?(value)
|
|
102
|
+
value.nil? || (value.is_a?(Hash) && value.empty?) || (value.is_a?(Array) && value.empty?)
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def initialize(compact_empty:, freeze_values:, max_depth:, symbolize_keys:)
|
|
106
|
+
def initialize(compact_empty:, freeze_values:, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:,
|
|
107
|
+
preserve_truncation_metadata:, symbolize_keys:)
|
|
109
108
|
@compact_empty = compact_empty
|
|
110
109
|
@freeze_values = freeze_values
|
|
110
|
+
@max_array_items = validate_optional_limit(max_array_items, name: :max_array_items)
|
|
111
111
|
@max_depth = max_depth
|
|
112
|
+
@max_hash_keys = validate_optional_limit(max_hash_keys, name: :max_hash_keys)
|
|
113
|
+
@max_string_bytes = validate_optional_limit(max_string_bytes, name: :max_string_bytes)
|
|
114
|
+
@preserve_truncation_metadata = preserve_truncation_metadata
|
|
112
115
|
@symbolize_keys = symbolize_keys
|
|
113
|
-
@
|
|
116
|
+
@track_truncation = @max_array_items || @max_hash_keys || @max_string_bytes
|
|
114
117
|
end
|
|
115
118
|
|
|
116
119
|
def call(value)
|
|
117
120
|
traverse(value) { |root, depth| copy_value(root, depth) }
|
|
118
121
|
end
|
|
119
122
|
|
|
120
|
-
def call_reusable(value)
|
|
121
|
-
@in_use = true
|
|
122
|
-
call(value)
|
|
123
|
-
ensure
|
|
124
|
-
@in_use = false
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def in_use? = @in_use
|
|
128
|
-
|
|
129
123
|
private
|
|
130
124
|
|
|
131
125
|
def copy_value(value, depth)
|
|
@@ -137,7 +131,7 @@ module Julewire
|
|
|
137
131
|
end
|
|
138
132
|
|
|
139
133
|
def copy_container(value, depth)
|
|
140
|
-
return
|
|
134
|
+
return mark_truncated(Serializer::MAX_DEPTH_VALUE) if depth == @max_depth
|
|
141
135
|
return frozen_empty_container(value) if @freeze_values && value.empty?
|
|
142
136
|
|
|
143
137
|
with_traversal_container(value, CIRCULAR_REFERENCE) do
|
|
@@ -145,52 +139,132 @@ module Julewire
|
|
|
145
139
|
end
|
|
146
140
|
end
|
|
147
141
|
|
|
148
|
-
def depth_limited?(depth)
|
|
149
|
-
@max_depth && depth >= @max_depth
|
|
150
|
-
end
|
|
151
|
-
|
|
152
142
|
def frozen_empty_container(value)
|
|
153
143
|
value.is_a?(Hash) ? EMPTY_HASH : EMPTY_ARRAY
|
|
154
144
|
end
|
|
155
145
|
|
|
156
146
|
def copy_hash(value, depth)
|
|
147
|
+
fields = nil
|
|
157
148
|
result = {}
|
|
149
|
+
visited = 0
|
|
158
150
|
value.each do |key, item|
|
|
151
|
+
if visited == @max_hash_keys
|
|
152
|
+
fields = append_truncation_field(fields, "hash_keys")
|
|
153
|
+
break
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
visited += 1
|
|
159
157
|
next if @compact_empty && self.class.omitted_empty?(item)
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
|
|
159
|
+
fields = copy_hash_entry(result, fields, key, item, depth)
|
|
160
|
+
end
|
|
161
|
+
finish_hash(result, fields)
|
|
162
|
+
end
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
def copy_hash_entry(result, fields, key, item, depth)
|
|
165
|
+
return copy_truncation_metadata_entry(result, fields, key, item) if reserved_truncation_key?(key)
|
|
166
|
+
|
|
167
|
+
validate_symbolized_key_shape!(key)
|
|
168
|
+
copied = copy_value(item, depth + 1)
|
|
169
|
+
return fields if @compact_empty && self.class.omitted_empty?(copied)
|
|
170
|
+
|
|
171
|
+
copied_key = copied_key_value(key)
|
|
172
|
+
value_or_key_truncated = consume_truncated
|
|
173
|
+
result[copied_key] = copied
|
|
174
|
+
fields = append_truncation_field(fields, copied_key.to_s) if value_or_key_truncated
|
|
175
|
+
fields
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def copy_truncation_metadata_entry(result, fields, key, item)
|
|
179
|
+
unless @preserve_truncation_metadata &&
|
|
180
|
+
allowed_truncation_metadata_key?(key) &&
|
|
181
|
+
TruncationMetadata.valid?(item, max_fields: truncation_metadata_field_limit)
|
|
182
|
+
raise_reserved_key!
|
|
165
183
|
end
|
|
166
|
-
|
|
184
|
+
|
|
185
|
+
result[copy_truncation_metadata_key(key)] = TruncationMetadata.copy(
|
|
186
|
+
item,
|
|
187
|
+
freeze_values: @freeze_values
|
|
188
|
+
)
|
|
189
|
+
fields
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def truncation_metadata_field_limit
|
|
193
|
+
@max_array_items || Serializer::DEFAULT_MAX_ARRAY_ITEMS
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def allowed_truncation_metadata_key?(key)
|
|
197
|
+
key.instance_of?(Symbol) || @symbolize_keys
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def reserved_truncation_key?(key)
|
|
201
|
+
key == Serializer::TRUNCATION_METADATA_KEY || key == Serializer::TRUNCATION_METADATA_KEY.to_sym
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def copy_truncation_metadata_key(key)
|
|
205
|
+
key.to_sym
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def validate_symbolized_key_shape!(key)
|
|
209
|
+
return unless @symbolize_keys
|
|
210
|
+
return if key.is_a?(String) || key.instance_of?(Symbol)
|
|
211
|
+
|
|
212
|
+
raise TypeError, Fields::Internal::FIELD_KEY_ERROR
|
|
167
213
|
end
|
|
168
214
|
|
|
169
215
|
def copy_array(value, depth)
|
|
170
216
|
result = []
|
|
217
|
+
visited = 0
|
|
218
|
+
array_items_truncated = false
|
|
219
|
+
array_item_values_truncated = false
|
|
171
220
|
value.each do |item|
|
|
221
|
+
if visited == @max_array_items
|
|
222
|
+
array_items_truncated = true
|
|
223
|
+
break
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
visited += 1
|
|
172
227
|
next if @compact_empty && self.class.omitted_empty?(item)
|
|
173
228
|
|
|
174
229
|
copied = copy_value(item, depth + 1)
|
|
230
|
+
child_truncated = consume_truncated
|
|
175
231
|
next if @compact_empty && self.class.omitted_empty?(copied)
|
|
176
232
|
|
|
177
233
|
result << copied
|
|
234
|
+
array_item_values_truncated ||= child_truncated
|
|
178
235
|
end
|
|
179
|
-
|
|
236
|
+
|
|
237
|
+
finish_array(result, array_truncation_fields(array_items_truncated, array_item_values_truncated))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def array_truncation_fields(array_items_truncated, array_item_values_truncated)
|
|
241
|
+
return unless array_items_truncated || array_item_values_truncated
|
|
242
|
+
|
|
243
|
+
fields = []
|
|
244
|
+
fields << "array_item_values" if array_item_values_truncated
|
|
245
|
+
fields << "array_items" if array_items_truncated
|
|
246
|
+
fields
|
|
180
247
|
end
|
|
181
248
|
|
|
182
|
-
def
|
|
183
|
-
return key
|
|
184
|
-
return copy_string(key) if key.is_a?(String)
|
|
249
|
+
def copied_key_value(key)
|
|
250
|
+
return key unless key.is_a?(String)
|
|
185
251
|
|
|
186
|
-
key
|
|
252
|
+
copy = copy_string(key)
|
|
253
|
+
@symbolize_keys ? copy.to_sym : copy
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def raise_reserved_key!
|
|
257
|
+
raise ArgumentError, "#{Serializer::TRUNCATION_METADATA_KEY} is reserved for Julewire truncation metadata"
|
|
187
258
|
end
|
|
188
259
|
|
|
189
260
|
def copy_string(value)
|
|
190
|
-
|
|
261
|
+
if @max_string_bytes && value.bytesize > @max_string_bytes
|
|
262
|
+
copy = "#{value.byteslice(0, @max_string_bytes).scrub("?")}#{Serializer::TRUNCATED_SUFFIX}"
|
|
263
|
+
return mark_truncated(freeze_container(copy))
|
|
264
|
+
end
|
|
191
265
|
|
|
192
266
|
copy = value.frozen? ? value : value.dup
|
|
193
|
-
|
|
267
|
+
freeze_container(copy)
|
|
194
268
|
end
|
|
195
269
|
|
|
196
270
|
def copy_time(value)
|
|
@@ -5,142 +5,25 @@ module Julewire
|
|
|
5
5
|
module Serialization
|
|
6
6
|
module ValueTraversal
|
|
7
7
|
def traverse(value)
|
|
8
|
-
previous_seen = @traversal_seen
|
|
9
|
-
previous_first_seen = @traversal_first_seen
|
|
10
|
-
previous_second_seen = @traversal_second_seen
|
|
11
|
-
previous_third_seen = @traversal_third_seen
|
|
12
|
-
previous_fourth_seen = @traversal_fourth_seen
|
|
13
|
-
@traversal_seen = nil
|
|
14
|
-
@traversal_first_seen = nil
|
|
15
|
-
@traversal_second_seen = nil
|
|
16
|
-
@traversal_third_seen = nil
|
|
17
|
-
@traversal_fourth_seen = nil
|
|
18
8
|
yield(value, 0)
|
|
19
|
-
ensure
|
|
20
|
-
@traversal_seen = previous_seen
|
|
21
|
-
@traversal_first_seen = previous_first_seen
|
|
22
|
-
@traversal_second_seen = previous_second_seen
|
|
23
|
-
@traversal_third_seen = previous_third_seen
|
|
24
|
-
@traversal_fourth_seen = previous_fourth_seen
|
|
25
9
|
end
|
|
26
10
|
|
|
27
11
|
private
|
|
28
12
|
|
|
29
|
-
def with_traversal_container(value, circular_value)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
first_seen = @traversal_first_seen
|
|
33
|
-
second_seen = @traversal_second_seen
|
|
34
|
-
third_seen = @traversal_third_seen
|
|
35
|
-
fourth_seen = @traversal_fourth_seen
|
|
36
|
-
return circular_value if seen&.key?(value) || first_seen.equal?(value) || second_seen.equal?(value) ||
|
|
37
|
-
third_seen.equal?(value) || fourth_seen.equal?(value)
|
|
13
|
+
def with_traversal_container(value, circular_value, &)
|
|
14
|
+
seen = traversal_seen
|
|
15
|
+
return circular_value if seen.include?(value)
|
|
38
16
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def traversal_seen?(value)
|
|
46
|
-
@traversal_seen&.key?(value) || @traversal_first_seen.equal?(value) ||
|
|
47
|
-
@traversal_second_seen.equal?(value) || @traversal_third_seen.equal?(value) ||
|
|
48
|
-
@traversal_fourth_seen.equal?(value)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def with_marked_traversal_container(value)
|
|
52
|
-
added = mark_traversal_container(
|
|
53
|
-
value,
|
|
54
|
-
@traversal_seen,
|
|
55
|
-
@traversal_first_seen,
|
|
56
|
-
@traversal_second_seen,
|
|
57
|
-
@traversal_third_seen,
|
|
58
|
-
@traversal_fourth_seen
|
|
59
|
-
)
|
|
60
|
-
yield
|
|
61
|
-
ensure
|
|
62
|
-
unmark_traversal_container(value, added)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def mark_traversal_container(value, seen, first_seen, second_seen, third_seen, fourth_seen)
|
|
66
|
-
# Most records visit only a handful of live containers. Keep those in
|
|
67
|
-
# slots and allocate the identity hash only for genuinely deep walks.
|
|
68
|
-
if seen
|
|
69
|
-
seen[value] = true
|
|
70
|
-
:hash
|
|
71
|
-
elsif first_seen.nil?
|
|
72
|
-
mark_first_seen(value)
|
|
73
|
-
elsif second_seen.nil?
|
|
74
|
-
mark_second_seen(value)
|
|
75
|
-
elsif third_seen.nil?
|
|
76
|
-
mark_third_seen(value)
|
|
77
|
-
elsif fourth_seen.nil?
|
|
78
|
-
mark_fourth_seen(value)
|
|
79
|
-
else
|
|
80
|
-
promote_traversal_seen(value, first_seen, second_seen, third_seen, fourth_seen)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def unmark_traversal_container(value, added)
|
|
85
|
-
case added
|
|
86
|
-
when :hash
|
|
87
|
-
@traversal_seen.delete(value)
|
|
88
|
-
when :first, :second, :third, :fourth
|
|
89
|
-
unmark_traversal_slot(value, added)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def mark_first_seen(value)
|
|
94
|
-
@traversal_first_seen = value
|
|
95
|
-
:first
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def mark_second_seen(value)
|
|
99
|
-
@traversal_second_seen = value
|
|
100
|
-
:second
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def mark_third_seen(value)
|
|
104
|
-
@traversal_third_seen = value
|
|
105
|
-
:third
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def mark_fourth_seen(value)
|
|
109
|
-
@traversal_fourth_seen = value
|
|
110
|
-
:fourth
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def promote_traversal_seen(value, first_seen, second_seen, third_seen, fourth_seen)
|
|
114
|
-
@traversal_seen = {}.compare_by_identity
|
|
115
|
-
@traversal_seen[first_seen] = true
|
|
116
|
-
@traversal_seen[second_seen] = true
|
|
117
|
-
@traversal_seen[third_seen] = true
|
|
118
|
-
@traversal_seen[fourth_seen] = true
|
|
119
|
-
@traversal_seen[value] = true
|
|
120
|
-
clear_traversal_slots!
|
|
121
|
-
:hash
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def unmark_traversal_slot(value, added)
|
|
125
|
-
return @traversal_seen.delete(value) if @traversal_seen
|
|
126
|
-
|
|
127
|
-
clear_traversal_slot(added)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def clear_traversal_slot(slot)
|
|
131
|
-
case slot
|
|
132
|
-
when :first then @traversal_first_seen = nil
|
|
133
|
-
when :second then @traversal_second_seen = nil
|
|
134
|
-
when :third then @traversal_third_seen = nil
|
|
135
|
-
when :fourth then @traversal_fourth_seen = nil
|
|
17
|
+
seen.add(value)
|
|
18
|
+
begin
|
|
19
|
+
yield
|
|
20
|
+
ensure
|
|
21
|
+
seen.delete(value)
|
|
136
22
|
end
|
|
137
23
|
end
|
|
138
24
|
|
|
139
|
-
def
|
|
140
|
-
@
|
|
141
|
-
@traversal_second_seen = nil
|
|
142
|
-
@traversal_third_seen = nil
|
|
143
|
-
@traversal_fourth_seen = nil
|
|
25
|
+
def traversal_seen
|
|
26
|
+
@traversal_seen ||= Set.new.compare_by_identity
|
|
144
27
|
end
|
|
145
28
|
end
|
|
146
29
|
|
|
@@ -19,20 +19,15 @@ module Julewire
|
|
|
19
19
|
nil
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def flush(*)
|
|
23
|
-
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def close(*)
|
|
27
|
-
self
|
|
28
|
-
end
|
|
22
|
+
def flush(*) = self
|
|
23
|
+
def close(*) = self
|
|
29
24
|
|
|
30
25
|
def health
|
|
31
|
-
{ status: :ok, counts: { captured:
|
|
26
|
+
{ status: :ok, counts: { captured: records.size } }
|
|
32
27
|
end
|
|
33
28
|
|
|
34
29
|
def clear
|
|
35
|
-
|
|
30
|
+
records.clear
|
|
36
31
|
self
|
|
37
32
|
end
|
|
38
33
|
end
|
|
@@ -46,17 +41,16 @@ module Julewire
|
|
|
46
41
|
end
|
|
47
42
|
|
|
48
43
|
def write(value)
|
|
49
|
-
|
|
44
|
+
writes << value
|
|
50
45
|
value.bytesize
|
|
51
46
|
end
|
|
52
47
|
|
|
53
48
|
def flush = self
|
|
54
|
-
|
|
55
49
|
def close = self
|
|
56
50
|
end
|
|
57
51
|
|
|
58
52
|
class << self
|
|
59
|
-
def configure_capture_destination(runtime
|
|
53
|
+
def configure_capture_destination(runtime, **)
|
|
60
54
|
destination = CaptureDestination.new(**)
|
|
61
55
|
runtime.configure do |config|
|
|
62
56
|
config.destinations.clear
|
|
@@ -70,53 +64,9 @@ module Julewire
|
|
|
70
64
|
yield records if block_given?
|
|
71
65
|
records
|
|
72
66
|
end
|
|
73
|
-
|
|
74
|
-
def unregister_destination(kind)
|
|
75
|
-
Core::Destinations.__send__(:unregister, kind)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def reset_shared_scheduler
|
|
79
|
-
Core::Scheduling::SharedScheduler.__send__(:reset_for_test!)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def with_overridden_singleton_method(receiver, method_name, replacement)
|
|
83
|
-
singleton_class = class << receiver; self; end
|
|
84
|
-
method_exists =
|
|
85
|
-
singleton_class.method_defined?(method_name) || singleton_class.private_method_defined?(method_name)
|
|
86
|
-
original = singleton_class.instance_method(method_name) if method_exists
|
|
87
|
-
verbose = $VERBOSE
|
|
88
|
-
$VERBOSE = nil
|
|
89
|
-
singleton_class.define_method(method_name, replacement)
|
|
90
|
-
yield
|
|
91
|
-
ensure
|
|
92
|
-
$VERBOSE = nil
|
|
93
|
-
if original
|
|
94
|
-
singleton_class.define_method(method_name, original)
|
|
95
|
-
elsif singleton_class&.method_defined?(method_name) || singleton_class&.private_method_defined?(method_name)
|
|
96
|
-
singleton_class.remove_method(method_name)
|
|
97
|
-
end
|
|
98
|
-
$VERBOSE = verbose
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def nonblocking_queue_values(queue)
|
|
102
|
-
values = []
|
|
103
|
-
loop do
|
|
104
|
-
values << queue.pop(true)
|
|
105
|
-
rescue ThreadError
|
|
106
|
-
return values
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
67
|
end
|
|
110
68
|
end
|
|
111
69
|
end
|
|
112
70
|
|
|
113
71
|
Testing = Core::Testing unless const_defined?(:Testing, false)
|
|
114
72
|
end
|
|
115
|
-
|
|
116
|
-
require_relative "testing/chaos"
|
|
117
|
-
require_relative "testing/chaos/catalog"
|
|
118
|
-
require_relative "testing/chaos/core_runtime"
|
|
119
|
-
require_relative "testing/chaos/destination"
|
|
120
|
-
require_relative "testing/chaos/emitter"
|
|
121
|
-
require_relative "testing/chaos/raising_output"
|
|
122
|
-
require_relative "testing/contracts"
|
|
@@ -7,9 +7,8 @@ module Julewire
|
|
|
7
7
|
class << self
|
|
8
8
|
def validate_byte_limit!(value, name:)
|
|
9
9
|
return if value.nil?
|
|
10
|
+
return value if value.instance_of?(Integer) && value.positive?
|
|
10
11
|
|
|
11
|
-
validate_integer_limit!(value, name: name, positive: true)
|
|
12
|
-
rescue ArgumentError
|
|
13
12
|
raise ArgumentError, "#{name} must be nil or a positive Integer"
|
|
14
13
|
end
|
|
15
14
|
|
|
@@ -18,7 +17,7 @@ module Julewire
|
|
|
18
17
|
end
|
|
19
18
|
|
|
20
19
|
def validate_integer_limit!(value, name:, positive: false)
|
|
21
|
-
return value if value.
|
|
20
|
+
return value if value.instance_of?(Integer) && valid_integer_limit?(value, positive: positive)
|
|
22
21
|
|
|
23
22
|
qualifier = positive ? "positive" : "non-negative"
|
|
24
23
|
raise ArgumentError, "#{name} must be a #{qualifier} Integer"
|
data/lib/julewire/core.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Julewire
|
|
|
6
6
|
# Each gem extends the shared Julewire namespace.
|
|
7
7
|
loader = Zeitwerk::Loader.for_gem_extension(self)
|
|
8
8
|
loader.inflector.inflect("cli" => "CLI")
|
|
9
|
-
loader.ignore("#{__dir__}/core/testing.rb"
|
|
9
|
+
loader.ignore("#{__dir__}/core/testing.rb")
|
|
10
10
|
loader.setup
|
|
11
11
|
|
|
12
12
|
module Core
|
|
@@ -25,7 +25,7 @@ module Julewire
|
|
|
25
25
|
|
|
26
26
|
value.to_sym
|
|
27
27
|
when Symbol
|
|
28
|
-
raise ArgumentError, "#{name} must not be empty" if value.
|
|
28
|
+
raise ArgumentError, "#{name} must not be empty" if value.empty?
|
|
29
29
|
|
|
30
30
|
value
|
|
31
31
|
else
|
|
@@ -54,11 +54,6 @@ module Julewire
|
|
|
54
54
|
extend Core::FacadeMethods
|
|
55
55
|
|
|
56
56
|
Core::RuntimeLocator.current = Core::Runtime.new
|
|
57
|
-
Core.singleton_class.class_eval do
|
|
58
|
-
define_method(:loader) { loader }
|
|
59
|
-
private :loader
|
|
60
|
-
end
|
|
61
|
-
|
|
62
57
|
ConsoleFormatter = Core::Records::ConsoleFormatter
|
|
63
58
|
JsonEncoder = Core::Serialization::JsonEncoder
|
|
64
59
|
Match = Core::Processing::Match
|