# `HTTPower.Middleware.CircuitBreaker`
[🔗](https://github.com/mdepolli/httpower/blob/v0.22.0/lib/httpower/middleware/circuit_breaker.ex#L1)

Circuit breaker implementation for HTTPower.

Implements the circuit breaker pattern to protect against cascading failures
when calling failing services. The circuit breaker has three states:

- **Closed** (normal): Requests pass through, failures are tracked
- **Open** (failing): Requests fail immediately without calling the service
- **Half-Open** (testing): Limited requests allowed to test recovery

## How It Works

1. **Closed State**: Requests pass through normally. The circuit breaker tracks
   failures in a sliding window. If failures exceed the threshold, it transitions
   to Open.

2. **Open State**: All requests fail immediately with `:service_unavailable`.
   After a timeout period, the circuit transitions to Half-Open.

3. **Half-Open State**: A limited number of test requests are allowed through.
   If they succeed, the circuit transitions back to Closed. If they fail,
   the circuit transitions back to Open.

## Configuration

    config :httpower, :circuit_breaker,
      enabled: true,                    # Enable/disable (default: false)
      failure_threshold: 5,             # Open after N failures
      failure_threshold_percentage: 50, # Or open after N% failure rate
      window_size: 10,                  # Track last N requests
      timeout: 60_000,                  # Stay open for 60s (milliseconds)
      half_open_requests: 1             # Allow N test requests

## Usage

    # Global circuit breaker
    config :httpower, :circuit_breaker,
      enabled: true,
      failure_threshold: 5,
      timeout: 60_000

    # Per-client circuit breaker
    client = HTTPower.new(
      base_url: "https://api.example.com",
      circuit_breaker: [
        failure_threshold: 3,
        timeout: 30_000
      ]
    )

    # Per-request circuit breaker key
    HTTPower.get(url, circuit_breaker_key: "payment_api")

## Example

    # After 5 failures, circuit opens
    for _ <- 1..5 do
      {:error, _} = HTTPower.get("https://failing-api.com/endpoint")
    end

    # Subsequent requests fail immediately
    {:error, %{reason: :service_unavailable}} =
      HTTPower.get("https://failing-api.com/endpoint")

    # After 60 seconds, circuit enters half-open
    # Next successful request closes the circuit
    :timer.sleep(60_000)
    {:ok, _} = HTTPower.get("https://failing-api.com/endpoint")

# `circuit_breaker_config`

```elixir
@type circuit_breaker_config() :: [
  enabled: boolean(),
  failure_threshold: pos_integer(),
  failure_threshold_percentage: pos_integer(),
  window_size: pos_integer(),
  timeout: pos_integer(),
  half_open_requests: pos_integer()
]
```

# `circuit_key`

```elixir
@type circuit_key() :: String.t()
```

# `circuit_state`

```elixir
@type circuit_state() :: %{
  state: state(),
  requests: [request_result()],
  opened_at: integer() | nil,
  half_open_attempts: integer()
}
```

# `request_result`

```elixir
@type request_result() :: {:success | :failure, integer()}
```

# `state`

```elixir
@type state() :: :closed | :open | :half_open
```

# `call`

```elixir
@spec call(
  circuit_key(),
  (-&gt; {:ok, term()} | {:error, term()}),
  circuit_breaker_config()
) ::
  {:ok, term()} | {:error, term()}
```

Checks if a request should be allowed through the circuit breaker.

Returns:
- `{:ok, :allowed}` if request can proceed
- `{:error, :service_unavailable}` if circuit is open
- `{:ok, :disabled}` if circuit breaker is disabled

## Examples

    # When circuit is closed (healthy), executes the function and returns its result:
    iex> HTTPower.CircuitBreaker.call("api.example.com", fn ->
    ...>   HTTPower.get("https://api.example.com/users")
    ...> end)
    {:ok, %HTTPower.Response{status: 200, body: ...}}

    # When circuit is open (too many failures), short-circuits immediately:
    iex> HTTPower.CircuitBreaker.call("api.example.com", fn ->
    ...>   HTTPower.get("https://api.example.com/users")
    ...> end)
    {:error, %HTTPower.Error{reason: :service_unavailable}}

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `close_circuit`

```elixir
@spec close_circuit(circuit_key()) :: :ok
```

Manually closes a circuit.

Useful for testing or manual intervention.

# `get_state`

```elixir
@spec get_state(circuit_key()) :: state() | nil
```

Gets the current state of a circuit.

Returns `:closed`, `:open`, `:half_open`, or `nil` if circuit doesn't exist.

# `handle_request`

Feature callback for the HTTPower pipeline.

Checks circuit breaker state and stores info for post-request recording.

Returns:
- `:ok` if circuit is closed (continue with request)
- `{:ok, request}` with circuit breaker info stored in private
- `{:error, reason}` if circuit is open (fail immediately)

## Examples

    iex> request = %HTTPower.Request{url: "https://api.example.com", ...}
    iex> HTTPower.CircuitBreaker.handle_request(request, [failure_threshold: 5])
    {:ok, modified_request}

# `open_circuit`

```elixir
@spec open_circuit(circuit_key()) :: :ok
```

Manually opens a circuit.

Useful for testing or manual intervention.

# `record_failure`

```elixir
@spec record_failure(circuit_key(), circuit_breaker_config()) :: :ok
```

Records a failed request for the circuit.

# `record_success`

```elixir
@spec record_success(circuit_key(), circuit_breaker_config()) :: :ok
```

Records a successful request for the circuit.

# `reset_circuit`

```elixir
@spec reset_circuit(circuit_key()) :: :ok
```

Resets a circuit to its initial closed state.

# `start_link`

Starts the circuit breaker GenServer.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
