the alchemist's happy path with elixir
If you are using Elixir >= 1.2, you might prefer happy_with
Available in Hex, the package can be installed as:
- Add happy to your list of dependencies in
mix.exs:
def deps do
[{:happy, "~> 1.3.1"}]
endOk, so I was just trying to find a nice way (beautiful syntax, yet flexible enough) to handle
errors in elixir. Handling :ok/:error like tuples without lots of if/cases.
After creating ok_jose, looking at Elixir's with special form and other alternatives, I wanted to create this tiny library with the following goals in mind:
- The happy path must be immediately obvious to the eyes.
- Code should not be cluttered and should just work using the elixir you already know.
- Avoid introducing noisy operators
~>>, requiring commas after each pattern - Should provide a way to recover when not so happy moments come.
If you dont need special features like tags, and are using elixir ~> 1.2, checkout happy_with wich is just tiny sugar around with special form.
import HappyThe happy_path macro takes a do block and rewrites any first-level pattern matching expression into a case.
happy_path do
{:ok, b} = a
{:ok, d} = b
c(d)
endgets rewritten to something like:
case(a) do
{:ok, b} ->
case (b) do
{:ok, d} -> c(d)
end
endNote that a variable pattern match (assignment) is not rewritten, as it will always match and would cause warnings.
happy_path do
x = some(thing) # simple assignment is left as is
endIf you want to handle non-matching values,
provide an else block with additional
matching clauses:
happy_path do
{:ok, b} = a
c(b)
else
{:error, x} -> x
endSometimes you would want to share common error handling code on many happy_paths, for example in an api controller with many actions, all of which handle common invalid cases like parameter validation.
In those cases you can provide happy_path with an
default error handler as first argument. Note that if no local
else clause matches, the error value is piped into
the provided error handler. Thus the handler is anything
you can pipe the error value into.
happy_path(else: handler) do
{:ok, x} = foo
x + 1
else
{:error, y} -> y
endgets rewritten to something like:
case foo do
{:ok, x} ->
x + 1
{:error, y} ->
y
err ->
err |> handler
endJust like with case you can include guard tests.
happy_path do
x when not is_nil(x) = some(foo)
x + 1
endTags is an special feature of happy_path not found on
alternatives like elixir's with expression.
Tags look like module attributes but they are not, they are just shorthand for tagging a pattern.
happy_path do
# using the `foo` tag
@foo {:ok, x} = y
# is exactly the same as
{:foo, {:ok, x}} = {:foo, y}
else
{:foo, {:error, e}} -> "Foo error"
endTags can help error handlers to get a clue about which
context the mismatch was produced on. It's mostly useful
for distingishing between lots of {:error, _} like tuples.
The special tag @happy lets you mark a pattern matching expression
to be skipped by happy_path. For example when you know something
will always match.
happy_path do
@happy {this, would} = {"always", "match"}
endproduces just:
{this, would} = {"always", "match"}happy_path do
%{valid?: true} = ch = User.changeset(params)
{:ok, user} = Repo.insert(ch)
render(conn, "user.json", user: user)
else
%{valid?: false} = ch -> render(conn, "validation_errors.json", ch: ch)
{:error, ch} -> render(conn, "db_error.json", ch: ch)
_ -> text(conn, "error")
end