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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/docs/advanced-configuration.md +2 -1
  4. data/docs/configuration.md +14 -7
  5. data/docs/context-and-propagation.md +26 -4
  6. data/docs/contracts.md +25 -50
  7. data/docs/extensions-and-api.md +87 -88
  8. data/docs/health-schema.md +12 -2
  9. data/docs/internals.md +24 -23
  10. data/docs/outputs-and-lifecycle.md +15 -8
  11. data/docs/records-and-data-policy.md +38 -7
  12. data/lib/julewire/core/cli/doctor.rb +20 -16
  13. data/lib/julewire/core/cli/line_helpers.rb +23 -12
  14. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
  15. data/lib/julewire/core/cli/log_formats/record_decoder.rb +1 -1
  16. data/lib/julewire/core/cli/log_formats.rb +9 -9
  17. data/lib/julewire/core/cli/tail.rb +19 -14
  18. data/lib/julewire/core/cli/transcode.rb +4 -5
  19. data/lib/julewire/core/cli.rb +1 -1
  20. data/lib/julewire/core/configuration.rb +1 -1
  21. data/lib/julewire/core/context_store.rb +56 -87
  22. data/lib/julewire/core/destinations/chaos_output.rb +2 -2
  23. data/lib/julewire/core/destinations/collection.rb +8 -16
  24. data/lib/julewire/core/destinations/definition.rb +1 -9
  25. data/lib/julewire/core/destinations/destination.rb +16 -66
  26. data/lib/julewire/core/destinations/processor_handling.rb +78 -0
  27. data/lib/julewire/core/destinations/registry.rb +1 -1
  28. data/lib/julewire/core/destinations/sink.rb +0 -1
  29. data/lib/julewire/core/destinations/synchronized_output.rb +54 -17
  30. data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
  31. data/lib/julewire/core/destinations/write_step.rb +5 -5
  32. data/lib/julewire/core/destinations.rb +0 -7
  33. data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
  34. data/lib/julewire/core/diagnostics/doctor.rb +22 -15
  35. data/lib/julewire/core/diagnostics/failure_snapshot.rb +9 -3
  36. data/lib/julewire/core/diagnostics/health.rb +41 -57
  37. data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
  38. data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
  39. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
  40. data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
  41. data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
  42. data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
  43. data/lib/julewire/core/diagnostics/tail.rb +18 -22
  44. data/lib/julewire/core/execution/boundary.rb +2 -8
  45. data/lib/julewire/core/execution/handle.rb +14 -17
  46. data/lib/julewire/core/execution/lineage.rb +26 -62
  47. data/lib/julewire/core/execution/measurement_handle.rb +6 -10
  48. data/lib/julewire/core/execution/scope.rb +17 -22
  49. data/lib/julewire/core/execution/scope_fields.rb +4 -13
  50. data/lib/julewire/core/execution/scope_identity.rb +1 -9
  51. data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
  52. data/lib/julewire/core/execution/summary_state.rb +16 -19
  53. data/lib/julewire/core/execution/view.rb +8 -17
  54. data/lib/julewire/core/facade_methods.rb +67 -65
  55. data/lib/julewire/core/fields/attribute_keys.rb +0 -2
  56. data/lib/julewire/core/fields/bags.rb +23 -27
  57. data/lib/julewire/core/fields/field_set.rb +52 -16
  58. data/lib/julewire/core/fields/field_stack.rb +69 -109
  59. data/lib/julewire/core/fields/internal/deletion.rb +6 -9
  60. data/lib/julewire/core/fields/internal.rb +35 -26
  61. data/lib/julewire/core/fields/lookup.rb +9 -13
  62. data/lib/julewire/core/fields/section_proxy.rb +1 -9
  63. data/lib/julewire/core/fields/stack_set.rb +9 -14
  64. data/lib/julewire/core/fields/static_labels.rb +1 -1
  65. data/lib/julewire/core/integration/configurable.rb +4 -3
  66. data/lib/julewire/core/integration/destination_health.rb +28 -7
  67. data/lib/julewire/core/integration/event_subscriber.rb +1 -1
  68. data/lib/julewire/core/integration/facade.rb +12 -15
  69. data/lib/julewire/core/integration/fork_hooks.rb +12 -36
  70. data/lib/julewire/core/integration/health.rb +0 -3
  71. data/lib/julewire/core/integration/ivar_state.rb +0 -5
  72. data/lib/julewire/core/integration/protocol.rb +20 -0
  73. data/lib/julewire/core/integration/settings.rb +4 -4
  74. data/lib/julewire/core/integration/subscriber_install.rb +2 -2
  75. data/lib/julewire/core/integration/subscription.rb +3 -2
  76. data/lib/julewire/core/integration/values.rb +0 -17
  77. data/lib/julewire/core/local_storage.rb +3 -19
  78. data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
  79. data/lib/julewire/core/processing/level_threshold.rb +7 -14
  80. data/lib/julewire/core/processing/match.rb +6 -4
  81. data/lib/julewire/core/processing/pipeline.rb +46 -40
  82. data/lib/julewire/core/processing/processor_chain.rb +22 -11
  83. data/lib/julewire/core/processing/processor_registry.rb +3 -4
  84. data/lib/julewire/core/processing/record_field_transform.rb +0 -4
  85. data/lib/julewire/core/processing/sampling.rb +1 -3
  86. data/lib/julewire/core/processing.rb +2 -2
  87. data/lib/julewire/core/propagation/carrier.rb +73 -19
  88. data/lib/julewire/core/propagation.rb +18 -4
  89. data/lib/julewire/core/records/build_input.rb +75 -0
  90. data/lib/julewire/core/records/console_formatter.rb +6 -6
  91. data/lib/julewire/core/records/deconstruct.rb +1 -1
  92. data/lib/julewire/core/records/display_message.rb +2 -4
  93. data/lib/julewire/core/records/draft.rb +127 -258
  94. data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
  95. data/lib/julewire/core/records/metadata.rb +4 -3
  96. data/lib/julewire/core/records/public_projection.rb +2 -2
  97. data/lib/julewire/core/records/raw_input.rb +1 -1
  98. data/lib/julewire/core/records/record.rb +30 -18
  99. data/lib/julewire/core/records/severity.rb +7 -15
  100. data/lib/julewire/core/runtime.rb +87 -72
  101. data/lib/julewire/core/runtime_registry.rb +20 -15
  102. data/lib/julewire/core/runtime_state.rb +5 -3
  103. data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
  104. data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
  105. data/lib/julewire/core/serialization/bounded_transform.rb +3 -2
  106. data/lib/julewire/core/serialization/bounded_traversal.rb +72 -83
  107. data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
  108. data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
  109. data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
  110. data/lib/julewire/core/serialization/exception_shape.rb +7 -6
  111. data/lib/julewire/core/serialization/json_encoder.rb +10 -13
  112. data/lib/julewire/core/serialization/serializer.rb +22 -49
  113. data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
  114. data/lib/julewire/core/serialization/text_encoder.rb +5 -5
  115. data/lib/julewire/core/serialization/truncation_metadata.rb +157 -0
  116. data/lib/julewire/core/serialization/value_copy.rb +183 -109
  117. data/lib/julewire/core/serialization/value_traversal.rb +10 -127
  118. data/lib/julewire/core/testing.rb +6 -56
  119. data/lib/julewire/core/validation.rb +2 -3
  120. data/lib/julewire/core/version.rb +1 -1
  121. data/lib/julewire/core.rb +2 -7
  122. metadata +7 -19
  123. data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
  124. data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
  125. data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
  126. data/lib/julewire/core/testing/chaos/destination.rb +0 -55
  127. data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
  128. data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
  129. data/lib/julewire/core/testing/chaos.rb +0 -80
  130. data/lib/julewire/core/testing/contracts/component.rb +0 -162
  131. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
  132. data/lib/julewire/core/testing/contracts/integration.rb +0 -166
  133. data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
  134. data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
  135. data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
  136. data/lib/julewire/core/testing/contracts/wire.rb +0 -60
  137. data/lib/julewire/core/testing/contracts.rb +0 -24
  138. data/lib/julewire/core/testing/coverage.rb +0 -58
  139. data/lib/julewire/core/testing/test_reports.rb +0 -78
@@ -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