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
@@ -8,25 +8,21 @@ module Julewire
8
8
  def value(source, key)
9
9
  return unless source.respond_to?(:[])
10
10
 
11
- direct = source[key]
12
- return direct unless direct.nil?
13
-
14
- alternate_key(source, key)
11
+ source[key]
15
12
  rescue StandardError
16
13
  nil
17
14
  end
18
15
 
19
- def blank?(value)
20
- value.nil? || (value.respond_to?(:empty?) && value.empty?)
16
+ # Decoded wire records have String keys, while in-process records use
17
+ # Symbols. Keep this conversion at presentation boundaries instead of
18
+ # making ordinary core lookups tolerant.
19
+ def wire_value(source, key)
20
+ result = value(source, key)
21
+ result.nil? ? value(source, key.to_s) : result
21
22
  end
22
23
 
23
- private
24
-
25
- def alternate_key(source, key)
26
- case key
27
- when Symbol then source[key.name]
28
- when String then source[key.to_sym]
29
- end
24
+ def blank?(value)
25
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
30
26
  end
31
27
  end
32
28
  end
@@ -34,7 +34,7 @@ module Julewire
34
34
 
35
35
  def to_h = section_hash
36
36
 
37
- def [](key) = nil_if_missing(section_value(key, default: MISSING))
37
+ def [](key) = call_store(:value, key, default: nil)
38
38
 
39
39
  private
40
40
 
@@ -51,10 +51,6 @@ module Julewire
51
51
  yield coerce_fields(fields, keyword_fields)
52
52
  end
53
53
 
54
- def nil_if_missing(value)
55
- value.equal?(MISSING) ? nil : value
56
- end
57
-
58
54
  def add_section(fields)
59
55
  call_store(:add, fields)
60
56
  end
@@ -67,10 +63,6 @@ module Julewire
67
63
  call_store(:hash)
68
64
  end
69
65
 
70
- def section_value(key, default:)
71
- call_store(:value, key, default: default)
72
- end
73
-
74
66
  def call_store(action, ...)
75
67
  @store.public_send(store_method(action), ...)
76
68
  end
@@ -4,13 +4,12 @@ module Julewire
4
4
  module Core
5
5
  module Fields
6
6
  class StackSet
7
- EMPTY_HASH = {}.freeze
8
- private_constant :EMPTY_HASH
9
-
10
7
  class << self
11
- def inherit_from(source, inherit_attributes: true)
12
- stacks = Bags.stack_sections.to_h do |section|
13
- [section, inherited_stack(source, section, inherit_section?(section, inherit_attributes))]
8
+ def inherit_from(source, inherit_attributes:)
9
+ stacks = Bags.stack_sections.each_with_object({}) do |section, inherited|
10
+ next unless inherit_section?(section, inherit_attributes)
11
+
12
+ inherited[section] = source.stack(section).branch
14
13
  end
15
14
  new(**stacks)
16
15
  end
@@ -20,16 +19,12 @@ module Julewire
20
19
  def inherit_section?(section, inherit_attributes)
21
20
  inherit_attributes || !%i[attributes neutral].include?(section)
22
21
  end
23
-
24
- def inherited_stack(source, section, inherit)
25
- inherit ? source.stack(section).fork : FieldStack.new
26
- end
27
22
  end
28
23
 
29
24
  def initialize(**sections)
30
25
  @stacks = Bags.stack_sections.to_h do |section|
31
- [section, field_stack(sections.fetch(section, EMPTY_HASH), section)]
32
- end.freeze
26
+ [section, field_stack(sections.fetch(section, nil), section)]
27
+ end
33
28
  end
34
29
 
35
30
  def stack(section)
@@ -19,7 +19,7 @@ module Julewire
19
19
  end
20
20
 
21
21
  def remove(key)
22
- Fields::Internal.delete_key!(@fields, key)
22
+ Internal.delete_key!(@fields, key)
23
23
  self
24
24
  end
25
25
 
@@ -28,7 +28,7 @@ module Julewire
28
28
  end
29
29
 
30
30
  def reset!
