Skip to content

vic/happy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Happy

help maintain this lib

the alchemist's happy path with elixir

If you are using Elixir >= 1.2, you might prefer happy_with

Installation

Available in Hex, the package can be installed as:

  1. Add happy to your list of dependencies in mix.exs:
  def deps do
    [{:happy, "~> 1.3.1"}]
  end

About

Ok, 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.

Usage

import Happy
the happy_path macro

The 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)
end

gets rewritten to something like:

case(a) do
  {:ok, b} ->
    case (b) do
      {:ok, d} -> c(d)
    end
end

Note 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
end
handling errors with else clauses

If 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
end
sharing common error handling code

Sometimes 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
end

gets rewritten to something like:

case foo do
  {:ok, x} -> 
    x + 1
  {:error, y} ->
    y
  err -> 
    err |> handler
end
support for guards

Just like with case you can include guard tests.

happy_path do
  x when not is_nil(x) = some(foo)
  x + 1
end
tags

Tags 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"
end

Tags 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.

@happy tag

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"}
end

produces just:

{this, would} = {"always", "match"}
Example usage in a web application creating a user
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

Is it any good?

Yes