lib/error.ex

defmodule XRPL.Error do
  @moduledoc false
  require Logger

  defexception [:reason, :details]

  @impl true
  def exception({reason, %Ecto.Changeset{} = details}) do
    %__MODULE__{reason: reason, details: mapper(details)}
  end

  def exception({reason, details}) do
    %__MODULE__{reason: reason, details: inspect(details)}
  end

  @impl true
  def message(%__MODULE__{reason: :validation_error, details: details}) do
    message = "XRPL call failed: Invalid params"
    Logger.info(message, details: inspect(details))
    message
  end

  def message(%__MODULE__{reason: :request_error, details: %{body: body, url: url}}) do
    message = "XRPL call failed: Request error"
    Logger.info(message, url: url, body: body)
    message
  end

  defp mapper(%Ecto.Changeset{} = changeset) do
    changeset
    |> Ecto.Changeset.traverse_errors(fn {msg, opts} ->
      Enum.reduce(opts, msg, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", _to_string(value))
      end)
    end)
    |> Enum.reduce("", fn {k, v}, acc ->
      joined_errors = Enum.join(v, "; ")
      "#{acc} #{k}: #{joined_errors}"
    end)
    |> String.trim_leading()
  end

  defp _to_string(val) when is_list(val) do
    Enum.join(val, ",")
  end

  defp _to_string(val), do: to_string(val)
end