Errors modeled as data.
def deps do
[
{:error, "~> 0.1.0"}
]
end
Errors come in two kinds:
:domain
errors, which are part of your business model, or Domain in DDD parlance. You can use the map in the second argument position to supply whatever extra details you find useful:
Error.domain(:invalid_username, %{
provided_username: "alex",
unmet_requirement: "Must start with capital letter"}
})
:infra
errors, which represent failures of the infrastructure or computation substrate. Similarly, you can supply a map as the second argument, with arbitrary details of your choosing.
Error.infra(:db_down, %{retried_count: 5})
Errors should be created at the place where they occur. Domain errors can be later converted to user-facing messages, in the presentation logic.
Infra errors, on the other hand, most often translate to a "500 error" and the only piece of information that an end-user needs to know is that "Something went wrong". At the same time, you want to log these errors extensively so that you can investigate the issue later.
Errors provide a convenient to_map
function, so that your view logic doesn't
need to know about the Error
type.
If you'd like to pattern-match on errors, it's possible to match directly on the
Error.InfraError
and Error.DomainError
structs, however it's strongly discouraged.
The best way of testing for domain or infra errors are dedicated guards. Import the guard is_error
,
or the specific guards is_infra_error
and is_domain_error
and use them in the match clauses:
import Error, only: [is_error: 1]
case something() do
{:ok, %MyItem{} = i} -> handle(i)
{:error, e} when is_error(e) -> Error.reason(e)
{:error, :some_atom} -> other_path()
end
Hosted on github.