julewire-core 1.0.1 → 1.1.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/docs/configuration.md +14 -7
  4. data/docs/context-and-propagation.md +20 -0
  5. data/docs/contracts.md +23 -49
  6. data/docs/extensions-and-api.md +78 -82
  7. data/docs/health-schema.md +12 -2
  8. data/docs/internals.md +21 -20
  9. data/docs/outputs-and-lifecycle.md +10 -6
  10. data/docs/records-and-data-policy.md +36 -5
  11. data/lib/julewire/core/cli/doctor.rb +20 -16
  12. data/lib/julewire/core/cli/line_helpers.rb +23 -12
  13. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
  14. data/lib/julewire/core/cli/log_formats.rb +9 -9
  15. data/lib/julewire/core/cli/tail.rb +19 -14
  16. data/lib/julewire/core/cli/transcode.rb +4 -5
  17. data/lib/julewire/core/cli.rb +1 -1
  18. data/lib/julewire/core/configuration.rb +1 -1
  19. data/lib/julewire/core/context_store.rb +51 -87
  20. data/lib/julewire/core/destinations/chaos_output.rb +2 -2
  21. data/lib/julewire/core/destinations/collection.rb +8 -16
  22. data/lib/julewire/core/destinations/definition.rb +1 -9
  23. data/lib/julewire/core/destinations/destination.rb +13 -63
  24. data/lib/julewire/core/destinations/processor_handling.rb +78 -0
  25. data/lib/julewire/core/destinations/registry.rb +1 -1
  26. data/lib/julewire/core/destinations/sink.rb +0 -1
  27. data/lib/julewire/core/destinations/synchronized_output.rb +14 -13
  28. data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
  29. data/lib/julewire/core/destinations/write_step.rb +5 -5
  30. data/lib/julewire/core/destinations.rb +0 -7
  31. data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
  32. data/lib/julewire/core/diagnostics/doctor.rb +16 -9
  33. data/lib/julewire/core/diagnostics/failure_snapshot.rb +6 -2
  34. data/lib/julewire/core/diagnostics/health.rb +41 -57
  35. data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
  36. data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
  37. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
  38. data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
  39. data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
  40. data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
  41. data/lib/julewire/core/diagnostics/tail.rb +18 -22
  42. data/lib/julewire/core/execution/boundary.rb +2 -8
  43. data/lib/julewire/core/execution/handle.rb +12 -17
  44. data/lib/julewire/core/execution/lineage.rb +16 -56
  45. data/lib/julewire/core/execution/measurement_handle.rb +6 -10
  46. data/lib/julewire/core/execution/scope.rb +15 -20
  47. data/lib/julewire/core/execution/scope_fields.rb +3 -12
  48. data/lib/julewire/core/execution/scope_identity.rb +1 -9
  49. data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
  50. data/lib/julewire/core/execution/summary_state.rb +16 -19
  51. data/lib/julewire/core/execution/view.rb +8 -17
  52. data/lib/julewire/core/facade_methods.rb +67 -65
  53. data/lib/julewire/core/fields/attribute_keys.rb +0 -2
  54. data/lib/julewire/core/fields/bags.rb +23 -27
  55. data/lib/julewire/core/fields/field_set.rb +29 -19
  56. data/lib/julewire/core/fields/field_stack.rb +68 -134
  57. data/lib/julewire/core/fields/internal/deletion.rb +6 -9
  58. data/lib/julewire/core/fields/internal.rb +19 -38
  59. data/lib/julewire/core/fields/lookup.rb +9 -13
  60. data/lib/julewire/core/fields/section_proxy.rb +1 -9
  61. data/lib/julewire/core/fields/stack_set.rb +7 -12
  62. data/lib/julewire/core/fields/static_labels.rb +1 -1
  63. data/lib/julewire/core/integration/configurable.rb +4 -3
  64. data/lib/julewire/core/integration/destination_health.rb +17 -7
  65. data/lib/julewire/core/integration/event_subscriber.rb +1 -1
  66. data/lib/julewire/core/integration/facade.rb +12 -15
  67. data/lib/julewire/core/integration/fork_hooks.rb +12 -36
  68. data/lib/julewire/core/integration/health.rb +0 -3
  69. data/lib/julewire/core/integration/ivar_state.rb +0 -5
  70. data/lib/julewire/core/integration/protocol.rb +20 -0
  71. data/lib/julewire/core/integration/settings.rb +4 -4
  72. data/lib/julewire/core/integration/subscriber_install.rb +2 -2
  73. data/lib/julewire/core/integration/subscription.rb +3 -2
  74. data/lib/julewire/core/integration/values.rb +0 -17
  75. data/lib/julewire/core/local_storage.rb +3 -19
  76. data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
  77. data/lib/julewire/core/processing/level_threshold.rb +7 -14
  78. data/lib/julewire/core/processing/match.rb +6 -4
  79. data/lib/julewire/core/processing/pipeline.rb +46 -40
  80. data/lib/julewire/core/processing/processor_chain.rb +22 -11
  81. data/lib/julewire/core/processing/processor_registry.rb +3 -4
  82. data/lib/julewire/core/processing/record_field_transform.rb +0 -4
  83. data/lib/julewire/core/processing/sampling.rb +1 -3
  84. data/lib/julewire/core/processing.rb +2 -2
  85. data/lib/julewire/core/propagation/carrier.rb +14 -14
  86. data/lib/julewire/core/propagation.rb +15 -2
  87. data/lib/julewire/core/records/build_input.rb +75 -0
  88. data/lib/julewire/core/records/console_formatter.rb +6 -6
  89. data/lib/julewire/core/records/deconstruct.rb +1 -1
  90. data/lib/julewire/core/records/display_message.rb +2 -4
  91. data/lib/julewire/core/records/draft.rb +125 -262
  92. data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
  93. data/lib/julewire/core/records/metadata.rb +4 -3
  94. data/lib/julewire/core/records/public_projection.rb +2 -2
  95. data/lib/julewire/core/records/raw_input.rb +1 -1
  96. data/lib/julewire/core/records/record.rb +19 -37
  97. data/lib/julewire/core/records/severity.rb +7 -15
  98. data/lib/julewire/core/runtime.rb +67 -71
  99. data/lib/julewire/core/runtime_registry.rb +20 -15
  100. data/lib/julewire/core/runtime_state.rb +5 -3
  101. data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
  102. data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
  103. data/lib/julewire/core/serialization/bounded_transform.rb +2 -12
  104. data/lib/julewire/core/serialization/bounded_traversal.rb +49 -66
  105. data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
  106. data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
  107. data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
  108. data/lib/julewire/core/serialization/exception_shape.rb +7 -6
  109. data/lib/julewire/core/serialization/json_encoder.rb +10 -13
  110. data/lib/julewire/core/serialization/serializer.rb +23 -50
  111. data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
  112. data/lib/julewire/core/serialization/text_encoder.rb +5 -5
  113. data/lib/julewire/core/serialization/truncation_metadata.rb +63 -48
  114. data/lib/julewire/core/serialization/value_copy.rb +60 -227
  115. data/lib/julewire/core/serialization/value_traversal.rb +10 -127
  116. data/lib/julewire/core/testing.rb +6 -56
  117. data/lib/julewire/core/validation.rb +2 -3
  118. data/lib/julewire/core/version.rb +1 -1
  119. data/lib/julewire/core.rb +2 -7
  120. metadata +6 -19
  121. data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
  122. data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
  123. data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
  124. data/lib/julewire/core/testing/chaos/destination.rb +0 -55
  125. data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
  126. data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
  127. data/lib/julewire/core/testing/chaos.rb +0 -80
  128. data/lib/julewire/core/testing/contracts/component.rb +0 -162
  129. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
  130. data/lib/julewire/core/testing/contracts/integration.rb +0 -166
  131. data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
  132. data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
  133. data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
  134. data/lib/julewire/core/testing/contracts/wire.rb +0 -60
  135. data/lib/julewire/core/testing/contracts.rb +0 -24
  136. data/lib/julewire/core/testing/coverage.rb +0 -58
  137. data/lib/julewire/core/testing/test_reports.rb +0 -78
