ok v2.3.0 OK View Source

The OK module enables clean and expressive error handling when coding with idiomatic :ok/:error tuples. We’ve included many examples in the function docs here, but you can also check out the README for more details and usage.

Feel free to open an issue for any questions that you have.

Link to this section Summary

Guards

Checks if a result tuple is tagged as :error, and returns true if so. If the tuple is tagged as :ok, returns false

Checks if a result tuple is tagged as :ok, and returns true if so. If the tuple is tagged as :error, returns false

Functions

Takes a result tuple, a predicate function, and an error reason. If the result tuple is tagged as a success then its value will be passed to the predicate function. If the predicate returns true, then the result tuple stay the same. If the predicate returns false, then the result tuple becomes {:error, reason}. If the tag is failure then the predicate function is skipped

Creates a failed result tuple with the given reason

Checks if a result tuple is tagged as :error, and returns true if so. If the tuple is tagged as :ok, returns false

Takes a result tuple and a next function. If the result tuple is tagged as a success then its value will be passed to the next function. If the tag is failure then the next function is skipped

Lightweight notation for working with the values from serval failible components

Applies a function to the interior value of a result tuple

Transform every element of a list with a mapping function. The mapping function must return a result tuple

Require a variable not to be nil

Wraps a value as a successful result tuple

Checks if a result tuple is tagged as :ok, and returns true if so. If the tuple is tagged as :error, returns false

Handle return value from several failible functions

Wraps any term in an :ok tuple, unless already a result monad

Pipeline version of map/2

The OK result pipe operator ~>>, or result monad flat_map operator, is similar to Elixir’s native |> except it is used within happy path. It takes the value out of an {:ok, value} tuple and passes it as the first argument to the function call on the right

Link to this section Guards

Link to this guard is_failure(result) View Source (macro)
is_failure(term()) :: Macro.t()

Checks if a result tuple is tagged as :error, and returns true if so. If the tuple is tagged as :ok, returns false.

Allowed in guards.

Examples

iex> require OK
...> f = fn result when OK.is_failure(result) -> "error" end
...> f.({:error, :some_reason})
"error"

iex> require OK
...> f = fn result when OK.is_failure(result) -> "error" end
...> f.({:ok, "some value"})
** (FunctionClauseError) no function clause matching in anonymous fn/1 in OKTest."doctest OK.is_failure/1 (28)"/1

iex> require OK
...> f = fn result when OK.is_failure(result) -> "error" end
...> f.(nil)
** (FunctionClauseError) no function clause matching in anonymous fn/1 in OKTest."doctest OK.is_failure/1 (29)"/1
Link to this guard is_success(result) View Source (macro)
is_success(term()) :: Macro.t()

Checks if a result tuple is tagged as :ok, and returns true if so. If the tuple is tagged as :error, returns false.

Allowed in guards.

Examples

iex> require OK
...> f = fn result when OK.is_success(result) -> "ok" end
...> f.({:ok, "some value"})
"ok"

iex> require OK
...> f = fn result when OK.is_success(result) -> "ok" end
...> f.({:error, :some_reason})
** (FunctionClauseError) no function clause matching in anonymous fn/1 in OKTest."doctest OK.is_success/1 (31)"/1

iex> require OK
...> f = fn result when OK.is_success(result) -> "ok" end
...> f.(nil)
** (FunctionClauseError) no function clause matching in anonymous fn/1 in OKTest."doctest OK.is_success/1 (32)"/1

Link to this section Functions

Link to this function check(arg, func, reason) View Source
check({:ok, a}, (a -> boolean()), test_failure_reason) ::
  {:ok, a} | {:error, test_failure_reason}
when a: any(), test_failure_reason: any()
check({:error, reason}, (a -> boolean()), test_failure_reason) ::
  {:error, reason}
when a: any(), reason: any(), test_failure_reason: any()

Takes a result tuple, a predicate function, and an error reason. If the result tuple is tagged as a success then its value will be passed to the predicate function. If the predicate returns true, then the result tuple stay the same. If the predicate returns false, then the result tuple becomes {:error, reason}. If the tag is failure then the predicate function is skipped.

Examples

iex> OK.check({:ok, 2}, fn (x) -> x == 2 end, :bad_value)
{:ok, 2}

iex> OK.check({:ok, 2}, fn (x) -> x == 3 end, :bad_value)
{:error, :bad_value}

iex> OK.check({:error, :some_reason}, fn (x) -> x == 4 end, :bad_value)
{:error, :some_reason}
Link to this macro failure(reason) View Source (macro)

Creates a failed result tuple with the given reason.

Examples

iex> OK.failure("reason")
{:error, "reason"}
Link to this function failure?(arg) View Source
failure?({:ok, a}) :: false when a: any()
failure?({:error, reason}) :: true when reason: any()

Checks if a result tuple is tagged as :error, and returns true if so. If the tuple is tagged as :ok, returns false.

Examples

iex> OK.failure?({:error, :some_reason})
true

iex> OK.failure?({:ok, "some value"})
false
Link to this function flat_map(arg, func) View Source
flat_map({:ok, a} | {:error, reason}, (a -> {:ok, b} | {:error, reason})) ::
  {:ok, b} | {:error, reason}
when a: any(), b: any(), reason: any()

Takes a result tuple and a next function. If the result tuple is tagged as a success then its value will be passed to the next function. If the tag is failure then the next function is skipped.

Examples

iex> OK.flat_map({:ok, 2}, fn (x) -> {:ok, 2 * x} end)
{:ok, 4}

iex> OK.flat_map({:error, :some_reason}, fn (x) -> {:ok, 2 * x} end)
{:error, :some_reason}

Lightweight notation for working with the values from serval failible components.

