Description
Errors is an Elixir package that adds debugging context to error reasons. It is meant to be used in the tagged tuple style of error handling, where a function may return {:ok, result} or {:error, reason}.
errors alternatives and similar packages
Based on the "Errors and Exception Handling" category.
Alternatively, view errors alternatives based on common mentions on social networks and blogs.
Do you think we are missing an alternative of errors or a related project?
Popular Comparisons
README
Errors is an Elixir package that adds debugging context to error reasons. It is meant to be used in the tagged tuple style of error handling, where a function may return {:ok, result}
or {:error, reason}
.
Motivation
To illustrate why this might be useful, consider the following code snippet that fetches the contents of a file from a GitHub repo:
defmodule HTTP do
def get(url) do
HTTPoison.get(url)
end
end
defmodule GitHub do
def file({user, repo, file}) do
HTTP.get("https://raw.githubusercontent.com/#{user}/#{repo}/master/#{file}")
end
end
defmodule Repo do
def read(path) do
GitHub.file({"nucleartide", "errors", path})
end
end
# iex> Repo.read("notafile.txt")
# ...> {:error, %HTTPoison.Error{id: nil, reason: :closed}}
Here, we see that the Repo.read/1
operation failed, but the error reason isn't particularly helpful. Where does the HTTPoison.Error
come from? What was :closed
?
This error is even more opaque if Repo.read/1
comes from a third-party library.
Solution
Errors clarifies error reasons by annotating reasons with a message and stack trace. Here's how you would refactor the code above to provide additional debugging context:
defmodule HTTP do
def get(url) do
with res = {:ok, _} <- HTTPoison.get(url) do
res
else
{:error, e} -> {:error, Errors.wrap(e, "http request failed")}
end
end
end
defmodule GitHub do
defp url({user, repo, file}) do
"https://raw.githubusercontent.com/#{user}/#{repo}/master/#{file}"
end
def file(github_file) do
with res = {:ok, _} <- HTTP.get(github_file |> url()) do
res
else
{:error, e} -> {:error, Errors.wrap(e, "couldn't fetch github file")}
end
end
end
defmodule Repo do
def read(path) do
with res = {:ok, _} <- GitHub.file({"nucleartide", "errors", path}) do
res
else
{:error, e} -> {:error, Errors.wrap(e, "couldn't read from #{path}")}
end
end
end
# iex> {:error, err} = Repo.read("notafile.txt")
# ...> {:error, %Errors.WrappedError{...}}
We can print this new Errors.WrappedError
to retrieve our annotated messages:
iex> IO.puts(err)
couldn't read from notafile.txt: couldn't fetch github file: http request failed: closed
Or inspect the error for a stack trace:
iex> IO.inspect(err)
** (Errors.WrappedError) couldn't read from notafile.txt
example.exs:27: HTTP.get/1
** (Errors.WrappedError) couldn't fetch github file
example.exs:17: GitHub.file/1
** (Errors.WrappedError) http request failed
example.exs:6: Repo.read/1
** (HTTPoison.Error) closed
Install
Add :errors
to your deps
in mix.exs
:
def deps do
[
{:errors, "~> 0.1.0"}
]
end
API
Errors exposes three primary macros/functions:
Errors.wrap/2
@spec wrap(error :: Exception.t, message :: String.t) :: Macro.t
defmacro wrap(error, message \\ "")
Use Errors.wrap/2
to "wrap" tagged errors received from other functions:
defmodule YourModule do
require Errors
def get() do
with res = {:ok, _} <- HTTPoison.get("https://www.google.com/") do
res
else
{:error, e} -> {:error, Errors.wrap(e, "can't ping google")}
end
end
end
Errors.new/1
@spec new(message :: String.t) :: Macro.t
defmacro new(message \\ "")
Use Errors.new/1
as a general replacement for custom Exception structs:
defmodule YourModule do
require Errors
def transform("https://" <> rest_of_link),
do: {:ok, rest_of_link}
def transform(_),
do: {:error, Errors.new("missing https:// prefix")}
end
Errors.cause/1
@spec cause(error :: Errors.Cause.t | any) :: Exception.t | any
def cause(error)
Errors.cause/1
returns the unwrapped "cause" of an error.
The passed-in error
should implement the Errors.Cause
protocol; errors returned by Errors.wrap/2
and Errors.new/1
implement Errors.Cause
already:
require Errors
%RuntimeError{message: "this is an error"}
|> Errors.wrap("uh-oh")
|> Errors.cause() # => %RuntimeError{message: "this is an error"}
WrappedError
Errors.wrap/2
and Errors.new/1
return a WrappedError
. WrappedError
implements the String.Chars and Inspect protocols, so you can freely print and inspect:
e = %RuntimeError{} |> Errors.wrap("uh-oh")
IO.puts(e) # will print "uh-oh: runtime error"
IO.inspect(e) # will print a stack trace
Hexdocs
See the hexdocs for more details and links to source code.
Feedback, issues, concerns
Please open an issue!
Links
Jason Tu · GitHub @nucleartide · Twitter @nucleartide