@@ -12,12 +12,6 @@ module Julewire
12
12
  Validation.validate_integer_limit!(value, name: name)
13
13
  end
14
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
15
  def finish_hash(result, fields)
22
16
  add_truncation_metadata!(result, fields)
23
17
  finish_container(result, fields)
@@ -38,7 +32,7 @@ module Julewire
38
32
 
39
33
  def finish_container(result, fields)
40
34
  value = freeze_container(result)
41
- fields ? mark_truncated(value) : clear_truncated(value)
35
+ fields ? mark_truncated(value) : value
42
36
  end
43
37
 
44
38
  def truncation_metadata(fields)
@@ -59,11 +53,6 @@ module Julewire
59
53
  value
60
54
  end
61
55
 
62
- def clear_truncated(value)
63
- @last_truncated = false
64
- value
65
- end
66
-
67
56
  def consume_truncated
68
57
  truncated = @last_truncated
69
58
  @last_truncated = false
@@ -76,97 +65,6 @@ module Julewire
76
65
  end
77
66
  private_constant :ValueCopyTruncation
78
67
 
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
-
170
68
  class ValueCopy
171
69
  include ValueTraversal
172
70
  include ValueCopyTruncation
@@ -174,87 +72,37 @@ module Julewire
174
72
  CIRCULAR_REFERENCE = Core::CIRCULAR_REFERENCE