31
- @config = build_config
31
+ @config = nil
32
32
  end
33
33
 
34
34
  private
@@ -38,9 +38,10 @@ module Julewire
38
38
  end
39
39
 
40
40
  def validate_config!(configuration)
41
- return if configuration.is_a?(configuration_class)
41
+ klass = configuration_class
42
+ return if configuration.is_a?(klass)
42
43
 
43
- raise TypeError, "expected #{configuration_class.name}"
44
+ raise TypeError, "expected #{klass}"
44
45
  end
45
46
 
46
47
  def configuration_class
@@ -10,7 +10,6 @@ module Julewire
10
10
  @state = Diagnostics::Health.new(
11
11
  callback_failure_counter: callback_failure_counter,
12
12
  counter_keys: counter_keys,
13
- failure_counter: failure_counter,
14
13
  track_failures: failure_counter == :failures
15
14
  )
16
15
  end
@@ -20,20 +19,31 @@ module Julewire
20
19
  end
21
20
 
22
21
  def record_failure(error, counter: @failure_counter, **metadata)
23
- @state.record_failure(error, counter: counter, degrade: false, **metadata)
22
+ @state.record_failure(error, counter: counter, **metadata)
24
23
  end
25
24
 
26
25
  def record_loss(reason:, counter: reason, **metadata)
27
- @state.record_loss(reason: reason, counter: counter, degrade: false, **metadata)
26
+ @state.record_loss(reason: reason, counter: counter, **metadata)
28
27
  end
29
28
 
30
29
  def record_callback_failure(callback_failure)
31
30
  @state.record_callback_failure(callback_failure)
32
31
  end
33
32
 
34
- def clear_degraded! = @state.clear_failures!
33
+ def degradation_marker = @state.degradation_marker
35
34
 
36
- def degraded? = @state.degraded?(status_from: :failure_or_loss)
35
+ def clear_degradation_if_unchanged(marker) = @state.clear_degradation_if_unchanged(marker)
36
+
37
+ def recover_if_successful
38
+ marker = degradation_marker
39
+ result = yield
40
+ clear_degradation_if_unchanged(marker) unless result == false
41
+ result
42
+ end
43
+
44
+ def clear_failures! = @state.clear_failures!
45
+
46
+ def degraded? = @state.degraded?
37
47
 
38
48
  def last_callback_failure = @state.last_callback_failure
39
49
 
@@ -42,8 +52,8 @@ module Julewire
42
52
  def last_failure = @state.last_failure
43
53
 
44
54
  def snapshot(status: nil, **fields)
45
- snapshot = @state.snapshot(status: status, status_from: :failure_or_loss, include_loss: true, **fields)
46
- callback_failure = @state.last_callback_failure
55
+ snapshot = @state.snapshot(status: status, include_loss: true, **fields)
56
+ callback_failure = last_callback_failure
47
57
  return snapshot unless callback_failure
48
58
 
49
59
  snapshot.merge(last_callback_failure: callback_failure).freeze
@@ -55,7 +55,7 @@ module Julewire
55
55
 
56
56
  private
57
57
 
58
- def after_configuration_change = nil
58
+ def after_configuration_change; end
59
59
  end
60
60
  end
61
61
  end
@@ -6,24 +6,16 @@ module Julewire
6
6
  # @api integration_spi
7
7
  module Facade
8
8
  class << self
9
- def emit(record = Core::UNSET, enforce_level: true, **fields)
9
+ def emit(record = UNSET, enforce_level: true, **fields)
10
10
  record = Core.emit_input(record, fields)
11
- runtime = RuntimeLocator.current
12
- if runtime.respond_to?(:emit_integration)
13
- runtime.emit_integration(record, enforce_level: enforce_level)
14
- elsif enforce_level
15
- runtime.emit(record)
16
- else
17
- runtime.emit_without_level(record)
18
- end
11
+ RuntimeLocator.current.emit_integration(record, enforce_level: enforce_level)
19
12
  nil
20
13
  end
21
14
 
