Packages

Initially build to reduce the author's pain point when using erlang tuple style error handling where it has no information of who create the error tuple.

Current section

Files

Jump to
errx lib errx.ex
Raw

lib/errx.ex

defmodule Errx do
@type t :: %Errx{}
defexception [:file, :func, :reason, :metadata, :parent]
defp loc(stack) do
{mod, fname, farity, [file: file, line: line]} =
Process.info(self(), :current_stacktrace) |> elem(1) |> Enum.fetch!(stack)
func = "#{mod}.#{fname}/#{farity}"
file = "#{file}:#{line}"
{func, file}
end
defp wrap_with_loc(error, stack_skip) do
{func, file} = loc(stack_skip)
case error do
%Errx{} ->
error
{:error, reason} ->
%Errx{func: func, file: file, reason: reason}
reason ->
%Errx{func: func, file: file, reason: reason}
end
end
@spec wrap(any) :: Errx.t()
def wrap(error) do
wrap_with_loc(error, 3)
end
@spec wrap(any, any) :: Errx.t()
def wrap(parent_error, child_error) do
%Errx{wrap_with_loc(child_error, 4) | parent: wrap_with_loc(parent_error, 4)}
end
@spec metadata(any, any) :: Errx.t()
def metadata(error, metadata) do
%Errx{wrap_with_loc(error, 3) | metadata: metadata}
end
@spec metadata(any) :: any
def metadata(error) do
case error do
%Errx{} ->
error.metadata
_ ->
nil
end
end
defmacro match(reason) do
quote do
%Errx{reason: unquote(reason)}
end
end
@spec match(any, any) :: boolean
def match(%Errx{reason: err1}, {:error, err2}) do
err1 == err2
end
def match({:error, err1}, %Errx{reason: err2}) do
err1 == err2
end
def match(err1, %Errx{reason: err2}) do
err1 == err2
end
def match(%Errx{reason: err1}, err2) do
err1 == err2
end
@impl true
def exception(attributes) when is_atom(attributes) do
wrap_with_loc(attributes, 3)
end
@impl true
def exception(attributes) when is_bitstring(attributes) do
%Errx{wrap_with_loc(:errx_exception, 4) | metadata: %{message: attributes}}
end
@impl true
def exception(%Errx{} = attributes) do
attributes
end
@impl true
def exception(attributes) do
%Errx{wrap_with_loc(:errx_exception, 4) | metadata: %{data: attributes}}
end
@impl true
@spec message(Errx.t()) :: binary
def message(%Errx{} = error) do
case error do
%Errx{metadata: %{message: reason}} when is_bitstring(reason) ->
reason
%Errx{reason: reason} when is_atom(reason) ->
Atom.to_string(reason)
%Errx{reason: reason} ->
inspect(reason)
_ ->
inspect(error)
end
end
end