175
73
  EMPTY_ARRAY = [].freeze
176
74
  EMPTY_HASH = {}.freeze
177
- RESERVED_KEYS = [Serializer::TRUNCATION_METADATA_KEY, Serializer::TRUNCATION_METADATA_KEY.to_sym].freeze
178
- private_constant :EMPTY_ARRAY, :EMPTY_HASH, :RESERVED_KEYS
75
+ private_constant :EMPTY_ARRAY, :EMPTY_HASH
179
76
 
180
77
  class << self
181
- include ValueCopyCache
182
-
183
78
  def call( # rubocop:disable Metrics/ParameterLists
184
79
  value,
185
80
  compact_empty: false,
186
81
  freeze_values: false,
187
82
  max_array_items: nil,
188
- max_depth: Core::NORMALIZATION_MAX_DEPTH,
83
+ max_depth: NORMALIZATION_MAX_DEPTH,
189
84
  max_hash_keys: nil,
190
85
  max_string_bytes: nil,
191
86
  preserve_truncation_metadata: false,
192
87
  symbolize_keys: false
193
88
  )
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
196
-
197
- copy_with(
198
- cached_copier(
199
- compact_empty: compact_empty,
200
- freeze_values: freeze_values,
201
- max_array_items: max_array_items,
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,
206
- symbolize_keys: symbolize_keys
207
- ),
208
- value
209
- )
210
- end
211
-
212
- def omitted_empty?(value)
213
- value.nil? || (value.is_a?(Hash) && value.empty?) || (value.is_a?(Array) && value.empty?)
214
- end
215
-
216
- private
217
-
218
- def container?(value) = value.is_a?(Hash) || value.is_a?(Array)
219
-
220
- def copy_with(copier, value)
221
- return copier.call_reusable(value) unless copier.in_use?
222
-
223
89
  new(
224
- compact_empty: copier.compact_empty,
225
- freeze_values: copier.freeze_values,
226
- max_array_items: copier.max_array_items,
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,
231
- symbolize_keys: copier.symbolize_keys
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:
232
98
  ).call(value)
233
99
  end
234
100
 
235
- def copy_leaf(value, freeze_values:)
236
- return copy_string(value, freeze_values: freeze_values) if value.is_a?(String)
237
- return copy_time(value, freeze_values: freeze_values) if value.is_a?(Time)
238
-
239
- value
240
- end
241
-
242
- def copy_string(value, freeze_values:)
243
- copy = value.frozen? ? value : value.dup
244
- freeze_values ? copy.freeze : copy
245
- end
246
-
247
- def copy_time(value, freeze_values:)
248
- return value unless freeze_values
249
- return value if value.frozen?
250
-
251
- value.dup.freeze
101
+ def omitted_empty?(value)
102
+ value.nil? || (value.is_a?(Hash) && value.empty?) || (value.is_a?(Array) && value.empty?)
252
103
  end
253
104
  end
254
105
 
255
- attr_reader :compact_empty, :freeze_values, :max_array_items, :max_depth, :max_hash_keys, :max_string_bytes,
256
- :preserve_truncation_metadata, :symbolize_keys
257
-
258
106
  def initialize(compact_empty:, freeze_values:, max_array_items:, max_depth:, max_hash_keys:, max_string_bytes:,
259
107
  preserve_truncation_metadata:, symbolize_keys:)
