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
@@ -11,10 +11,8 @@ module Julewire
11
11
  no_output_dropped
12
12
  processor_dropped
13
13
  processor_error
14
+ processor_invalid
14
15
  ].freeze
15
- EMPTY_HASH = {}.freeze
16
- private_constant :EMPTY_HASH
17
-
18
16
  # @api integration_spi
19
17
  # Integration-facing raw/normalized record pipeline.
20
18
  def initialize(configuration:, invalid_severity_reporter: Diagnostics::InvalidSeverityReporter.counter)
@@ -32,11 +30,11 @@ module Julewire
32
30
  initialize_tracking
33
31
  end
34
32
 
35
- def emit(input = Core::UNSET, **fields, &)
33
+ def emit(input = UNSET, **fields, &)
36
34
  emit_with_level_check(input, true, fields, &)
37
35
  end
38
36
 
39
- def emit_without_level(input = Core::UNSET, **fields, &)
37
+ def emit_without_level(input = UNSET, **fields, &)
40
38
  emit_with_level_check(input, false, fields, &)
41
39
  end
42
40
 
@@ -50,7 +48,7 @@ module Julewire
50
48
  # Runtime summaries already carry their captured scope fields.
51
49
  def emit_isolated_input(input, enforce_level: true)
52
50
  emit_input_with_guard(input, enforce_level: enforce_level, lazy: false) do
53
- build_isolated_draft(input)
51
+ build_isolated_owned_draft(input)
54
52
  end
55
53
  end
56
54
 
@@ -109,7 +107,6 @@ module Julewire
109
107
  {
110
108
  encoder: Serialization::JsonEncoder.new(max_backtrace_lines: configuration.error_backtrace_lines),
111
109
  formatter: Records::Formatter.new,
112
- error_backtrace_lines: configuration.error_backtrace_lines,
113
110
  on_drop: configuration.on_drop,
114
111
  on_failure: configuration.on_failure
115
112
  }
@@ -124,9 +121,10 @@ module Julewire
124
121
 
125
122
  def build_processor_chain(configuration)
126
123
  ProcessorChain.new(
127
- processors: configuration.processors.to_a.freeze,
128
- error_backtrace_lines: @error_backtrace_lines,
129
- on_error: method(:record_processor_error)
124
+ processors: configuration.processors.to_a,
125
+ on_error: method(:record_processor_error),
126
+ on_invalid: method(:record_invalid_processor_result),
127
+ on_invalid_draft: method(:record_invalid_processor_draft)
130
128
  )
131
129
  end
132
130
 
@@ -146,7 +144,7 @@ module Julewire
146
144
  if raw_input_blocked?(input, enforce_level: enforce_level, lazy: lazy)
147
145
  increment_pipeline_counter(:level_dropped)
148
146
  else
149
- emit_prepared_draft(yield, enforce_level: enforce_level, merge_static_labels: false)
147
+ emit_prepared_draft(yield, enforce_level: enforce_level)
150
148
  end
151
149
  finish_emit_attempt(degradation_marker)
152
150
  rescue StandardError => e
@@ -159,7 +157,6 @@ module Julewire
159
157
  return false unless @destinations.empty?
160
158
 
161
159
  record_no_output_drop
162
- true
163
160
  end
164
161
 
165
162
  def finish_emit_attempt(degradation_marker)
@@ -171,9 +168,7 @@ module Julewire
171
168
  Records::Record.validate_normalized!(record)
172
169
  return emit_fast_record(record, enforce_level: enforce_level) if fast_record_path?
173
170
 
174
- emit_prepared_draft(Records::Draft.from_record(record, freeze_sections: false),
175
- enforce_level: enforce_level)
176
- nil
171
+ emit_prepared_draft(Records::Draft.from_record(record), enforce_level: enforce_level)
177
172
  end
178
173
 
179
174
  def fast_record_path?
@@ -188,7 +183,6 @@ module Julewire
188
183
 
189
184
  increment_pipeline_counter(:entered)
190
185
  emit_to_destinations(record)
191
- nil
192
186
  end
193
187
 
194
188
  def raw_input_blocked?(input, enforce_level:, lazy:)
@@ -210,14 +204,14 @@ module Julewire
210
204
  )
