Skip to main content

lib/rocksky/error.ex

defmodule Rocksky.Error do
  @moduledoc """
  Error returned from any Rocksky SDK call.

  * `:status` — HTTP status (nil for transport errors)
  * `:reason` — atom or term describing the failure
  * `:message` — human-readable message
  * `:body` — response body if present (parsed JSON or raw string)
  """

  defexception [:status, :reason, :message, :body]

  @type t :: %__MODULE__{
          status: pos_integer() | nil,
          reason: atom() | term(),
          message: String.t(),
          body: term()
        }

  @impl true
  def message(%__MODULE__{message: message, status: nil}), do: message

  def message(%__MODULE__{message: message, status: status}) do
    "(HTTP #{status}) #{message}"
  end

  @doc false
  def from_status(status, body) do
    reason =
      case status do
        400 -> :bad_request
        401 -> :unauthorized
        403 -> :forbidden
        404 -> :not_found
        429 -> :rate_limited
        s when s in 500..599 -> :server_error
        _ -> :http_error
      end

    %__MODULE__{
      status: status,
      reason: reason,
      message: extract_message(body) || "request failed",
      body: body
    }
  end

  @doc false
  def from_transport(reason) do
    %__MODULE__{
      status: nil,
      reason: :transport_error,
      message: "transport error: #{inspect(reason)}",
      body: nil
    }
  end

  defp extract_message(%{"message" => msg}) when is_binary(msg), do: msg
  defp extract_message(%{"error" => msg}) when is_binary(msg), do: msg
  defp extract_message(msg) when is_binary(msg), do: msg
  defp extract_message(_), do: nil
end