lib/spark/error/dsl_error.ex

defmodule Spark.Error.DslError do
  @moduledoc "Used when a DSL is incorrectly configured."
  @attrs [:module, :message, :path, :stacktrace]
  defexception @attrs

  @type t :: %__MODULE__{
          __exception__: true,
          module: nil | module,
          message: String.t() | any,
          path: [:atom],
          stacktrace: any
        }

  defmodule Stacktrace do
    @moduledoc false
    defstruct [:stacktrace]

    defimpl Inspect do
      def inspect(_, _) do
        "%Stacktrace{}"
      end
    end
  end

  @impl true
  def exception(message) when is_binary(message), do: exception(message: message)

  def exception(opts) do
    {:current_stacktrace, stacktrace} =
      Process.info(self(), :current_stacktrace)

    opts =
      opts
      |> Enum.to_list()
      |> Keyword.put(:stacktrace, %Stacktrace{stacktrace: stacktrace})
      |> Keyword.take(@attrs)

    struct!(__MODULE__, opts)
  end

  @impl true
  def message(%{module: module, message: message, path: blank})
      when is_nil(blank) or blank == [] do
    "[#{normalize_module_name(module)}]\n #{get_message(message)}"
  end

  def message(%{module: module, message: message, path: dsl_path}) do
    dsl_path = Enum.join(dsl_path, " -> ")
    "[#{normalize_module_name(module)}]\n #{dsl_path}:\n  #{get_message(message)}"
  end

  defp normalize_module_name(module) do
    inspect(module)
  end

  defp get_message(message) when is_exception(message) do
    Exception.format(:error, message)
  end

  defp get_message(message) when is_binary(message) do
    message
  end

  defp get_message(message) do
    inspect(message)
  end
end