211
205
  end
212
206
 
213
- def build_isolated_draft(input, input_owned: false)
207
+ def build_isolated_owned_draft(input)
214
208
  build_draft_from(
215
209
  input,
216
- input_owned: input_owned,
217
- context: EMPTY_HASH,
218
- neutral: EMPTY_HASH,
219
- attributes: EMPTY_HASH,
220
- carry: EMPTY_HASH,
210
+ input_owned: true,
211
+ context: nil,
212
+ neutral: nil,
213
+ attributes: nil,
214
+ carry: nil,
221
215
  scope: nil
222
216
  )
223
217
  end
@@ -229,9 +223,7 @@ module Julewire
229
223
  neutral: neutral,
230
224
  attributes: attributes,
231
225
  carry: carry,
232
- static_labels: @labels,
233
226
  input_owned: input_owned,
234
- freeze_sections: @processors_empty,
235
227
  scope: scope,
236
228
  error_backtrace_lines: @error_backtrace_lines,
237
229
  invalid_severity_reporter: @invalid_severity_reporter
@@ -241,7 +233,6 @@ module Julewire
241
233
  def initialize_tracking
242
234
  @health = Diagnostics::Health.new(
243
235
  counter_keys: COUNTER_KEYS,
244
- callback_metadata: {},
245
236
  callback_failure_counter: :callback_error
246
237
  )
247
238
  end
@@ -267,13 +258,13 @@ module Julewire
267
258
  @threshold.allow?(record_or_draft.fetch(:severity))
268
259
  end
269
260
 
270
- def emit_prepared_draft(draft, enforce_level:, merge_static_labels: true)
261
+ def emit_prepared_draft(draft, enforce_level:)
271
262
  if enforce_level && !emit_record?(draft)
272
263
  increment_pipeline_counter(:level_dropped)
273
264
  return
274
265
  end
275
266
 
276
- draft = merge_static_labels(draft) if merge_static_labels
267
+ draft = merge_static_labels(draft)
277
268
  increment_pipeline_counter(:entered)
278
269
  emit_processed_draft(draft, enforce_level: enforce_level)
279
270
  rescue StandardError => e
@@ -289,9 +280,12 @@ module Julewire
289
280
  return
290
281
  end
291
282
 
292
- processed, enforce_processed_level = processed_draft_and_level(processed, enforce_level)
283
+ if processed.instance_of?(ProcessorChain::ErrorResult)
284
+ processed = processed.draft
285
+ enforce_level = false
286
+ end
293
287
 
294
- if enforce_processed_level && !emit_record?(processed)
288
+ if enforce_level && !emit_record?(processed)
295
289
  increment_pipeline_counter(:level_dropped)
296
290
  return
297
291
  end
@@ -299,27 +293,40 @@ module Julewire
299
293
  emit_to_destinations(processed.to_record)
300
294
  end
301
295
 
302
- def processed_draft_and_level(processed, default_enforce_level)
303
- if processed.is_a?(ProcessorChain::ErrorResult)
304
- [processed.draft, false]
305
- else
306
- [processed, default_enforce_level]
307
- end
308
- end
309
-
310
296
  def record_processor_error(error, record_metadata)
311
297
  increment_pipeline_counter(:processor_error)
312
298
  notify_failure(error, phase: :processor, record_metadata: record_metadata)
313
299
  end
314
300
 
301
+ def record_invalid_processor_result(processor_name, result, record_metadata)
302
+ increment_pipeline_counter(:processor_invalid)
303
+ error, metadata = InvalidResultFailure.build(
304
+ message: "processor returned unsupported result",
305
+ phase: :processor_result,
306
+ processor_name: processor_name,
307
+ record_metadata: record_metadata,
308
+ result: result
309
+ )
310
+ notify_failure(error, **metadata)
311
+ end
312
+
313
+ def record_invalid_processor_draft(processor_name, error, record_metadata)
314
+ increment_pipeline_counter(:processor_invalid)
315
+ notify_failure(
316
+ error,
317
+ phase: :processor_data,
318
+ processor: processor_name,
319
+ record_metadata: record_metadata
320
+ )
321
+ end
322
+
315
323
  def emit_internal_error_record(error)
