DoubleDown.Contract.Dispatch (double_down v0.47.2)

Copy Markdown View Source

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 configApplication.get_env(otp_app, contract)[:impl]
  3. Raise — no handler configured

call_config/4 — config-only dispatch

  1. Application configApplication.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.

Summary

Functions

Dispatch a port operation to the resolved implementation.

Dispatch a port operation directly from application config.

Read the current stateful handler state for a contract.

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

Build a canonical key for test stub matching.

Restore a single contract's stateful handler state.

Functions

call(otp_app, contract, operation, args)

@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(otp_app, contract, operation, args)

@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(contract)

@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.

handler_active?(contract)

@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(contract, operation, args)

@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(contract, owner_pid, snapshot)

@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).