Class: GenevaDrive::ExceptionPolicy
- Inherits:
-
Object
- Object
- GenevaDrive::ExceptionPolicy
- Defined in:
- lib/geneva_drive/exception_policy.rb
Overview
Bundles exception handling configuration into a reusable object.
Can be used at both class level (via on_exception) and step level
(via the on_exception: keyword argument).
Supports two mutually exclusive modes:
Declarative mode — specify an action symbol and options: ExceptionPolicy.new(:reattempt!, wait: 15.seconds, max_reattempts: 5)
Imperative mode — provide a block that receives the exception and calls flow control methods in the workflow context: ExceptionPolicy.new { |error| reattempt!(wait: error.retry_after) }
Policies can optionally target specific exception classes via the +matching:+ keyword. A policy without +matching:+ is a "blanket" policy that matches any exception. A policy with +matching:+ is a "specific" policy that only fires for errors matching the given class(es).
Multiple policies can be composed into an array and passed to a step's +on_exception:+ option. GenevaDrive wraps the array in a CombinedExceptionPolicy that walks specific policies first, then falls back to the first blanket policy. If no policy in the array matches, resolution continues at the class level.
Defined Under Namespace
Classes: LazyExceptionMatcher
Constant Summary collapse
- VALID_ACTIONS =
Valid action values (same as StepDefinition::EXCEPTION_HANDLERS)
%i[pause! cancel! reattempt! skip!].freeze
- VALID_TERMINAL_ACTIONS =
Creates a new exception policy.
Valid terminal_action values
%i[pause! cancel! skip!].freeze
- VALID_REPORT_OPTIONS =
Valid report values
%i[always never terminal_only].freeze
Instance Attribute Summary collapse
-
#action ⇒ Symbol?
readonly
The action (:pause!, :cancel!, :reattempt!, :skip!) — nil in imperative mode.
-
#exception_matchers ⇒ Array<#===>
readonly
Exception matchers this policy checks (empty = match all).
-
#handler ⇒ Proc?
readonly
The handler block (imperative mode).
-
#max_reattempts ⇒ Integer?
readonly
Maximum consecutive reattempts before pausing (nil = unlimited).
-
#report ⇒ Symbol
readonly
When to report the exception via Rails.error.report (:always, :never, or :terminal_only).
-
#terminal_action ⇒ Symbol
readonly
What to do when max_reattempts is exceeded (:pause!, :cancel!, or :skip!).
-
#wait ⇒ ActiveSupport::Duration?
readonly
Wait time before reattempt.
Instance Method Summary collapse
-
#apply(error, reattempt_count:, workflow:) ⇒ Hash
Applies this policy to the given error and returns a result Hash describing the action to take.
-
#blanket? ⇒ Boolean
Returns true if this is a blanket policy (no exception matchers).
-
#captures?(error) ⇒ Boolean
(also: #matches?)
Returns true if this policy captures the given error.
-
#declarative? ⇒ Boolean
Returns true if this is a declarative policy (action symbol, no block).
-
#initialize(action = nil, wait: nil, max_reattempts: nil, terminal_action: :pause!, matching: nil, report: :always, &block) ⇒ ExceptionPolicy
constructor
A new instance of ExceptionPolicy.
-
#policies ⇒ Array<GenevaDrive::ExceptionPolicy>
Returns this policy wrapped in an Array for uniform iteration.
-
#specific? ⇒ Boolean
Returns true if this policy has exception matchers.
Constructor Details
#initialize(action, matching: nil, wait: nil, max_reattempts: nil, terminal_action: :pause!, report: :always) ⇒ ExceptionPolicy #initialize(matching: nil, report: :always) {|error| ... } ⇒ ExceptionPolicy
Returns a new instance of ExceptionPolicy.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/geneva_drive/exception_policy.rb', line 148 def initialize(action = nil, wait: nil, max_reattempts: nil, terminal_action: :pause!, matching: nil, report: :always, &block) @report = report unless VALID_REPORT_OPTIONS.include?(@report) raise ArgumentError, "report: must be one of #{VALID_REPORT_OPTIONS.join(", ")}, got #{@report.inspect}" end if block if action || wait || max_reattempts || terminal_action != :pause! raise ArgumentError, "Cannot pass action, wait, max_reattempts, or terminal_action when a block is given" end @handler = block @action = nil @wait = nil @max_reattempts = nil @terminal_action = :pause! else raise ArgumentError, "Either an action or a block is required" unless action @handler = nil @action = action @wait = wait @max_reattempts = max_reattempts @terminal_action = terminal_action validate! end @exception_matchers = build_matchers(matching) end |
Instance Attribute Details
#action ⇒ Symbol? (readonly)
Returns the action (:pause!, :cancel!, :reattempt!, :skip!) — nil in imperative mode.
73 74 75 |
# File 'lib/geneva_drive/exception_policy.rb', line 73 def action @action end |
#exception_matchers ⇒ Array<#===> (readonly)
Returns exception matchers this policy checks (empty = match all). Each entry can be an Exception subclass or any object responding to #===.
86 87 88 |
# File 'lib/geneva_drive/exception_policy.rb', line 86 def exception_matchers @exception_matchers end |
#handler ⇒ Proc? (readonly)
Returns the handler block (imperative mode).
92 93 94 |
# File 'lib/geneva_drive/exception_policy.rb', line 92 def handler @handler end |
#max_reattempts ⇒ Integer? (readonly)
Returns maximum consecutive reattempts before pausing (nil = unlimited).
79 80 81 |
# File 'lib/geneva_drive/exception_policy.rb', line 79 def max_reattempts @max_reattempts end |
#report ⇒ Symbol (readonly)
Returns when to report the exception via Rails.error.report (:always, :never, or :terminal_only).
89 90 91 |
# File 'lib/geneva_drive/exception_policy.rb', line 89 def report @report end |
#terminal_action ⇒ Symbol (readonly)
Returns what to do when max_reattempts is exceeded (:pause!, :cancel!, or :skip!).
82 83 84 |
# File 'lib/geneva_drive/exception_policy.rb', line 82 def terminal_action @terminal_action end |
#wait ⇒ ActiveSupport::Duration? (readonly)
Returns wait time before reattempt.
76 77 78 |
# File 'lib/geneva_drive/exception_policy.rb', line 76 def wait @wait end |
Instance Method Details
#apply(error, reattempt_count:, workflow:) ⇒ Hash
Applies this policy to the given error and returns a result Hash describing the action to take.
For declarative policies, checks the reattempt limit and returns the appropriate action. For imperative policies, executes the handler block in the workflow context and translates the resulting flow control signal into a result.
The returned Hash always contains +:action+ (Symbol without +!+), +:error+ (the original exception), and +:report+ (the reporting mode). Reattempt results also include +:wait+. When the terminal action fires (reattempt limit exceeded), +:terminal+ is set to +true+.
234 235 236 237 238 239 240 |
# File 'lib/geneva_drive/exception_policy.rb', line 234 def apply(error, reattempt_count:, workflow:) if handler apply_imperative(error, workflow) else apply_declarative(error, reattempt_count) end end |
#blanket? ⇒ Boolean
Returns true if this is a blanket policy (no exception matchers).
206 207 208 |
# File 'lib/geneva_drive/exception_policy.rb', line 206 def blanket? exception_matchers.empty? end |
#captures?(error) ⇒ Boolean Also known as: matches?
Returns true if this policy captures the given error. A policy with no exception matchers captures all errors (blanket policy). A policy with matchers only captures errors matching the given class(es).
190 191 192 |
# File 'lib/geneva_drive/exception_policy.rb', line 190 def captures?(error) exception_matchers.empty? || exception_matchers.any? { |matcher| matcher === error } end |
#declarative? ⇒ Boolean
Returns true if this is a declarative policy (action symbol, no block).
180 181 182 |
# File 'lib/geneva_drive/exception_policy.rb', line 180 def declarative? handler.nil? end |
#policies ⇒ Array<GenevaDrive::ExceptionPolicy>
Returns this policy wrapped in an Array for uniform iteration.
CombinedExceptionPolicy stores its children in an Array via #policies. This method lets callers use +exception_policy.policies+ on either type without branching, producing a flat list of leaf policies.
250 251 252 |
# File 'lib/geneva_drive/exception_policy.rb', line 250 def policies [self] end |
#specific? ⇒ Boolean
Returns true if this policy has exception matchers.
199 200 201 |
# File 'lib/geneva_drive/exception_policy.rb', line 199 def specific? exception_matchers.any? end |