Module: RaceGuard::RuleEngine

Defined in:
lib/race_guard/rule_engine.rb

Overview

Registry + dispatch for user-defined rules (see define_rule).

Constant Summary collapse

REGISTRY =

rubocop:disable Style/MutableConstant – process-wide mutable registry

{}
REGISTRY_MUTEX =
Mutex.new

Class Method Summary collapse

Class Method Details

.define_rule(name) {|builder| ... } ⇒ Object

Yields:

  • (builder)

Raises:

  • (ArgumentError)


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/race_guard/rule_engine.rb', line 11

def self.define_rule(name, &)
  raise ArgumentError, 'RaceGuard.define_rule requires a block' unless block_given?

  sym = name.to_sym
  builder = Rule::Builder.new(sym)
  yield builder
  rule = builder.build

  REGISTRY_MUTEX.synchronize do
    raise ArgumentError, "rule #{sym.inspect} is already defined" if REGISTRY.key?(sym)

    REGISTRY[sym] = rule
  end
  RaceGuard
end

.dispatch(event, metadata = {}) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/race_guard/rule_engine.rb', line 27

def self.dispatch(event,  = {})
  sym = event.to_sym
  meta = ().merge(event: sym)
  rules_snapshot = REGISTRY_MUTEX.synchronize { REGISTRY.dup }

  cfg = RaceGuard.configuration
  rules_snapshot.each do |rule_name, rule|
    next unless cfg.enabled_rule?(rule_name)

    run_rule_hooks(rule, meta)
    next unless rule.run_on?(sym)

    evaluate_and_report(rule_name, rule, meta)
  end
  nil
end

.evaluate(name, metadata: {}) ⇒ Object



44
45
46
47
48
49
50
51
52
# File 'lib/race_guard/rule_engine.rb', line 44

def self.evaluate(name, metadata: {})
  sym = name.to_sym
  rule = REGISTRY_MUTEX.synchronize { REGISTRY[sym] }
  return nil unless rule
  return nil unless RaceGuard.configuration.enabled_rule?(sym)

  meta = ().merge(event: :evaluate)
  evaluate_and_report(sym, rule, meta)
end

.evaluate_and_report(rule_name, rule, meta) ⇒ Object



83
84
85
86
87
88
89
90
91
92
# File 'lib/race_guard/rule_engine.rb', line 83

def self.evaluate_and_report(rule_name, rule, meta)
  ctx = RaceGuard.context.current
  return unless rule.detect_proc.call(ctx, meta)

  msg = rule.message_proc.call(ctx, meta).to_s
  sev = rule.severity_override || RaceGuard.configuration.severity_for(rule_name)
  RaceGuard.report(detector: rule_name.to_s, message: msg, severity: sev)
rescue StandardError
  nil
end

.normalize_metadata(metadata) ⇒ Object



65
66
67
68
69
70
71
72
73
# File 'lib/race_guard/rule_engine.rb', line 65

def self.()
  h =  || {}
  return h.transform_keys(&:to_sym) if h.respond_to?(:transform_keys)

  h.each_with_object({}) do |(k, v), o|
    sym = k.respond_to?(:to_sym) ? k.to_sym : k.to_s.to_sym
    o[sym] = v
  end
end

.reset_registry!Object

Test helper: clears the rule registry (does not disable rules in config).



60
61
62
63
# File 'lib/race_guard/rule_engine.rb', line 60

def self.reset_registry!
  REGISTRY_MUTEX.synchronize { REGISTRY.clear }
  nil
end

.rule_defined?(name) ⇒ Boolean

Returns:

  • (Boolean)


54
55
56
57
# File 'lib/race_guard/rule_engine.rb', line 54

def self.rule_defined?(name)
  sym = name.to_sym
  REGISTRY_MUTEX.synchronize { REGISTRY.key?(sym) }
end

.run_rule_hooks(rule, meta) ⇒ Object



75
76
77
78
79
80
81
# File 'lib/race_guard/rule_engine.rb', line 75

def self.run_rule_hooks(rule, meta)
  rule.hooks_for(meta[:event]).each do |blk|
    blk.call(RaceGuard.context.current, meta)
  rescue StandardError
    nil
  end
end