# `IdempotencyKit.Phoenix.Action`
[🔗](https://github.com/metacircu1ar/idempotency_kit/blob/main/lib/idempotency_kit/phoenix/action.ex#L1)

Reusable Plug/Phoenix adapter for idempotent controller actions.

This module is host-app agnostic. Supply app-specific callbacks via options:

- `:idempotency_module` (required): module implementing
  `request_hash/1`, `claim_request/4`, and `complete_request/4`
- `:render_error_fun` (optional): `(conn, status, detail, metadata) -> conn`

# `error_spec`

```elixir
@type error_spec() :: %{
  :status =&gt; response_status(),
  :detail =&gt; String.t(),
  optional(:code) =&gt; String.t(),
  optional(:metadata) =&gt; map()
}
```

# `persist_response_fun`

```elixir
@type persist_response_fun() :: (Plug.Conn.t(), term() -&gt; persist_result())
```

# `persist_result`

```elixir
@type persist_result() :: {:ok, String.t(), pos_integer(), map()} | {:error, term()}
```

# `render_error_fun`

```elixir
@type render_error_fun() :: (Plug.Conn.t(), response_status(), String.t(), map() -&gt;
                         Plug.Conn.t())
```

# `replay_response_fun`

```elixir
@type replay_response_fun() :: (Plug.Conn.t(), term() -&gt;
                            {:ok, Plug.Conn.t()} | :default | {:error, term()})
```

# `response_status`

```elixir
@type response_status() :: atom() | integer()
```

# `maybe_run`

```elixir
@spec maybe_run(Plug.Conn.t(), keyword(), (Plug.Conn.t() -&gt; Plug.Conn.t())) ::
  {:handled, Plug.Conn.t()} | {:no_key, Plug.Conn.t()}
```

# `maybe_run_for_user`

```elixir
@spec maybe_run_for_user(
  Plug.Conn.t(),
  pos_integer(),
  String.t(),
  term(),
  (Plug.Conn.t() -&gt; Plug.Conn.t()),
  keyword()
) :: {:handled, Plug.Conn.t()} | {:no_key, Plug.Conn.t()}
```

---

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