22
- def with_execution(type:, **, &)
23
- raise ArgumentError, "block required" unless block_given?
24
-
15
+ def with_execution(type:, **options, &)
25
16
  integration_write_section!(:execution)
26
- RuntimeLocator.current.with_execution(type: type, owned: true, **, &)
17
+ validate_owned_fields!(options)
18
+ RuntimeLocator.current.with_execution(type: type, owned: true, **options, &)
27
19
  end
28
20
 
29
21
  def with_attributes(fields, &)
@@ -88,6 +80,7 @@ module Julewire
88
80
  raise ArgumentError, "block required" unless block_given?
89
81
 
90
82
  integration_write_section!(section)
83
+ validate_owned_fields!(fields)
91
84
  case section
92
85
  when :attributes then ContextStore.current.with_attributes(fields, owned: true, &)
93
86
  when :carry then ContextStore.current.with_carry(fields, owned: true, &)
@@ -104,7 +97,6 @@ module Julewire
104
97
  when :context then ContextStore.current.add_context(fields, owned: true)
105
98
  when :neutral then ContextStore.current.add_neutral(fields, owned: true)
106
99
  end
107
- nil
108
100
  end
109
101
 
110
102
  def integration_write_section!(section)
@@ -115,10 +107,15 @@ module Julewire
115
107
  raise ArgumentError, "integration cannot write #{section}"
116
108
  end
117
109
 
110
+ def validate_owned_fields!(value)
111
+ Protocol.validate_symbol_hash(value)
112
+ end
113
+
118
114
  def add_summary_fields(fields, writer)
119
115
  integration_write_section!(:summary)
116
+ validate_owned_fields!(fields)
120
117
  scope = ContextStore.current.current_scope
121
- return unless scope && fields.is_a?(Hash)
118
+ return unless scope
122
119
 
123
120
  fields = Serialization::DeepCompactEmpty.compact_owned!(fields)
124
121
  scope.public_send(writer, fields, owned: true) unless fields.empty?
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/map"
4
+
3
5
  module Julewire
4
6
  module Core
5
7
  module Integration
@@ -7,51 +9,28 @@ module Julewire
7
9
  Entry = Data.define(:integration, :component, :callback)
8
10
  private_constant :Entry
9
11
 
10
- @mutex = Mutex.new
11
- @entries = {}
12
+ @entries = Concurrent::Map.new
12
13
 
13
14
  class << self
14
15
  def register(integration, component:, &callback)
15
16
  raise ArgumentError, "block required" unless callback
16
17
 
17
- name = integration_name(integration)
18
- component = component.to_sym
19
- register_entry(name, component, callback)
18
+ validate_symbol_name!(integration, name: :integration)
19
+ validate_symbol_name!(component, name: :component)
20
+ register_entry(integration, component, callback)
20
21
  end
21
22
 
22
23
  def run
23
- snapshot = mutex.synchronize { entries.values }
24
+ snapshot = entries.values
24
25
  snapshot.each { run_entry(it) }
25
- nil
26
- end
27
-
28
- def after_fork!
29
- @mutex = Mutex.new
30
- nil
31
- end
32
-
33
- def reset!
34
- mutex.synchronize { entries.clear }
35
- nil
36
26
  end
37
27
 
38
28
  private
39
29
 
40
- attr_reader :entries, :mutex
30
+ attr_reader :entries
41
31
 
42
32
  def register_entry(name, component, callback)
43
- mutex.synchronize do
44
- entries[[name, component]] = Entry.new(name, component, callback)
45
- end
46
- nil
47
- rescue StandardError => e
48
- Diagnostics::ProcessIntegrationHealth.record_failure(
49
- name,
50
- e,
51
- action: :register_after_fork,
52
- component: component
53
- )
54
- nil
33
+ entries[[name, component]] = Entry.new(name, component, callback)
55
34
  end
56
35
 
57
36
  def run_entry(entry)
@@ -63,14 +42,11 @@ module Julewire
63
42
  action: :after_fork,
64
43
  component: entry.component
65
44
  )
66
- nil
67
45
  end
68
46
 
