DoubleDown.ContractFacade (double_down v0.47.2)

Copy Markdown View Source

Generates a dispatch facade for a DoubleDown.Contract.

use DoubleDown.ContractFacade reads a contract's __callbacks__/0 metadata and generates facade functions and key helpers that dispatch via DoubleDown.Contract.Dispatch.

Combined contract + facade (simplest)

When :contract is omitted, it defaults to __MODULE__ and use DoubleDown.Contract is issued implicitly. This gives a single-module contract + facade:

defmodule MyApp.Todos do
  use DoubleDown.ContractFacade, otp_app: :my_app

  defcallback get_todo(id :: String.t()) :: {:ok, Todo.t()} | {:error, term()}
  defcallback list_todos() :: [Todo.t()]
end

MyApp.Todos is both the contract (has @callbacks, __callbacks__/0) and the dispatch facade.

Separate contract and facade

For cases where you want the contract in a different module:

defmodule MyApp.Todos do
  use DoubleDown.ContractFacade, contract: MyApp.Todos.Contract, otp_app: :my_app
end

Options

  • :contract — the contract module that defines port operations via use DoubleDown.Contract and defcallback declarations. Defaults to __MODULE__ (combined contract + facade).
  • :otp_app (required) — the OTP application name for config-based dispatch. Implementations are resolved from Application.get_env(otp_app, contract)[:impl].
  • :test_dispatch? — controls whether the generated facade includes the NimbleOwnership-based test handler resolution step. Accepts true, false, or a zero-arity function returning a boolean. The function is evaluated at compile time. Defaults to fn -> Mix.env() != :prod end, so production builds get a config-only dispatch path with zero NimbleOwnership overhead.
  • :static_dispatch? — when true and :test_dispatch? is false, reads the implementation module from config at compile time via Application.compile_env/3 and generates direct function calls — eliminating the Application.get_env lookup at runtime entirely. Falls back to runtime config dispatch if the config is not available at compile time. Accepts true, false, or a zero-arity function. Defaults to fn -> Mix.env() == :prod end.

See also

Configuration

# config/config.exs
config :my_app, MyApp.Todos, impl: MyApp.Todos.Ecto

Testing

# test/test_helper.exs
DoubleDown.Testing.start()

# test/my_test.exs
setup do
  DoubleDown.Testing.set_fn_handler(MyApp.Todos, fn
    :get_todo, [id] -> {:ok, %Todo{id: id}}
    :list_todos, [] -> []
  end)
  :ok
end

test "gets a todo" do
  assert {:ok, %Todo{}} = MyApp.Todos.get_todo("42")
end