260
108
  @compact_empty = compact_empty
@@ -265,27 +113,13 @@ module Julewire
265
113
  @max_string_bytes = validate_optional_limit(max_string_bytes, name: :max_string_bytes)
266
114
  @preserve_truncation_metadata = preserve_truncation_metadata
267
115
  @symbolize_keys = symbolize_keys
268
- @in_use = false
269
- @last_truncated = false
270
- @track_truncation = !!(@max_array_items || @max_hash_keys || @max_string_bytes)
116
+ @track_truncation = @max_array_items || @max_hash_keys || @max_string_bytes
271
117
  end
272
118
 
273
119
  def call(value)
274
- @last_truncated = false
275
120
  traverse(value) { |root, depth| copy_value(root, depth) }
276
- ensure
277
- @last_truncated = false
278
- end
279
-
280
- def call_reusable(value)
281
- @in_use = true
282
- call(value)
283
- ensure
284
- @in_use = false
285
121
  end
286
122
 
287
- def in_use? = @in_use
288
-
289
123
  private
290
124
 
291
125
  def copy_value(value, depth)
@@ -297,7 +131,7 @@ module Julewire
297
131
  end
298
132
 
299
133
  def copy_container(value, depth)
300
- return mark_truncated(copy_string(Serializer::MAX_DEPTH_VALUE)) if depth_limited?(depth)
134
+ return mark_truncated(Serializer::MAX_DEPTH_VALUE) if depth == @max_depth
301
135
  return frozen_empty_container(value) if @freeze_values && value.empty?
302
136
 
303
137
  with_traversal_container(value, CIRCULAR_REFERENCE) do
@@ -305,10 +139,6 @@ module Julewire
305
139
  end
306
140
  end
307
141
 
308
- def depth_limited?(depth)
309
- @max_depth && depth >= @max_depth
310
- end
311
-
312
142
  def frozen_empty_container(value)
313
143
  value.is_a?(Hash) ? EMPTY_HASH : EMPTY_ARRAY
314
144
  end
@@ -318,13 +148,12 @@ module Julewire
318
148
  result = {}
319
149
  visited = 0
320
150
  value.each do |key, item|
321
- if hash_limit_reached?(visited)
151
+ if visited == @max_hash_keys
322
152
  fields = append_truncation_field(fields, "hash_keys")
323
153
  break
324
154
  end
325
155
 
326
156
  visited += 1
327
- # Raw-empty values still spend work budget. The limit protects traversal work, not output size.
328
157
  next if @compact_empty && self.class.omitted_empty?(item)
329
158
 
330
159
  fields = copy_hash_entry(result, fields, key, item, depth)
@@ -332,32 +161,31 @@ module Julewire
332
161
  finish_hash(result, fields)
333
162
  end
334
163
 
335
- def hash_limit_reached?(visited)
336
- @max_hash_keys && visited >= @max_hash_keys
337
- end
338
-
339
164
  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)
165
+ return copy_truncation_metadata_entry(result, fields, key, item) if reserved_truncation_key?(key)
341
166
 
167
+ validate_symbolized_key_shape!(key)
342
168
  copied = copy_value(item, depth + 1)
343
- child_truncated = consume_truncated
344
169
  return fields if @compact_empty && self.class.omitted_empty?(copied)
345
170
 
346
- copied_key = copy_key(key)
347
- key_truncated = consume_truncated
171
+ copied_key = copied_key_value(key)
172
+ value_or_key_truncated = consume_truncated
348
173
  result[copied_key] = copied
349
- record_hash_truncation(fields, copied_key, key_truncated || child_truncated)
174
+ fields = append_truncation_field(fields, copied_key.to_s) if value_or_key_truncated
175
+ fields
350
176
  end
351
177
 
352
- def copy_truncation_metadata_entry(result, fields, key, item, depth)
178
+ def copy_truncation_metadata_entry(result, fields, key, item)
353
179
  unless @preserve_truncation_metadata &&
354
180
  allowed_truncation_metadata_key?(key) &&
355
181
  TruncationMetadata.valid?(item, max_fields: truncation_metadata_field_limit)
356
- raise_reserved_key!(key)
182
+ raise_reserved_key!
357
183
  end
358
184
 