Values are extracted from an ok tuple using the in (<-) operator. Any line using this operator that trys to match on an error tuple will result in early return.

If all bindings can be made, i.e. all functions returned {:ok, value}, then the after block is executed to return the final value.

Return values from the after block are wrapped as an ok result, unless they are already a result tuple. The return value of a for comprehension is always a result monad

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...> after
...>   a + b
...> end
{:ok, 6.0}

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...> after
...>   OK.success(a + b)
...> end
{:ok, 6.0}

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   _ <- safe_div(a, 2)
...> after
...>   {:error, :something_else}
...> end
{:error, :something_else}

Regular matching using the = operator is also available, for calculating intermediate values.

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b = 2.0
...> after
...>   a + b
...> end
{:ok, 6.0}

iex> OK.for do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 0) # error here
...> after
...>   a + b               # does not execute this line
...> end
{:error, :zero_division}

iex> OK.for do: :literal, after: :result
{:ok, :result}
Link to this function map(arg, func) View Source
map({:ok, a}, (a -> b)) :: {:ok, b} when a: any(), b: any()
map({:error, reason}, (any() -> any())) :: {:error, reason}
when reason: any()

Applies a function to the interior value of a result tuple.

If the tuple is tagged :ok the value will be mapped by the function. A tuple tagged :error will be unchanged.

Examples

iex> OK.map({:ok, 2}, fn (x) -> 2 * x end)
{:ok, 4}

iex> OK.map({:error, :some_reason}, fn (x) -> 2 * x end)
{:error, :some_reason}
Link to this function map_all(list, func) View Source
map_all([a], (a -> {:ok, b} | {:error, reason})) ::
  {:ok, [b]} | {:error, reason}
when a: any(), b: any(), reason: any()

Transform every element of a list with a mapping function. The mapping function must return a result tuple.

If all of the result tuples are tagged :ok, then it returns a list tagged with :ok. If one or more of the result tuples are tagged :error, it returns the first error.

Examples

iex> OK.map_all(1..3, &safe_div(6, &1))
{:ok, [6.0, 3.0, 2.0]}

iex> OK.map_all([-1, 0, 1], &safe_div(6, &1))
{:error, :zero_division}
Link to this function required(value, reason \\ :value_required) View Source
required(any(), any()) :: {:ok, any()} | {:error, any()}

Require a variable not to be nil.

Optionally provide a reason why variable is required.

Examples

iex> OK.required(:some)
{:ok, :some}

iex> OK.required(nil)
{:error, :value_required}

iex> OK.required(Map.get(%{}, :port), :port_number_required)
{:error, :port_number_required}
Link to this macro success(value) View Source (macro)

Wraps a value as a successful result tuple.

Examples

iex> OK.success(:value)
{:ok, :value}
Link to this function success?(arg) View Source
success?({:ok, a}) :: true when a: any()
success?({:error, reason}) :: false when reason: any()

Checks if a result tuple is tagged as :ok, and returns true if so. If the tuple is tagged as :error, returns false.

Examples

iex> OK.success?({:ok, "some value"})
true

iex> OK.success?({:error, :some_reason})
false

Handle return value from several failible functions.

Values are extracted from an ok tuple using the in (<-) operator. Any line using this operator that trys to match on an error tuple will result in early return.

If all bindings can be made, i.e. all functions returned {:ok, value}, then the after block is executed to return the final value.

If any binding fails then the rescue block will be tried.

Note: return value from after will be returned unwrapped

Examples

iex> OK.try do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 2)
...> after
...>   a + b
...> rescue
...>   :zero_division ->
...>     :nan
...> end
6.0

iex> OK.try do
...>   a <- safe_div(8, 2)
...>   b <- safe_div(a, 0)
...> after
...>   a + b
...> rescue
...>   :zero_division ->
...>     :nan
...> end
:nan

Wraps any term in an :ok tuple, unless already a result monad.

Examples

iex> OK.wrap("value")
{:ok, "value"}

iex> OK.wrap({:ok, "value"})
{:ok, "value"}

iex> OK.wrap({:error, "reason"})
{:error, "reason"}

Pipeline version of map/2.

Examples

iex> {:ok, 5} ~> Integer.to_string
{:ok, "5"}

iex> {:error, :zero_division_error} ~> Integer.to_string
{:error, :zero_division_error}

iex> {:ok, "a,b"} ~> String.split(",")
{:ok, ["a", "b"]}

The OK result pipe operator ~>>, or result monad flat_map operator, is similar to Elixir’s native |> except it is used within happy path. It takes the value out of an {:ok, value} tuple and passes it as the first argument to the function call on the right.

It can be used in several ways.

Pipe to a local call.
(This is equivalent to calling double(5))

iex> {:ok, 5} ~>> double()
{:ok, 10}

Pipe to a remote call.
(This is equivalent to calling OKTest.double(5))

iex> {:ok, 5} ~>> OKTest.double()
{:ok, 10}

iex> {:ok, 5} ~>> __MODULE__.double()
{:ok, 10}

Pipe with extra arguments.
(This is equivalent to calling safe_div(6, 2))

iex> {:ok, 6} ~>> safe_div(2)
{:ok, 3.0}

iex> {:ok, 6} ~>> safe_div(0)
{:error, :zero_division}

It also works with anonymous functions.

iex> {:ok, 3} ~>> (fn (x) -> {:ok, x + 1} end).()
{:ok, 4}

iex> {:ok, 6} ~>> decrement().(2)
{:ok, 4}

When an error is returned anywhere in the pipeline, it will be returned.

iex> {:ok, 6} ~>> safe_div(0) ~>> double()
{:error, :zero_division}

iex> {:error, :previous_bad} ~>> safe_div(0) ~>> double()
{:error, :previous_bad}