69
- def integration_name(value)
70
- name = value.to_s
71
- raise ArgumentError, "integration name is required" if name.empty?
72
-
73
- name.to_sym
47
+ def validate_symbol_name!(value, name:)
48
+ raise TypeError, "#{name} must be a Symbol" unless value.instance_of?(Symbol)
49
+ raise ArgumentError, "#{name} is required" if value == :""
74
50
  end
75
51
  end
76
52
  end
@@ -12,7 +12,6 @@ module Julewire
12
12
  else
13
13
  Diagnostics::ProcessIntegrationHealth.record_failure(integration, error, **metadata)
14
14
  end
15
- nil
16
15
  end
17
16
 
18
17
  def record_success(integration, runtime: nil, **)
@@ -21,14 +20,12 @@ module Julewire
21
20
  else
22
21
  Diagnostics::ProcessIntegrationHealth.record_success(integration)
23
22
  end
24
- nil
25
23
  end
26
24
 
27
25
  def with_failure_health(integration, component:, action:, runtime: nil, **metadata)
28
26
  yield.tap { record_success(integration, runtime: runtime) }
29
27
  rescue StandardError => e
30
28
  record_failure(integration, e, runtime: runtime, component: component, action: action, **metadata)
31
- nil
32
29
  end
33
30
 
34
31
  def scoped(integration, runtime: nil)
@@ -10,18 +10,13 @@ module Julewire
10
10
  end
11
11
 
12
12
  def fetch(owner)
13
- return unless owner.respond_to?(:instance_variable_get)
14
-
15
13
  owner.instance_variable_get(@marker)
16
14
  rescue StandardError
17
15
  nil
18
16
  end
19
17
 
20
18
  def store(owner, value)
21
- return value unless owner.respond_to?(:instance_variable_set)
22
-
23
19
  owner.instance_variable_set(@marker, value)
24
- value
25
20
  rescue StandardError
26
21
  value
27
22
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Julewire
4
+ module Core
5
+ module Integration
6
+ # @api integration_spi
7
+ module Protocol
8
+ class << self
9
+ def validate_symbol_keys(value)
10
+ Serialization::DeepFreeze.validate_symbol_keys(value)
11
+ end
12
+
13
+ def validate_symbol_hash(value)
14
+ Serialization::DeepFreeze.validate_symbol_hash(value)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -13,7 +13,7 @@ module Julewire
13
13
 
14
14
  module ClassMethods
15
15
  def setting(name, default: nil, predicate: false, validate: nil, &block)
16
- settings_defaults[name] = block || proc { default }
16
+ settings_defaults[name] = block || -> { default }
17
17
  settings_validators[name] = validate if validate
18
18
  ivar = :"@#{name}"
19
19
 
@@ -60,7 +60,7 @@ module Julewire
60
60
  end
61
61
 
62
62
  def setting_default(default)
63
- Core::Fields::FieldSet.deep_dup(instance_exec(&default))
63
+ Fields::FieldSet.deep_dup(instance_exec(&default))
64
64
  end
65
65
 
66
66
  def validate_settings!
@@ -81,9 +81,9 @@ module Julewire
81
81
  case validator
82
82
  when Symbol
83
83
  validator_method = method(validator)
84
- validator_method.arity == 1 ? validator_method.call(value) : validator_method.call(value, name)
84
+ validator_method.arity == 2 ? validator_method.call(value, name) : validator_method.call(value)
85
85
  else
86
- validator.arity == 1 ? instance_exec(value, &validator) : instance_exec(value, name, &validator)
86
+ validator.arity == 2 ? instance_exec(value, name, &validator) : instance_exec(value, &validator)
87
87
  end
88
88
  end
89
89
  end
@@ -17,10 +17,10 @@ module Julewire
17
17
  private
18
18
 
19
19
  def update_subscription(configuration)
20
- @subscription&.update(configuration)
20
+ @subscription.update(configuration)
21
21
  end
22
22
 
23
- def store_subscription(subscriber, unsubscribe: nil)
23
+ def store_subscription(subscriber, unsubscribe:)
24
24
  @subscription = Subscription.new(subscriber, unsubscribe: unsubscribe)
