# `DoubleDown.Double.CanonicalHandlerState`
[🔗](https://github.com/mccraigmccraig/double_down/blob/main/lib/double_down/double/canonical_handler_state.ex#L1)

State for `DoubleDown.Double.canonical_handler/5`.

Stored inline in `HandlerMeta.Stateful.state` when `Double` installs
its canonical stateful handler. Tracks queued expectations,
per-operation stubs, per-operation fakes, and the fallback handler
(function, stateful fake, or module) with its associated state.

## Fields

* `contract` — the contract module this state belongs to (never nil)
* `expects` — `%{operation => [fun | :passthrough]}` queued expectations
* `fakes` — `%{operation => fun}` per-operation stateful fake overrides
* `stubs` — `%{operation => fun}` per-operation stateless stub functions
* `rejects` — `MapSet` of operation atoms that must not be called
* `fallback` — the fallback handler, one of:
  - `nil` — no fallback configured
  - `{:stateless, fun}` — stateless 3-arity function fallback
  - `{:stateful, fun}` — 4/5-arity stateful fake function
  - `{:module, module}` — module implementing the contract behaviour
* `fallback_state` — domain state for stateful fakes (only meaningful
  when `fallback` is `{:stateful, _}`)

# `fallback`

```elixir
@type fallback() ::
  nil
  | {:stateless, DoubleDown.Dispatch.Types.stateless_fun()}
  | {:stateful, DoubleDown.Dispatch.Types.stateful_fun()}
  | {:module, module()}
```

# `t`

```elixir
@type t() :: %DoubleDown.Double.CanonicalHandlerState{
  contract: module(),
  expects: %{
    required(atom()) =&gt; [DoubleDown.Double.Types.expect_fun() | :passthrough]
  },
  fakes: %{required(atom()) =&gt; DoubleDown.Double.Types.fake_fun()},
  fallback: fallback(),
  fallback_state: term(),
  rejects: term(),
  stubs: %{required(atom()) =&gt; DoubleDown.Double.Types.stub_fun()}
}
```

# `add_expect`

```elixir
@spec add_expect(t(), atom(), DoubleDown.Double.Types.expect_fun() | :passthrough) ::
  t()
```

Add a single expect entry for an operation.

# `add_expects`

```elixir
@spec add_expects(t(), atom(), [DoubleDown.Double.Types.expect_fun() | :passthrough]) ::
  t()
```

Add multiple expect entries for an operation (e.g. from `times: n`).

# `add_reject`

```elixir
@spec add_reject(t(), atom(), non_neg_integer()) :: t()
```

Mark an operation/arity as rejected (must not be called).

# `new`

```elixir
@spec new(module()) :: t()
```

Create a new canonical handler state for the given contract.

# `pop_expect`

```elixir
@spec pop_expect(t(), atom()) ::
  {:ok, DoubleDown.Double.Types.expect_fun() | :passthrough, t()} | :none
```

Pop the next expect entry for an operation.

# `put_fake`

```elixir
@spec put_fake(t(), atom(), DoubleDown.Double.Types.fake_fun()) :: t()
```

Set a per-operation fake.

# `put_fallback_state`

```elixir
@spec put_fallback_state(t(), term()) :: t()
```

Update the fallback_state (used during dispatch).

# `put_stub`

```elixir
@spec put_stub(t(), atom(), DoubleDown.Double.Types.stub_fun()) :: t()
```

Set a per-operation stub.

# `rejected?`

```elixir
@spec rejected?(t(), atom(), non_neg_integer()) :: boolean()
```

Check if an operation/arity has been rejected.

# `set_module_fallback`

```elixir
@spec set_module_fallback(t(), module()) :: t()
```

Set a module fallback.

# `set_stateful_fallback`

```elixir
@spec set_stateful_fallback(t(), DoubleDown.Dispatch.Types.stateful_fun(), term()) ::
  t()
```

Set a stateful function fallback with initial state.

# `set_stateless_fallback`

```elixir
@spec set_stateless_fallback(t(), DoubleDown.Dispatch.Types.stateless_fun()) :: t()
```

Set a stateless function fallback.

# `stateful_fallback?`

```elixir
@spec stateful_fallback?(t()) :: boolean()
```

Check if a stateful fallback is configured.

---

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