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

In-flight request deduplication to prevent duplicate operations.

This module prevents duplicate requests from causing duplicate side effects
(e.g., double charges, duplicate orders) by tracking in-flight requests and
sharing responses with identical concurrent requests.

## How It Works

1. **Request Fingerprinting** - Each request gets a hash based on method + URL + body
2. **In-Flight Tracking** - First request executes normally, subsequent identical requests wait
3. **Response Sharing** - When the first request completes, all waiting requests receive the same response
4. **Automatic Cleanup** - Tracking data is automatically removed after configurable TTL

## Use Cases

- Prevent double charges from double-clicks on payment buttons
- Prevent duplicate orders from retry storms or race conditions
- Ensure idempotency for critical mutations (POST/PUT/DELETE)

## Configuration

    # Global configuration
    config :httpower, :deduplicate,
      enabled: true,
      ttl: 5_000  # 5 seconds - how long to track in-flight requests

    # Per-request configuration
    HTTPower.post(url,
      body: payment_data,
      deduplicate: true
    )

    # Or with options
    HTTPower.post(url,
      body: payment_data,
      deduplicate: [
        enabled: true,
        ttl: 10_000,
        wait_timeout: 60_000,   # How long waiters block for in-flight request (default: 30s)
        key: "custom-dedup-key"  # Optional: override hash generation
      ]
    )

## States

- **`:in_flight`** - Request currently executing, other identical requests will wait
- **`:completed`** - Brief period after completion to catch race conditions (100-500ms)

## Thread Safety

Uses ETS for thread-safe storage and GenServer for coordination.

# `cancel`

```elixir
@spec cancel(String.t()) :: :ok
```

Cancels an in-flight request (called on error/timeout).

## Examples

    HTTPower.RequestDeduplicator.cancel(request_hash)

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `complete`

```elixir
@spec complete(String.t(), any(), keyword()) :: :ok
```

Completes a request, storing the response and notifying waiters.

## Examples

    HTTPower.RequestDeduplicator.complete(request_hash, response, config)

# `deduplicate`

```elixir
@spec deduplicate(
  String.t(),
  keyword()
) ::
  {:ok, :execute} | {:ok, :wait, reference()} | {:ok, any()} | {:error, atom()}
```

Attempts to deduplicate a request.

Returns:
- `{:ok, :execute}` - First occurrence, proceed with execution
- `{:ok, :wait, ref}` - Duplicate request, wait for in-flight to complete
- `{:ok, response}` - Request just completed, return cached response
- `{:error, reason}` - Deduplication disabled or error occurred

## Examples

    case HTTPower.RequestDeduplicator.deduplicate(request_hash, config) do
      {:ok, :execute} ->
        # Execute the request
        execute_request()

      {:ok, :wait, ref} ->
        # Wait for in-flight request to complete
        await_response(ref)

      {:ok, response} ->
        # Use cached response from just-completed request
        {:ok, response}
    end

# `handle_request`

Feature callback for the HTTPower pipeline.

Checks for duplicate requests and either executes, waits, or returns cached response.

Returns:
- `{:ok, request}` with dedup info stored in private (first occurrence)
- `{:halt, response}` if cached response available (short-circuit)
- Waits and returns `{:halt, response}` for duplicate in-flight requests

## Examples

    iex> request = %HTTPower.Request{method: :post, url: "https://api.example.com/charge", body: "..."}
    iex> HTTPower.Dedup.handle_request(request, [enabled: true])
    {:ok, modified_request}

# `hash`

```elixir
@spec hash(atom(), String.t(), String.t() | nil) :: String.t()
```

Generates a deduplication hash from request parameters.

## Examples

    hash = HTTPower.RequestDeduplicator.hash(:post, "https://api.com/charge", ~s({"amount": 100}))

# `start_link`

Starts the request deduplicator GenServer.

---

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