lib/openagents/effects/registry.ex

main at 58e6347eeb72 · 1 KB

defmodule OpenAgents.Effects.Registry do
  @moduledoc """
  Which module runs which kind of effect.

  The map is configuration, not a scan: an effect kind with no handler is a
  refusal, never a silent no-op, because an outbox that quietly drops an effect
  it does not recognize is exactly the "best-effort" behaviour the outbox
  exists to replace.

  Kinds are bounded strings admitted here. Nothing turns a payload string into
  a module or an atom at runtime.
  """

  @default_handlers %{
    "work.launch_worker" => OpenAgents.Effects.Handlers.WorkLaunch,
    "email.delivery" => OpenAgents.Effects.Handlers.EmailDelivery
  }

  @doc "Every admitted effect kind and its handler."
  @spec handlers() :: %{String.t() => module()}
  def handlers do
    configured =
      :openagents
      |> Application.get_env(:effects, [])
      |> Keyword.get(:handlers, %{})
      |> Map.new()

    Map.merge(@default_handlers, configured)
  end

  @doc "Every effect kind this release can run."
  @spec kinds() :: [String.t()]
  def kinds, do: handlers() |> Map.keys() |> Enum.sort()

  @doc "Resolve one kind to its handler."
  @spec fetch(String.t()) :: {:ok, module()} | {:error, :unknown_kind}
  def fetch(kind) when is_binary(kind) do
    case Map.fetch(handlers(), kind) do
      {:ok, handler} -> {:ok, handler}
      :error -> {:error, :unknown_kind}
    end
  end
end