359
- result[copy_truncation_metadata_key(key)] = copy_value(item, depth + 1)
360
- consume_truncated
185
+ result[copy_truncation_metadata_key(key)] = TruncationMetadata.copy(
186
+ item,
187
+ freeze_values: @freeze_values
188
+ )
361
189
  fields
362
190
  end
363
191
 
@@ -366,7 +194,7 @@ module Julewire
366
194
  end
367
195
 
368
196
  def allowed_truncation_metadata_key?(key)
369
- key.is_a?(Symbol) || @symbolize_keys
197
+ key.instance_of?(Symbol) || @symbolize_keys
370
198
  end
371
199
 
372
200
  def reserved_truncation_key?(key)
@@ -374,64 +202,69 @@ module Julewire
374
202
  end
375
203
 
376
204
  def copy_truncation_metadata_key(key)
377
- @symbolize_keys && key.is_a?(String) ? key.to_sym : 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
378
213
  end
379
214
 
380
215
  def copy_array(value, depth)
381
- fields = nil
382
216
  result = []
383
217
  visited = 0
218
+ array_items_truncated = false
219
+ array_item_values_truncated = false
384
220
  value.each do |item|
385
- if array_limit_reached?(visited)
386
- fields = append_truncation_field(fields, "array_items")
221
+ if visited == @max_array_items
222
+ array_items_truncated = true
387
223
  break
388
224
  end
389
225
 
390
226
  visited += 1
391
227
  next if @compact_empty && self.class.omitted_empty?(item)
392
228
 
393
- fields = copy_array_item(result, fields, item, depth)
229
+ copied = copy_value(item, depth + 1)
230
+ child_truncated = consume_truncated
231
+ next if @compact_empty && self.class.omitted_empty?(copied)
232
+
233
+ result << copied
234
+ array_item_values_truncated ||= child_truncated
394
235
  end
395
- finish_array(result, fields)
396
- end
397
236
 
398
- def array_limit_reached?(visited)
399
- @max_array_items && visited >= @max_array_items
237
+ finish_array(result, array_truncation_fields(array_items_truncated, array_item_values_truncated))
400
238
  end
401
239
 
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)
240
+ def array_truncation_fields(array_items_truncated, array_item_values_truncated)
241
+ return unless array_items_truncated || array_item_values_truncated
406
242
 
407
- result << copied
408
- child_truncated ? append_truncation_field(fields, "array_item_values") : fields
243
+ fields = []
244
+ fields << "array_item_values" if array_item_values_truncated
245
+ fields << "array_items" if array_items_truncated
246
+ fields
409
247
  end
410
248
 
411
- def copy_key(key)
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)
249
+ def copied_key_value(key)
250
+ return key unless key.is_a?(String)
415
251
 
416
- copied
252
+ copy = copy_string(key)
253
+ @symbolize_keys ? copy.to_sym : copy
417
254
  end
418
255
 
419
- def raise_reserved_key!(key)
420
- return unless RESERVED_KEYS.include?(key)
421
-
256
+ def raise_reserved_key!
422
257
  raise ArgumentError, "#{Serializer::TRUNCATION_METADATA_KEY} is reserved for Julewire truncation metadata"
423
258
  end
424
259
 
425
260
  def copy_string(value)
426
- return value unless value.is_a?(String)
427
-
428
261
  if @max_string_bytes && value.bytesize > @max_string_bytes
429
262
  copy = "#{value.byteslice(0, @max_string_bytes).scrub("?")}#{Serializer::TRUNCATED_SUFFIX}"
430
263
  return mark_truncated(freeze_container(copy))
431
264
  end
432
265
 
433
266
  copy = value.frozen? ? value : value.dup
434
- clear_truncated(freeze_container(copy))
267
+ freeze_container(copy)
435
268
  end
436
269
 
437
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
- added = false
31
- seen = @traversal_seen
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
- added = mark_traversal_container(value, seen, first_seen, second_seen, third_seen, fourth_seen)
40
- yield
41
- ensure
42
- unmark_traversal_container(value, added)
43
- end
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 clear_traversal_slots!
140
- @traversal_first_seen = nil
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
- self
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: @records.size } }
26
+ { status: :ok, counts: { captured: records.size } }
32
27
  end
33
28
 
34
29
  def clear
35
- @records.clear
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
- @writes << value
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 = Julewire, **)
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"