316
324
  emit_prepared_draft(
317
- Diagnostics::InternalRecords.emit_error(error, error_backtrace_lines: @error_backtrace_lines),
325
+ Diagnostics::InternalRecords.emit_error(error),
318
326
  enforce_level: false
319
327
  )
320
328
  rescue StandardError => e
321
329
  notify_failure(e, phase: :internal_error_record)
322
- nil
323
330
  end
324
331
 
325
332
  def emit_to_destinations(record)
@@ -328,7 +335,6 @@ module Julewire
328
335
 
329
336
  def record_no_output_drop
330
337
  increment_pipeline_counter(:no_output_dropped)
331
- nil
332
338
  end
333
339
 
334
340
  def notify_failure(error, **metadata)
@@ -7,10 +7,11 @@ module Julewire
7
7
  DROP = Core.sentinel(:drop)
8
8
  ErrorResult = Data.define(:draft)
9
9
 
10
- def initialize(processors:, error_backtrace_lines:, on_error:)
10
+ def initialize(processors:, on_error:, on_invalid:, on_invalid_draft:)
11
11
  @processors = processors
12
- @error_backtrace_lines = error_backtrace_lines
13
12
  @on_error = on_error
13
+ @on_invalid = on_invalid
14
+ @on_invalid_draft = on_invalid_draft
14
15
  end
15
16
 
16
17
  def empty? = @processors.empty?
@@ -19,13 +20,15 @@ module Julewire
19
20
  current = draft
20
21
 
21
22
  @processors.each do |processor|
22
- current = apply_processor_result(current, processor.call(current))
23
- break if current == :drop
23
+ result = processor.call(current)
24
+ current = apply_processor_result(current, processor, result)
25
+ return DROP if current.equal?(DROP)
26
+ return DROP unless valid_processor_draft?(processor, current)
24
27
  rescue StandardError => e
25
28
  action = handle_processor_error(processor, e, current)
26
29
  case action
27
30
  when :continue
28
- next
31
+ return DROP unless valid_processor_draft?(processor, current)
29
32
  when :drop
30
33
  return DROP
31
34
  else
@@ -33,15 +36,17 @@ module Julewire
33
36
  end
34
37
  end
35
38
 
36
- current == :drop ? DROP : current
39
+ current
37
40
  end
38
41
 
39
42
  private
40
43
 
41
- def apply_processor_result(current, result)
42
- return :drop if result == :drop
43
- return result if result.is_a?(Records::Draft)
44
+ def apply_processor_result(current, processor, result)
45
+ return DROP if result == :drop
46
+ return current if result.nil?
47
+ return result if result.instance_of?(Records::Draft)
44
48
 
49
+ @on_invalid.call(processor.processor_name, result, Records::Metadata.call(current))
45
50
  current
46
51
  end
47
52
 
@@ -55,12 +60,18 @@ module Julewire
55
60
  ErrorResult.new(processor_error_record(processor, error, record_metadata))
56
61
  end
57
62
 
63
+ def valid_processor_draft?(processor, draft)
64
+ draft.validate!
65
+ rescue StandardError => e
66
+ @on_invalid_draft.call(processor.processor_name, e, Records::Metadata.call(draft))
67
+ false
68
+ end
69
+
58
70
  def processor_error_record(processor, error, record_metadata)
59
71
  Diagnostics::InternalRecords.processor_error(
60
72
  processor_name: processor.processor_name,
61
73
  error: error,
62
- record_metadata: record_metadata,
63
- error_backtrace_lines: @error_backtrace_lines
74
+ record_metadata: record_metadata
64
75
  )
65
76
  end
66
77
  end
@@ -49,7 +49,6 @@ module Julewire
49
49
 
50
50
  entry = build_entry(processor, arguments, options)
51
51
  prepend ? @entries.unshift(entry) : @entries << entry
52
- self
53
52
  end
54
53
 
55
54
  def build_entry(processor, arguments = [], options = {})
@@ -66,7 +65,7 @@ module Julewire
66
65
  on_error: on_error,
67
66
  factory: true
68
67
  )
