Current section
Files
Jump to
Current section
Files
lib/oxide.ex
defmodule Oxide do
@moduledoc false
end
defmodule Oxide.Result do
@moduledoc """
Helpers for working with result tuples, `{:ok, value}` and `{:error, reason}`.
Unless otherwise stated, functions raise `FunctionClauseError` when given an unexpected
non-result.
"""
@doc ~S"""
## Examples
iex> Result.is_ok?({:ok, 3})
true
iex> Result.is_ok?({:error, 3})
false
"""
def is_ok?(result)
def is_ok?({:ok, _}), do: true
def is_ok?({:error, _}), do: false
@doc ~S"""
Wrap a value in an ok result.
"""
def ok(t), do: {:ok, t}
@doc ~S"""
## Examples
iex> Result.is_error?({:ok, 3})
false
iex> Result.is_error?({:error, 3})
true
"""
def is_error?(result)
def is_error?({:ok, _}), do: false
def is_error?({:error, _}), do: true
@doc ~S"""
Wrap a value in an error result.
iex> :some_error_reason |> Result.error()
{:error, :some_error_reason}
"""
def error(e), do: {:error, e}
@doc ~S"""
Unwrap an `:ok` result, and raise an `:error` reason.
iex> Result.unwrap!({:ok, :value})
:value
iex> Result.unwrap!({:error, "message"})
** (RuntimeError) message
"""
def unwrap!(result)
def unwrap!({:ok, t}), do: t
def unwrap!({:error, e}), do: raise(e)
@doc ~S"""
Unwrap an `:ok` result, falling back to `default` for an `:error` result.
iex> Result.unwrap_or({:ok, :cake}, :icecream)
:cake
iex> Result.unwrap_or({:error, :peas}, :icecream)
:icecream
"""
def unwrap_or(result, default)
def unwrap_or({:ok, t}, _default), do: t
def unwrap_or({:error, _}, default), do: default
@doc ~S"""
Unwrap an `:ok` result, falling back to executing a zero-arity function if the result
is an error.
iex> Result.unwrap_or_else({:ok, :cake}, fn -> :icecream end)
:cake
iex> Result.unwrap_or_else({:error, :peas}, fn -> :icecream end)
:icecream
"""
def unwrap_or_else(result, f)
def unwrap_or_else({:ok, t}, _f), do: t
def unwrap_or_else({:error, _}, f), do: f.()
def unwrap_err!(result)
def unwrap_err!({:ok, _}), do: raise("called `Result.unwrap_err()` on an `:ok` value")
def unwrap_err!({:error, e}), do: e
# def expect_err
@doc ~S"""
Map the value of an `:ok` result to another `:ok` result, leaving errors unchanged.
iex> Result.map({:ok, 3}, fn x -> x + 1 end)
{:ok, 4}
iex> Result.map({:error, :nan}, fn x -> x + 1 end)
{:error, :nan}
"""
def map(result, f)
def map({:ok, t}, f), do: {:ok, f.(t)}
def map({:error, e}, _f), do: {:error, e}
def map_or(result, default, f)
def map_or({:ok, t}, _default, f), do: f.(t)
def map_or({:error, _}, default, _f), do: default
def map_err(result, f)
def map_err({:ok, t}, _f), do: {:ok, t}
def map_err({:error, e}, f), do: {:error, f.(e)}
def and_then(result, f)
def and_then({:ok, t}, f), do: f.(t)
def and_then({:error, e}, _f), do: {:error, e}
@doc ~S"""
Convert a maybe-nil value to a result.
Maps `nil` to `{:error, reason}` and any non-`nil` value to `{:ok, value}`.
iex> %{"key" => "value"} |> Map.get("key") |> Result.err_if_nil(:notfound)
{:ok, "value"}
iex> %{"key" => "value"} |> Map.get("missing") |> Result.err_if_nil(:notfound)
{:error, :notfound}
"""
def err_if_nil(value, reason) when is_nil(value), do: {:error, reason}
def err_if_nil(value, _reason) when not is_nil(value), do: {:ok, value}
@doc ~S"""
Result pipe operator.
The result pipe operator `~>` is a result-aware analogue to the pipe operator `|>`.
It allows chaining functions that return results, piping the inner value of `:ok` results
and short-circuiting the pipeline if any of the functions return an error. For example
with {:ok, x1} <- f1(x),
{:ok, x2} <- f2(x1) do
f3(x2)
end
can be written as
x |> f1() ~> f2() ~> f3()
"""
defmacro left ~> right do
quote do
case unquote(left) do
{:ok, t} -> t |> unquote(right)
{:error, e} -> {:error, e}
end
end
end
end