Skip to main content

lib/jido/chat/webhook_response.ex

defmodule Jido.Chat.WebhookResponse do
  @moduledoc """
  Typed webhook response envelope.
  """

  alias Jido.Chat.Wire

  @schema Zoi.struct(
            __MODULE__,
            %{
              status: Zoi.integer() |> Zoi.default(200),
              headers: Zoi.map() |> Zoi.default(%{}),
              body: Zoi.any() |> Zoi.nullish(),
              metadata: Zoi.map() |> Zoi.default(%{})
            },
            coerce: true
          )

  @type t :: unquote(Zoi.type_spec(@schema))

  @enforce_keys Zoi.Struct.enforce_keys(@schema)
  defstruct Zoi.Struct.struct_fields(@schema)

  @doc "Returns the Zoi schema for WebhookResponse."
  def schema, do: @schema

  @doc "Creates a typed webhook response envelope."
  @spec new(map()) :: t()
  def new(attrs) when is_map(attrs), do: Jido.Chat.Schema.parse!(__MODULE__, @schema, attrs)

  @doc "Returns a default accepted response."
  @spec accepted(map() | nil) :: t()
  def accepted(body \\ nil), do: new(%{status: 200, body: body || %{ok: true}})

  @doc "Returns an error response envelope."
  @spec error(integer(), map() | String.t() | nil) :: t()
  def error(status, body \\ nil) when is_integer(status) do
    body =
      cond do
        is_map(body) -> body
        is_binary(body) -> %{error: body}
        true -> %{error: "request_failed"}
      end

    new(%{status: status, body: body})
  end

  @doc "Serializes webhook response into plain map with type marker."
  @spec to_map(t()) :: map()
  def to_map(%__MODULE__{} = response) do
    response
    |> Map.from_struct()
    |> Wire.to_plain()
    |> Map.put("__type__", "webhook_response")
  end

  @doc "Builds webhook response from serialized data."
  @spec from_map(map()) :: t()
  def from_map(map) when is_map(map), do: new(map)
end