69
- elsif processor.is_a?(Class)
68
+ elsif processor.instance_of?(Class)
70
69
  Entry.new(
71
70
  entry: processor,
72
71
  arguments: arguments.dup.freeze,
@@ -81,13 +80,13 @@ module Julewire
81
80
  end
82
81
 
83
82
  def normalize_entry(entry)
84
- entry.is_a?(Entry) ? entry : build_entry(entry)
83
+ entry.instance_of?(Entry) ? entry : build_entry(entry)
85
84
  end
86
85
 
87
86
  def materialize(entry)
88
87
  processor = if entry.factory
89
88
  Processing.build(entry.entry, *entry.arguments, **entry.options)
90
- elsif entry.entry.is_a?(Class)
89
+ elsif entry.entry.instance_of?(Class)
91
90
  entry.entry.new(*entry.arguments, **entry.options)
92
91
  else
93
92
  entry.entry
@@ -14,10 +14,6 @@ module Julewire
14
14
  private_constant :SCALAR_KEY_SET
15
15
 
16
16
  class << self
17
- def container_keys = CONTAINER_KEYS
18
-
19
- def scalar_keys = SCALAR_KEYS
20
-
21
17
  def container_key?(key) = CONTAINER_KEY_SET.key?(key)
22
18
 
23
19
  def scalar_key?(key) = SCALAR_KEY_SET.key?(key)
@@ -77,7 +77,7 @@ module Julewire
77
77
  return :drop if value.nil?
78
78
  return if @threshold == HASH_SPACE
79
79
 
80
- Sampling.stable_hash(value) < @threshold ? nil : :drop
80
+ :drop unless Sampling.stable_hash(value) < @threshold
81
81
  end
82
82
 
83
83
  private
@@ -98,8 +98,6 @@ module Julewire
98
98
  end
99
99
 
100
100
  def field_value(hash, key)
101
- return unless hash.is_a?(Hash)
102
-
103
101
  Fields::FieldSet.value_for(hash, key)
104
102
  end
105
103
  end
@@ -10,7 +10,6 @@ module Julewire
10
10
  raise ArgumentError, "processor factory block required" unless factory
11
11
 
12
12
  @factories[normalize_kind(kind)] = factory
13
- nil
14
13
  end
15
14
 
16
15
  def build(kind, ...)
@@ -31,7 +30,8 @@ module Julewire
31
30
  raise ArgumentError, "processor kind must respond to #to_sym" unless kind.respond_to?(:to_sym)
32
31
 
33
32
  name = kind.to_sym
34
- raise ArgumentError, "processor kind cannot be empty" if name.name.empty?
33
+ raise ArgumentError, "processor kind #to_sym must return a Symbol" unless name.instance_of?(Symbol)
34
+ raise ArgumentError, "processor kind cannot be empty" if name.empty?
35
35
 
36
36
  name
37
37
  end
@@ -10,6 +10,8 @@ module Julewire
10
10
  DEFAULT_KEY = "julewire"
11
11
  DEFAULT_MAX_BYTES = 65_536
12
12
  DEFAULT_ENVELOPE = Core.sentinel(:default_envelope)
13
+
14
+ # @api integration_spi
13
15
  class Extracted
14
16
  attr_reader :envelope, :status, :reason, :error
15
17
 
@@ -23,7 +25,6 @@ module Julewire
23
25
  def failure? = !error.nil?
24
26
  end
25
27
  private_constant :DEFAULT_ENVELOPE
26
- private_constant :Extracted
27
28
 
28
29
  # @api integration_spi
29
30
  class ExtractionError < StandardError
@@ -40,7 +41,7 @@ module Julewire
40
41
  def encode(envelope: DEFAULT_ENVELOPE, max_bytes: nil)
41
42
  Validation.validate_byte_limit!(max_bytes, name: :max_bytes)
42
43
 
43
- encoded = JSON.generate(serialized_envelope(envelope), allow_nan: false)
44
+ encoded = JSON.generate(serialized_envelope(envelope))
44
45
  return if max_bytes && encoded.bytesize > max_bytes
45
46
 
46
47
  encoded
@@ -49,14 +50,14 @@ module Julewire
49
50
  def inject(carrier = {}, envelope: DEFAULT_ENVELOPE, key: DEFAULT_KEY, max_bytes: nil)
50
51
  validate_carrier!(carrier)
51
52
  encoded = encode(envelope: envelope, max_bytes: max_bytes)
52
- clear_carrier_key!(carrier, key) unless encoded
53
+ clear_carrier_key!(carrier, key)
53
54
  return unless encoded
54
55
 
55
56
  carrier[key.to_s] = encoded
56
57
  carrier
57
58
  end
58
59
 
59
- def extract(carrier, key: DEFAULT_KEY, max_bytes: DEFAULT_MAX_BYTES)
60
+ def extract_envelope(carrier, key: DEFAULT_KEY, max_bytes: DEFAULT_MAX_BYTES)
60
61
  extract_result(carrier, key: key, max_bytes: max_bytes).envelope
61
62
  end
62
63
 
@@ -90,7 +91,10 @@ module Julewire
90
91
  end
91
92
 
92
93
  parsed = JSON.parse(string)
93
- return extracted_failure(:non_hash, "carrier payload must be a JSON object") unless parsed.is_a?(Hash)
94
+ unless parsed.instance_of?(Hash)
95
+ return extracted_failure(:non_hash,
96
+ "carrier payload must be a JSON object")
97
+ end
94
98
 
95
99
  extracted(Fields::FieldSet.deep_symbolize_owned_keys(parsed), :ok)
96
100
  rescue StandardError => e
@@ -103,7 +107,7 @@ module Julewire
103
107
 
104
108
  def extracted_failure(status, reason, cause = nil)
105
109
  error = ExtractionError.new(status, reason)
106
- error.set_backtrace(cause.backtrace) if cause&.backtrace
110
+ error.set_backtrace(cause.backtrace) if cause
107
111
  Extracted.new(envelope: {}, status: status, reason: reason, error: error)
108
112
  end
109
113
 
@@ -122,14 +126,10 @@ module Julewire
122
126
  def clear_carrier_key!(carrier, key)
123
127
  string_key = key.to_s
124
128
  symbol_key = Fields::Internal.normalize_key(key)
125
- if carrier.respond_to?(:delete)
126
- begin
127
- carrier.delete(string_key)
128
- carrier.delete(symbol_key)
129
- rescue StandardError
130
- clear_carrier_key_by_assignment(carrier, string_key, symbol_key)
131
- end
132
- else
129
+ begin
130
+ carrier.delete(string_key)
131
+ carrier.delete(symbol_key)
132
+ rescue StandardError
133
133
  clear_carrier_key_by_assignment(carrier, string_key, symbol_key)
134
134
  end
135
135
  end
@@ -19,8 +19,11 @@ module Julewire
19
19
  def restore(envelope, link_executions: false, owned: false, &)
20
20
  raise ArgumentError, "block required" unless block_given?
21
21
 
22
- sections = FIELD_SECTIONS.to_h { |section| [section, hash_value(envelope, section)] }
23
- execution = hash_value(envelope, :execution)
22
+ Integration::Protocol.validate_symbol_hash(envelope) if owned
23
+ sections = FIELD_SECTIONS.to_h do |section|
24
+ [section, restore_hash_value(envelope, section, owned: owned)]
25
+ end
26
+ execution = restore_hash_value(envelope, :execution, owned: owned)
24
27
  ContextStore.current.with_propagation(
25
28
  **sections,
26
29
  execution: execution,
@@ -45,6 +48,16 @@ module Julewire
45
48
  value = Fields::FieldSet.value_for(hash, key)
46
49
  value.is_a?(Hash) ? value : {}
47
50
  end
51
+
52
+ def restore_hash_value(envelope, key, owned:)
53
+ return hash_value(envelope, key) unless owned
54
+ return {} unless envelope.key?(key)
55
+
56
+ value = envelope.fetch(key)
57
+ return value if value.is_a?(Hash)
58
+
59
+ raise TypeError, "propagation #{key} must be a Hash"
60
+ end
48
61
  end
49
62
  end
50
63
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Julewire
4
+ module Core
5
+ module Records
6
+ # Converts raw emit input into known record fields plus payload fields.
7
+ # @api bridge_spi
8
+ module BuildInput
9
+ RECORD_INPUT_KEYS = Record::REQUIRED_KEYS.freeze
10
+ RECORD_INPUT_KEY_SET = RECORD_INPUT_KEYS.to_h { [it, true] }.freeze
11
+ private_constant :RECORD_INPUT_KEYS, :RECORD_INPUT_KEY_SET
12
+
13
+ class << self
14
+ def normalize_public(input)
15
+ return {} if input.nil?
16
+ return normalize_public_hash(input) if RawInput.hash_input?(input)
17
+
18
+ { message: input.to_s }
19
+ end
20
+
21
+ def validate_owned(input)
22
+ raise TypeError, "owned record input must be a Hash" unless input.is_a?(Hash)
23
+
24
+ Serialization::DeepFreeze.validate_symbol_keys(input)
25
+ validate_known_keys!(input)
26
+ input
27
+ end
28
+
29
+ private
30
+
31
+ def validate_known_keys!(input)
32
+ unknown = input.each_key.reject { RECORD_INPUT_KEY_SET.key?(it) }
33
+ return if unknown.empty?
34
+
35
+ raise TypeError, "owned record input has unknown top-level keys: #{unknown.join(", ")}"
36
+ end
37
+
38
+ def normalize_public_hash(input)
39
+ normalized = {}
40
+ payload_fields = {}
41
+ input.each do |key, raw_value|
42
+ normalized_key = Fields::Internal.normalize_key(key)
43
+ value = raw_value.equal?(input) ? CIRCULAR_REFERENCE : raw_value
44
+ if RECORD_INPUT_KEY_SET.key?(normalized_key)
45
+ normalized[normalized_key] = value
46
+ else
47
+ payload_fields[normalized_key] = value
48
+ end
49
+ end
50
+ merge_unknown_payload!(normalized, payload_fields)
51
+ normalized
52
+ end
53
+
54
+ def merge_unknown_payload!(normalized, payload_fields)
55
+ return if payload_fields.empty?
56
+
57
+ normalized[:payload] = if normalized.key?(:payload)
58
+ merge_payload_input(normalized.fetch(:payload), payload_fields)
59
+ else
60
+ payload_fields
61
+ end
62
+ end
63
+
64
+ def merge_payload_input(explicit_payload, unknown_payload)
65
+ if explicit_payload.is_a?(Hash)
66
+ Fields::FieldSet.merge(unknown_payload, explicit_payload)
67
+ else
68
+ Fields::FieldSet.merge(unknown_payload, Fields::FieldSet::VALUE_KEY => explicit_payload)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -9,13 +9,13 @@ module Julewire
9
9
  Record.validate_normalized!(record)
10
10
 
11
11
  {
12
- event: record[:event],
13
- labels: record[:labels],
12
+ event: record.fetch(:event),
13
+ labels: record.fetch(:labels),
14
14
  message: DisplayMessage.call(record),
15
- payload: record[:payload],
16
- severity: record[:severity],
17
- source: record[:source],
18
- timestamp: record[:timestamp]
15
+ payload: record.fetch(:payload),
16
+ severity: record.fetch(:severity),
17
+ source: record.fetch(:source),
18
+ timestamp: record.fetch(:timestamp)
19
19
  }
20
20
  end
21
21
  end
@@ -9,7 +9,7 @@ module Julewire
9
9
  return to_h unless keys
10
10
 
11
11
  keys.each_with_object({}) do |key, selected|
12
- selected[key] = Fields::FieldSet.deep_dup(@data[key]) if @data.key?(key)
12
+ selected[key] = Fields::FieldSet.deep_dup(fetch(key)) if key?(key)
13
13
  end
14
14
  end
15
15
  end
@@ -118,9 +118,7 @@ module Julewire
118
118
 
119
119
  def duration_text(value)
120
120
  text = format("%.3f", value.round(3))
121
- text.delete_suffix!("0") while text.end_with?("0")
122
- text.delete_suffix!(".")
123
- text
121
+ text.sub(/(?:\.0+|(\.\d+?)0+)\z/, "\\1")
124
122
  end
125
123
 
126
124
  def job_queue(neutral)
@@ -153,7 +151,7 @@ module Julewire
153
151
  end
154
152
 
155
153
  def value_at(value, key)
156
- Fields::Lookup.value(value, key)
154
+ Fields::Lookup.wire_value(value, key)
157
155
  end
158
156
 
159
157
  def blank?(value)