Packages

Simple error struct factory for Elixir

Current section

Files

Jump to
exc_error lib exc_error.ex
Raw

lib/exc_error.ex

defmodule ExcError do
@default_fields [:cause]
defmodule Helpers do
def to_s({:error, error}), do: to_s(error)
def to_s(term) do
if String.Chars.impl_for(term) do
to_string(term)
else
inspect(term)
end
end
end
defmacro define(name, options \\ []) do
record_fields = prepare_record_fields(options) ++ @default_fields
quote location: :keep do
defmodule unquote(name) do
defexception(unquote(record_fields))
unquote do
unless type_defined?(options[:do]) do
quote do
@type t :: %__MODULE__{
unquote_splicing(field_types(record_fields))
}
end
end
end
import unquote(__MODULE__).Helpers
def wrap(_cause, options \\ [])
def wrap({:error, error}, options),
do: wrap(error, options)
def wrap(%ErlangError{original: error}, options),
do: wrap(error, options)
def wrap(cause, options), do: struct(__MODULE__, Keyword.put(options, :cause, cause))
defoverridable wrap: 1, wrap: 2
@impl Exception
def message(exc), do: unquote(__MODULE__).default_message(exc)
defoverridable message: 1
unquote(Keyword.get(options, :do, :nop))
defimpl String.Chars do
def to_string(exc), do: unquote(name).message(exc)
end
end
end
end
defmacro define(name, fields, options) when is_list(fields) do
options = fields ++ options
quote do
unquote(__MODULE__).define(unquote(name), unquote(options))
end
end
def default_message(exc) do
default_name = exc.__struct__ |> to_string() |> String.split(".") |> List.last()
msg = Map.get(exc, :message) || default_name
if exc.cause do
"#{msg}; cause: #{Helpers.to_s(exc.cause)}"
else
msg
end
end
defp prepare_record_fields(options) do
fields = options |> Enum.reject(&match?({:do, _}, &1))
if Enum.empty?(fields), do: [:message], else: fields
end
defp field_types(fields) do
Enum.map(fields, fn
{key, _} -> {key, :term}
key -> {key, :term}
end)
end
defp type_defined?(nil), do: false
defp type_defined?({:__block__, _, children}) when is_list(children),
do: Enum.any?(children, &struct_typespec?/1)
defp type_defined?(expr), do: struct_typespec?(expr)
defp struct_typespec?({:@, _, [{:type, _, [{:"::", _, [{:t, _, _} | _]}]}]}), do: true
defp struct_typespec?(_), do: false
end