Class: GenevaDrive::CombinedExceptionPolicy Private

Inherits:
Object
  • Object
show all
Defined in:
lib/geneva_drive/combined_exception_policy.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Wraps an array of ExceptionPolicy objects into a single composable unit.

Created automatically when a step's +on_exception:+ receives an Array of ExceptionPolicy objects. You never need to instantiate this class directly; it is produced by StepDefinition during validation. The executor also builds a CombinedExceptionPolicy at resolution time by composing step-level, class-level, and default policies into one stack.

Resolution order

#apply walks the constituent policies in order. The first policy whose captures? returns +true+ for the error wins. Ordering determines precedence: specific policies (those with +matching:+) should be listed before blanket policies, like +rescue+ clauses in Ruby.

If no policy captures the error, #apply returns +nil+.

Global reattempt cap

#max_reattempts returns the minimum +max_reattempts+ value across all constituent policies (ignoring +nil+, which means unlimited). When #apply delegates to a child that wants to reattempt, it checks this global cap first. If the cap is exceeded, the matched policy's +terminal_action+ is applied instead. This prevents runaway retries when different exception types alternate.

Examples:

Policies defined on a step

step :sync, on_exception: [
  ExceptionPolicy.new(:reattempt!, matching: Timeout::Error, max_reattempts: 10),
  ExceptionPolicy.new(:cancel!, matching: FatalApiError),
  ExceptionPolicy.new(:skip!)  # blanket fallback
] do
  ExternalApi.sync(hero)
end
# StepDefinition wraps this array in a CombinedExceptionPolicy automatically.
# - Timeout::Error  -> reattempt (up to 10 times)
# - FatalApiError   -> cancel
# - anything else   -> skip
# - global cap      -> 10 (the only finite max_reattempts in the array)

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(policies) ⇒ CombinedExceptionPolicy

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of CombinedExceptionPolicy.

Parameters:



51
52
53
# File 'lib/geneva_drive/combined_exception_policy.rb', line 51

def initialize(policies)
  @policies = Array(policies)
end

Instance Attribute Details

#policiesArray<GenevaDrive::ExceptionPolicy> (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the constituent policies.

Returns:



48
49
50
# File 'lib/geneva_drive/combined_exception_policy.rb', line 48

def policies
  @policies
end

Instance Method Details

#apply(error, reattempt_count:, workflow:) ⇒ Hash?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Applies the first matching policy to the given error.

Walks policies in order, delegating to the first one that captures the error. Before delegating a reattempt, checks the global cap (#max_reattempts) and overrides with the matched policy's +terminal_action+ if exceeded.

Parameters:

  • error (Exception)

    the exception that was raised

  • reattempt_count (Integer)

    consecutive reattempts so far

  • workflow (GenevaDrive::Workflow)

    the workflow instance

Returns:

See Also:



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/geneva_drive/combined_exception_policy.rb', line 79

def apply(error, reattempt_count:, workflow:)
  child = @policies.find { |p| p.captures?(error) }
  return nil unless child

  result = child.apply(error, reattempt_count: reattempt_count, workflow: workflow)

  # Enforce global reattempt cap (min max_reattempts across all children).
  # If the matched policy wants to reattempt but the global cap is exceeded,
  # override with the matched policy's terminal_action.
  cap = max_reattempts
  if cap && result[:action] == :reattempt && reattempt_count >= cap
    terminal = child.terminal_action.to_s.chomp("!").to_sym
    return {action: terminal, error: error, report: result[:report], terminal: true}
  end

  result
end

#captures?(error) ⇒ Boolean Also known as: matches?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns true if any constituent policy captures the given error.

Parameters:

  • error (Exception)

Returns:

  • (Boolean)


59
60
61
# File 'lib/geneva_drive/combined_exception_policy.rb', line 59

def captures?(error)
  @policies.any? { |policy| policy.captures?(error) }
end

#max_reattemptsInteger?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the effective global reattempt cap: the minimum max_reattempts across all constituent policies (ignoring nil = unlimited). Returns nil if all policies have unlimited reattempts.

Returns:

  • (Integer, nil)


102
103
104
105
# File 'lib/geneva_drive/combined_exception_policy.rb', line 102

def max_reattempts
  caps = @policies.filter_map(&:max_reattempts)
  caps.empty? ? nil : caps.min
end