lib/httpx/request_error.ex

defmodule HTTPX.RequestError do
  @moduledoc ~S"""
  HTTPX request error.
  """
  alias HTTPX.Request
  @predefined_messages []

  @typedoc @moduledoc
  @type t :: %__MODULE__{}

  defexception [
    :message,
    :code,
    :url,
    :headers,
    :body,
    :options
  ]

  @doc @moduledoc
  @spec exception(term, term, list) :: t
  def exception(reason, message \\ nil, context \\ []) do
    options = context[:options] || []

    clean_options =
      options
      |> Keyword.delete(:body)
      |> Keyword.delete(:headers)

    msg =
      cond do
        not is_nil(message) -> message
        is_atom(reason) -> @predefined_messages[reason] || to_string(reason)
        :complex -> inspect(reason)
      end

    %__MODULE__{
      code: reason,
      message: msg,
      url: context[:url],
      headers: context[:headers] || options[:headers] || [],
      body: context[:body] || options[:body],
      options: clean_options
    }
  end

  @doc ~S"Print a HTTPX request exception"
  @spec message(t) :: binary
  def message(exception) do
    [
      to_string(exception.code),
      "\r\n  url: ",
      exception.url,
      if exception.options[:params] do
        "\r\n  params:\r\n    " <> print_data(exception.options[:params])
      else
        nil
      end,
      if exception.body do
        case exception.body do
          {type, body} -> "\r\n  body: (#{type}) \r\n    " <> print_data(body)
          body -> "\r\n  body:\r\n\"\"\"\r\n" <> body <> "\r\n\"\"\""
        end
      else
        nil
      end,
      if exception.headers != [] do
        "\r\n  headers:\r\n    " <> print_data(exception.headers)
      else
        nil
      end,
      if exception.options[:settings] do
        hackney_settings =
          Request.__default_settings__()
          |> Keyword.merge(exception.options[:settings])

        "\r\n  hackney:\r\n    " <> print_data(hackney_settings)
      else
        nil
      end,
      "\r\n  Trace:"
    ]
    |> Enum.reject(&is_nil/1)
    |> IO.iodata_to_binary()
  end

  defp print_data(data, key_converter \\ &to_string/1) do
    data
    |> Enum.map(fn
      {k, v} -> key_converter.(k) <> ": " <> inspect(v)
      value -> inspect(value)
    end)
    |> Enum.join("\r\n    ")
  end

  @spec exception(msg :: term) :: Exception.t()
end