# `DoubleDown.Contract.Dispatch`
[🔗](https://github.com/mccraigmccraig/double_down/blob/main/lib/double_down/contract/dispatch.ex#L1)

Dispatch resolution for DoubleDown contracts.

Three dispatch paths are available, selected at compile time by the
`:test_dispatch?` and `:static_dispatch?` options on
`use DoubleDown.ContractFacade`:

### `call/4` — test-aware dispatch (default in non-prod)

1. **Test handler** — process-scoped via `NimbleOwnership`
   (only checked if the ownership server is running)
2. **Application config** — `Application.get_env(otp_app, contract)[:impl]`
3. **Raise** — no handler configured

### `call_config/4` — config-only dispatch

1. **Application config** — `Application.get_env(otp_app, contract)[:impl]`
2. **Raise** — no handler configured

No `NimbleOwnership` code is referenced in the generated facade
functions, eliminating the `GenServer.whereis` lookup entirely.

### Static dispatch (default in prod when config available)

The implementation module is resolved at compile time and the
generated facade calls it directly — no `NimbleOwnership`, no
`Application.get_env`. Zero dispatch overhead. Falls back to
`call_config/4` if the config is not available at compile time.

# `call`

```elixir
@spec call(atom() | nil, module(), atom(), [term()]) :: term()
```

Dispatch a port operation to the resolved implementation.

Called by generated facade functions when `test_dispatch?: true`
(the default in non-production environments). Checks for a
process-scoped test handler via `NimbleOwnership` before falling
back to application config.

# `call_config`

```elixir
@spec call_config(atom() | nil, module(), atom(), [term()]) :: term()
```

Dispatch a port operation directly from application config.

Called by generated facade functions when `test_dispatch?: false`
(the default in production). Skips the `NimbleOwnership` test
handler lookup entirely — zero overhead beyond `Application.get_env`.

# `get_state`

```elixir
@spec get_state(module()) :: term()
```

Read the current stateful handler state for a contract.

Returns the domain state — for Double-managed handlers this is
the `fallback_state` field; for raw stateful handlers this is
the entire state value. Used to snapshot state before a transaction.

Respects the `$callers` chain, so state is accessible from child
processes (e.g. `Task.async`).

# `handler_active?`

```elixir
@spec handler_active?(module()) :: boolean()
```

Check whether the calling process has a test handler installed for
the given contract.

Returns `true` when a handler is active (via `DoubleDown.Double.fake/2`,
`expect/3`, etc.), `false` otherwise. Useful for test infrastructure that
needs to skip real-DB side-effects (e.g. setting Postgres session variables)
when an in-memory handler is intercepting Repo calls.

Respects the `$callers` chain, so handlers installed in a parent process
are visible to spawned children.

# `key`

```elixir
@spec key(module(), atom(), [term()]) :: term()
```

Build a canonical key for test stub matching.

Keys are normalized so that map/keyword argument order doesn't affect matching.

# `restore_state`

```elixir
@spec restore_state(module(), pid(), term()) :: :ok
```

Restore a single contract's stateful handler state.

Replaces the state for the given contract in NimbleOwnership,
leaving the handler function and all other contracts' state
untouched. Used by transaction rollback to restore the pre-
transaction snapshot.

Handles both Double-managed handlers (restores the `fallback_state`
field within the Double's internal map) and raw stateful handlers
installed via `set_stateful_handler` (replaces the entire state).

---

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