25
25
  subscriber
26
26
  end
@@ -7,7 +7,8 @@ module Julewire
7
7
  class Subscription
8
8
  attr_reader :subscriber
9
9
 
10
- def initialize(subscriber, unsubscribe: nil)
10
+ def initialize(subscriber, unsubscribe:)
11
+ Validation.validate_callable!(unsubscribe, name: :unsubscribe)
11
12
  @subscriber = subscriber
12
13
  @unsubscribe = unsubscribe
13
14
  end
@@ -18,7 +19,7 @@ module Julewire
18
19
  end
19
20
 
20
21
  def reset
21
- @unsubscribe&.call
22
+ @unsubscribe.call
22
23
  nil
23
24
  rescue StandardError
24
25
  nil
@@ -59,8 +59,6 @@ module Julewire
59
59
  def path_value(object, path, default: nil)
60
60
  current = object
61
61
  Array(path).each do |key|
62
- return default if current.nil?
63
-
64
62
  current = indexed_value(current, key)
65
63
  return default if current.equal?(MISSING)
66
64
  end
@@ -68,11 +66,6 @@ module Julewire
68
66
  end
69
67
 
70
68
  def first_value(source, keys:)
71
- if source.is_a?(Hash)
72
- found = direct_hash_first_value(source, keys)
73
- return found unless found.equal?(MISSING)
74
- end
75
-
76
69
  keys.each do |key|
77
70
  found = indexed_value(source, key)
78
71
  return found unless found.equal?(MISSING) || blank_value?(found)
@@ -82,16 +75,6 @@ module Julewire
82
75
 
83
76
  private
84
77
 
85
- def direct_hash_first_value(source, keys)
86
- keys.each do |key|
87
- next unless source.key?(key)
88
-
89
- found = source[key]
90
- return found unless blank_value?(found)
91
- end
92
- MISSING
93
- end
94
-
95
78
  def indexed_value(source, key)
96
79
  return hash_value(source, key, default: MISSING) if source.is_a?(Hash)
97
80
  return MISSING unless source.respond_to?(:[])
@@ -15,15 +15,12 @@ module Julewire
15
15
  private_constant :RUNTIME_KEY, :CONTEXT_STORE_THREAD_KEY, :CONTEXT_STORE_FIBER_IVAR
16
16
 
17
17
  @runtime_ref = Concurrent::AtomicReference.new
18
- @runtime_mutex = Mutex.new
19
18
 
20
19
  class << self
21
20
  def runtime
22
21
  return ractor_runtime if ractor_local_storage?
23
22
 
24
- runtime_ref.get || runtime_mutex.synchronize do
25
- runtime_ref.get || Runtime.new.tap { runtime_ref.set(it) }
26
- end
23
+ runtime_ref.update { it || Runtime.new }
27
24
  end
28
25
 
29
26
  def runtime=(runtime)
@@ -42,22 +39,9 @@ module Julewire
42
39
  store_context(nil)
43
40
  end
44
41
 
45
- def after_fork!
46
- runtime = runtime_ref.get
47
- @runtime_mutex = Mutex.new
48
- @runtime_ref = Concurrent::AtomicReference.new(runtime)
49
- nil
50
- end
51
-
52
- # Private testing seam for storage-selection behavior.
53
- def main_ractor?
54
- ::Ractor.main?
55
- end
56
- private :main_ractor?
57
-
58
42
  private
59
43
 
60
- attr_reader :runtime_mutex, :runtime_ref
44
+ attr_reader :runtime_ref
61
45
 
62
46
  def ractor_runtime
63
47
  ::Ractor.store_if_absent(RUNTIME_KEY) { Runtime.new }
@@ -83,7 +67,7 @@ module Julewire
83
67
  end
84
68
 
85
69
  def ractor_local_storage?
86
- !main_ractor?
70
+ !::Ractor.main?
87
71
  end
88
72
  end
89
73
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Julewire
4
+ module Core
5
+ module Processing
6
+ module InvalidResultFailure
7
+ class << self
8
+ def build(message:, phase:, processor_name:, record_metadata:, result:)
9
+ [
10
+ ArgumentError.new(message),
11
+ {
12
+ phase: phase,
13
+ processor: processor_name,
14
+ record_metadata: record_metadata,
15
+ result_class: result.class.name
16
+ }
17
+ ]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -4,7 +4,6 @@ module Julewire
4
4
  module Core
5
5
  module Processing
6
6
  class LevelThreshold
7
- DEFAULT_EVENT = "log"
8
7
  DEFAULT_SEVERITY = :info
9
8
 
10
9
  attr_reader :level
@@ -20,32 +19,26 @@ module Julewire
20
19
  end
21
20
 
22
21
  def raw_input_allowed?(input)
23
- return allow?(DEFAULT_SEVERITY) unless Records::RawInput.hash_input?(input)
24
-
25
- severity, invalid, invalid_raw_value = raw_input_severity(input)
22
+ severity, invalid_raw_value = raw_input_severity(input)
26
23
  allowed = allow?(severity)
27
24
  # Surviving inputs warn later at Records::Draft normalization.
28
- record_invalid_raw_severity(input, invalid_raw_value) if invalid && !allowed
25
+ record_invalid_raw_severity(invalid_raw_value) unless invalid_raw_value.equal?(MISSING) || allowed
29
26
  allowed
30
27
  end
31
28
 
32
29
  private
33
30
 
34
31
  def raw_input_severity(input)
35
- return [DEFAULT_SEVERITY, false, nil] unless Records::RawInput.explicit_severity?(input)
32
+ return [DEFAULT_SEVERITY, MISSING] unless Records::RawInput.explicit_severity?(input)
36
33
 
37
34
  raw_value = Records::RawInput.value(input, :severity)
38
- [Records::Severity.normalize(raw_value), false, nil]
35
+ [Records::Severity.normalize(raw_value), MISSING]
39
36
  rescue ArgumentError
40
- [DEFAULT_SEVERITY, true, raw_value]
37
+ [DEFAULT_SEVERITY, raw_value]
41
38
  end
42
39
 
43
- def record_invalid_raw_severity(input, raw_value)
44
- @invalid_severity_reporter.call(
45
- raw_value,
46
- source: Records::RawInput.value(input, :source),
47
- event: Records::RawInput.value(input, :event) || DEFAULT_EVENT
48
- )
40
+ def record_invalid_raw_severity(raw_value)
41
+ @invalid_severity_reporter.call(raw_value)
49
42
  end
50
43
  end
51
44
  end
@@ -17,7 +17,7 @@ module Julewire
17
17
  raise ArgumentError, "match handler is required" unless handler
18
18
 
19
19
  conditions = normalize_conditions(conditions, keyword_conditions)
20
- @rules << Rule.new(conditions.freeze, handler)
20
+ @rules << Rule.new(conditions, handler)
21
21
  self
22
22
  end
23
23
 
@@ -46,7 +46,9 @@ module Julewire
46
46
  end
47
47
 
48
48
  def matches_conditions?(draft, conditions)
49
- conditions.all? { |key, pattern| matches_value?(pattern, draft[key]) }
49
+ conditions.all? do |key, pattern|
50
+ draft.key?(key) && matches_value?(pattern, draft.fetch(key))
51
+ end
50
52
  end
51
53
 
52
54
  def matches_value?(pattern, value)
@@ -64,8 +66,8 @@ module Julewire
64
66
  return false unless value.is_a?(Hash)
65
67
 
66
68
  pattern.all? do |key, nested_pattern|
67
- nested_value = Fields::FieldSet.value_for(value, key, default: Core::UNSET)
68
- !nested_value.equal?(Core::UNSET) && matches_value?(nested_pattern, nested_value)
69
+ nested_value = Fields::FieldSet.value_for(value, key, default: UNSET)
70
+ !nested_value.equal?(UNSET) && matches_value?(nested_pattern, nested_value)
69
71
  end
70
72
  end
71
73
  end