Error handling for Elixir. Let it flow.

Err is a tiny library for composing and normalizing error flows in Elixir.

It works with the conventions Elixir developers already use:

  • {:ok, value} and {:error, reason}
  • nil as absence
  • existing return values from Phoenix, Ecto, Oban, and your own code

Instead of introducing a new result type or DSL, Err helps turn mixed return styles into flows that are easier to compose, transform, and reason about.

Use it to:

  • compose result pipelines cleanly
  • normalize nil, tuples, and exception-based APIs
  • transform errors close to the boundary
  • keep with-based application code readable

Features

Installation

Add err to your list of dependencies in mix.exs:

def deps do
  [
    {:err, "~> 0.2"}
  ]
end

Why Err?

Elixir already has excellent primitives for error handling: pattern matching, with, case, and tagged tuples.

The friction usually starts when application code combines several styles from several libraries:

  • Repo.get/2 returns nil
  • Repo.insert/2 returns {:ok, struct} or {:error, changeset}
  • some APIs raise exceptions
  • others return custom tuples or atoms

Err is a small glue layer for normalizing those differences.

Usage

Wrap values

iex> Err.ok(42)
{:ok, 42}

iex> Err.error(:timeout)
{:error, :timeout}

Normalize nil into a result

iex> Err.from_nil("config.json", :not_found)
{:ok, "config.json"}

iex> Err.from_nil(nil, :not_found)
{:error, :not_found}

Convert raising code into a result

iex> Err.try_rescue(fn -> 100 + 23 end)
{:ok, 123}

iex> Err.try_rescue(fn -> raise "boom" end) |> Err.map_err(&Exception.message/1)
{:error, "boom"}

Run Task work through results

iex> task = Err.async(fn -> 40 + 2 end)
iex> Err.await(task)
{:ok, 42}

iex> [Task.async(fn -> 1 end), Task.async(fn -> {:error, :boom} end)] |> Err.await_many()
[{:ok, 1}, {:error, :boom}]

Unwrap with defaults

iex> Err.unwrap_or({:ok, "config.json"}, "default.json")
"config.json"

iex> Err.unwrap_or({:error, :not_found}, "default.json")
"default.json"

Transform success values

iex> Err.map({:ok, 5}, fn num -> num * 2 end)
{:ok, 10}

Transform error values

iex> Err.map_err({:error, :timeout}, fn reason -> "#{reason}_error" end)
{:error, "timeout_error"}

Chain operations

iex> Err.and_then({:ok, 5}, fn num -> {:ok, num * 2} end)
{:ok, 10}

Add side effects without changing the result

iex> Err.tap({:ok, 5}, fn value -> send(self(), {:seen, value}) end)
{:ok, 5}

iex> Err.tap_err({:error, :timeout}, fn reason -> send(self(), {:seen_error, reason}) end)
{:error, :timeout}

Branch explicitly at the boundary

iex> Err.match({:ok, 5}, ok: &(&1 * 2), error: fn _ -> 0 end)
10

iex> Err.match(nil, ok: & &1, error: fn _ -> :missing end)
:missing

Flatten nested results

iex> Err.flatten({:ok, {:ok, 1}})
{:ok, 1}

Eager fallback

iex> Err.or_else({:error, :cache_miss}, {:ok, "disk.db"})
{:ok, "disk.db"}

Lazy fallback

iex> Err.or_else_lazy({:error, :cache_miss}, fn _reason -> {:ok, "disk.db"} end)
{:ok, "disk.db"}

Combine results (fail fast)

iex> Err.all([{:ok, 1}, {:ok, 2}, {:ok, 3}])
{:ok, [1, 2, 3]}

iex> Err.all([{:ok, 1}, {:error, :timeout}])
{:error, :timeout}

Extract ok values

iex> Err.values([{:ok, 1}, {:error, :x}, {:ok, 2}])
[1, 2]

Split into ok and error lists

iex> Err.partition([{:ok, 1}, {:error, "a"}, {:ok, 2}])
{[1, 2], ["a"]}

Check if result is ok

def process(result) when Err.is_ok(result) do
  result
end

Check if result is error

def process(result) when Err.is_err(result) do
  result
end

Real-World Example

def fetch_user_profile(id) do
  with {:ok, user} <- Repo.get(User, id) |> Err.from_nil(:not_found),
       {:ok, account} <- Accounts.fetch_account(user) |> Err.map_err(&normalize_error/1),
       {:ok, stats} <- Stats.fetch(account) |> Err.map_err(&normalize_error/1) do
    {:ok, %{user: user, account: account, stats: stats}}
  end
end

Err complements with, case, and pattern matching. It does not try to replace them.

Summary

Types

An option type representing either some value or none.

A result type representing either success or failure.

Either a result/0 or an option/0 type.

Functions

Combines a list of values into a single result.

Chains the result by calling fun when the value is present.

Starts a task and normalizes its return value into a result.

Awaits a task and converts its outcome into a result without exiting the caller.

Awaits multiple tasks and converts each outcome into a result without exiting the caller.

Ensures the value satisfies predicate, otherwise returns {:error, error}.

Returns true when the value is an error result and its payload satisfies predicate.

Wraps value in an {:error, value} tuple.

Returns the wrapped value from an {:ok, value} tuple or raises the provided exception.

Returns the wrapped error from an {:error, reason} tuple or raises the provided exception.

Flattens a nested result into a single layer.

Returns the second value if the first one is successful/present.

Normalizes a nullable value into a result.

Checks if a value is an {:error, ...} result tuple.

Checks if a value is none (nil).

Checks if a value is an {:ok, ...} result tuple.

Checks if a value is "some" (not nil).

Transforms the success value inside an {:ok, value} tuple or some value by applying a function to it.

Transforms the error inside an {:error, reason} tuple by applying a function to it.

Matches on success/presence and error/absence with explicit handlers.

Wraps value in an {:ok, value} tuple.

Returns true when the value is an ok result and its payload satisfies predicate.

Returns the first value if it is present/successful, otherwise returns the second value.

Returns the first value if it is present/successful, otherwise calls the function to compute an alternative value.

Splits a list of results into ok values and error values.

Replaces the value inside an {:ok, value} tuple with a new value.

Replaces the error inside an {:error, reason} tuple with a new value.

Replaces the error inside an {:error, reason} tuple by calling a function.

Replaces the value inside an {:ok, value} tuple by calling a function.

Returns true when the value is present and satisfies predicate.

Calls fun with the success value and returns the original value unchanged.

Calls fun with the error value and returns the original value unchanged.

Executes fun and converts rescued exceptions into an error result.

Returns the wrapped error or default when the result is ok or value is present.

Returns the wrapped value or default when the result is error or value is empty.

Returns the wrapped value or computes it from default_fun when the result is an error or value is empty.

Extracts all success values from a list of results.

Combines two successful/present values into a pair.

Types

option()

@type option() :: any() | nil

An option type representing either some value or none.

Can be:

  • value - Some value is present
  • nil - No value (none)

result()

@type result() :: tuple()

A result type representing either success or failure.

Can be:

  • {:ok, value} - A successful result with a value
  • {:error, error} - A failed result with an error
  • Any tuple starting with :ok or :error (supports multiple elements)

value()

@type value() :: result() | option()

Either a result/0 or an option/0 type.

Functions

all(values)

@spec all([value()]) :: value()

Combines a list of values into a single result.

  • If all values are {:ok, value}, returns {:ok, list_of_values}.
  • If any value is an error, returns the first error encountered (fail fast).
  • If any value is nil, returns nil

Examples

iex> Err.all([{:ok, 1}, {:ok, 2}, {:ok, 3}])
{:ok, [1, 2, 3]}

iex> Err.all([{:ok, 1}, {:error, :timeout}, {:ok, 3}])
{:error, :timeout}

iex> Err.all([{:ok, 1}, nil, {:ok, 3}])
nil

iex> Err.all([])
{:ok, []}

iex> Err.all([{:ok, "a"}, {:ok, "b"}])
{:ok, ["a", "b"]}

and_then(value, fun)

@spec and_then(value(), (any() -> any())) :: any()

Chains the result by calling fun when the value is present.

For {:ok, value} the extracted value (or list of values) is passed to fun. Error tuples and nil are returned unchanged, allowing the pipeline to short-circuit.

Examples

iex> Err.and_then({:ok, 5}, fn num -> num * 2 end)
10

iex> Err.and_then(5, fn num -> num * 2 end)
10

iex> Err.and_then({:ok, :admin, %{id: 1}}, fn [role, user] -> {:ok, %{role: role, user_id: user.id}} end)
{:ok, %{role: :admin, user_id: 1}}

iex> Err.and_then({:error, :timeout}, fn num -> {:ok, num * 2} end)
{:error, :timeout}

iex> Err.and_then(nil, fn value -> {:ok, value} end)
nil

async(fun)

@spec async((-> any())) :: Task.t()

Starts a task and normalizes its return value into a result.

Plain values are wrapped as {:ok, value}. Existing result tuples are returned unchanged. Rescued exceptions become {:error, exception}. Throws and exits are returned as tagged errors.

This is useful when adapting Task-based work into the same result flow used by synchronous code.

Examples

iex> task = Err.async(fn -> 40 + 2 end)
iex> Err.await(task)
{:ok, 42}

iex> task = Err.async(fn -> {:ok, :cached} end)
iex> Err.await(task)
{:ok, :cached}

iex> task = Err.async(fn -> raise "boom" end)
iex> Err.await(task) |> Err.map_err(&Exception.message/1)
{:error, "boom"}

await(task, timeout \\ 5000)

@spec await(Task.t(), timeout()) :: result()

Awaits a task and converts its outcome into a result without exiting the caller.

Plain task replies are wrapped as {:ok, value}. Existing result tuples are returned unchanged. If the task exits, returns {:error, {:exit, reason}}. If the timeout is reached, the task is shut down and {:error, :timeout} is returned.

Examples

iex> Task.async(fn -> 21 * 2 end) |> Err.await()
{:ok, 42}

iex> Task.async(fn -> {:error, :not_found} end) |> Err.await()
{:error, :not_found}

await_many(tasks, timeout \\ 5000)

@spec await_many([Task.t()], timeout()) :: [result()]

Awaits multiple tasks and converts each outcome into a result without exiting the caller.

Results are returned in the same order as the input tasks. Each reply follows the same normalization rules as await/2.

Examples

iex> [Task.async(fn -> 1 end), Task.async(fn -> {:error, :boom} end)] |> Err.await_many()
[{:ok, 1}, {:error, :boom}]

ensure(value, predicate, error)

@spec ensure(value(), (any() -> any()), any()) :: value()

Ensures the value satisfies predicate, otherwise returns {:error, error}.

Existing error tuples are returned unchanged. For successful result tuples the extracted value (or list of values) is passed to predicate. For plain values and option-style values, a truthy predicate keeps the original value and a falsy predicate returns {:error, error}.

Examples

iex> Err.ensure({:ok, 10}, &(&1 > 5), :too_small)
{:ok, 10}

iex> Err.ensure({:ok, 3}, &(&1 > 5), :too_small)
{:error, :too_small}

iex> Err.ensure({:error, :timeout}, &(&1 > 5), :too_small)
{:error, :timeout}

iex> Err.ensure("hello", &(String.length(&1) > 3), :too_short)
"hello"

iex> Err.ensure(nil, & &1, :missing)
{:error, :missing}

err_and?(value, predicate)

@spec err_and?(any(), (any() -> any())) :: boolean()

Returns true when the value is an error result and its payload satisfies predicate.

Returns false for non-error values without calling predicate.

Examples

iex> Err.err_and?({:error, :timeout}, &(&1 == :timeout))
true

iex> Err.err_and?({:error, :boom}, &(&1 == :timeout))
false

iex> Err.err_and?({:ok, 1}, &(&1 == :timeout))
false

error(value)

@spec error(any()) :: result()

Wraps value in an {:error, value} tuple.

Examples

iex> Err.error(:timeout)
{:error, :timeout}

iex> Err.error({:validation_failed, :email})
{:error, {:validation_failed, :email}}

expect!(value, exception)

@spec expect!(value(), Exception.t()) :: any()

Returns the wrapped value from an {:ok, value} tuple or raises the provided exception.

For two-element result tuples ({:ok, value}) it returns value. When the tuple contains additional metadata, it returns the remaining elements as a list.

If the value is {:error, _}, nil, or any other value, raises the provided exception.

Examples

iex> Err.expect!({:ok, "config.json"}, RuntimeError.exception("config not found"))
"config.json"

iex> Err.expect!({:ok, :user, %{role: :admin}}, RuntimeError.exception("user not found"))
[:user, %{role: :admin}]

expect_err!(value, exception)

@spec expect_err!(value(), Exception.t()) :: any()

Returns the wrapped error from an {:error, reason} tuple or raises the provided exception.

For two-element error tuples ({:error, reason}) it returns reason. When the tuple contains additional metadata, it returns the remaining elements as a list.

If the value is {:ok, _}, nil, or any other value, raises the provided exception.

Examples

iex> Err.expect_err!({:error, :timeout}, RuntimeError.exception("expected an error"))
:timeout

iex> Err.expect_err!({:error, 404, "Not Found"}, RuntimeError.exception("expected an error"))
[404, "Not Found"]

flatten(value)

@spec flatten(value()) :: result()

Flattens a nested result into a single layer.

If the outer result is {:ok, inner} and inner is also a result tuple, returns the inner result. Otherwise returns the value unchanged.

Examples

iex> Err.flatten({:ok, {:ok, 1}})
{:ok, 1}

iex> Err.flatten({:ok, {:ok, 1, :meta}})
{:ok, 1, :meta}

iex> Err.flatten({:ok, {:error, :timeout}})
{:error, :timeout}

iex> Err.flatten({:error, :failed})
{:error, :failed}

iex> Err.flatten({:ok, "value"})
{:ok, "value"}

followed_by(first, second)

@spec followed_by(value(), value()) :: value()

Returns the second value if the first one is successful/present.

For Result types ({:ok, value} or {:error, reason}), returns the second value if the first is {:ok, _}, otherwise returns the first error unchanged.

For Option types (nil or any value), returns the second value if the first is not nil, otherwise returns nil.

Examples

iex> Err.followed_by({:ok, 1}, {:ok, 2})
{:ok, 2}

iex> Err.followed_by({:ok, 1}, {:error, :boom})
{:error, :boom}

iex> Err.followed_by({:error, :timeout}, {:ok, 2})
{:error, :timeout}

iex> Err.followed_by("primary", "secondary")
"secondary"

iex> Err.followed_by(nil, "secondary")
nil

from_nil(value, error)

@spec from_nil(value(), any()) :: result()

Normalizes a nullable value into a result.

Returns {:ok, value} for any non-nil value. Returns {:error, reason} when the value is nil. Existing result tuples are returned unchanged.

This is useful for adapting APIs such as Repo.get/2 that return nil on absence into flows that work naturally with with, map_err/2, and or_else/2.

Examples

iex> Err.from_nil("config.json", :not_found)
{:ok, "config.json"}

iex> Err.from_nil(nil, :not_found)
{:error, :not_found}

iex> Err.from_nil({:ok, 1}, :not_found)
{:ok, 1}

iex> Err.from_nil({:error, :timeout}, :not_found)
{:error, :timeout}

is_err(value)

(macro)
@spec is_err(any()) :: boolean()

Checks if a value is an {:error, ...} result tuple.

Returns true for any tuple starting with :error, false otherwise.

Allowed in guard tests.

Examples

iex> Err.is_err({:error, :timeout})
true

iex> Err.is_err({:error, 404, "Not Found"})
true

iex> Err.is_err({:ok, 1})
false

iex> Err.is_err(nil)
false

iex> Err.is_err("error")
false

def my_function(result) when is_err(result)

is_none(value)

(macro)
@spec is_none(any()) :: boolean()

Checks if a value is none (nil).

Returns true only for nil.

Allowed in guard tests.

Examples

iex> Err.is_none(nil)
true

iex> Err.is_none(1)
false

iex> Err.is_none({:ok, 1})
false

def my_function(value) when Err.is_none(value)

is_ok(value)

(macro)
@spec is_ok(any()) :: boolean()

Checks if a value is an {:ok, ...} result tuple.

Returns true for any tuple starting with :ok, false otherwise.

Allowed in guard tests.

Examples

iex> Err.is_ok({:ok, 1})
true

iex> Err.is_ok({:ok, 1, 2})
true

iex> Err.is_ok({:error, :timeout})
false

iex> Err.is_ok(nil)
false

iex> Err.is_ok("value")
false

def my_function(result) when is_ok(result)

is_some(value)

(macro)
@spec is_some(any()) :: boolean()

Checks if a value is "some" (not nil).

Returns true for any value except nil.

Allowed in guard tests.

Examples

iex> Err.is_some(1)
true

iex> Err.is_some("hello")
true

iex> Err.is_some({:ok, 1})
true

iex> Err.is_some(false)
true

iex> Err.is_some(nil)
false

def my_function(value) when is_some(value)

map(value, fun)

@spec map(value(), (any() -> any())) :: value()

Transforms the success value inside an {:ok, value} tuple or some value by applying a function to it.

For Result types ({:ok, value} or {:error, reason}), applies the function to the value if it's {:ok, _}, otherwise returns the error unchanged.

For Option types (nil or any value), applies the function to the value if it's not nil, otherwise returns nil.

Examples

iex> Err.map({:ok, 5}, fn num -> num * 2 end)
{:ok, 10}

iex> Err.map({:ok, "hello"}, &String.upcase/1)
{:ok, "HELLO"}

iex> Err.map({:error, :timeout}, fn num -> num * 2 end)
{:error, :timeout}

iex> Err.map(nil, fn num -> num * 2 end)
nil

iex> Err.map("hello", &String.upcase/1)
"HELLO"

map_err(value, fun)

@spec map_err(value(), (any() -> any())) :: value()

Transforms the error inside an {:error, reason} tuple by applying a function to it.

For Result types ({:ok, value} or {:error, reason}), applies the function to the error if it's {:error, _}, otherwise returns the ok tuple unchanged.

Ignores nil and non-Result values, returning them unchanged.

Examples

iex> Err.map_err({:error, 404}, fn code -> "HTTP #{code}" end)
{:error, "HTTP 404"}

iex> Err.map_err({:ok, "success"}, fn reason -> "#{reason}_error" end)
{:ok, "success"}

iex> Err.map_err(nil, fn reason -> "#{reason}_error" end)
nil

iex> Err.map_err(404, fn reason -> "#{reason}_error" end)
404

match(value, handlers)

@spec match(value(), ok: (any() -> any()), error: (any() -> any())) :: any()

Matches on success/presence and error/absence with explicit handlers.

Existing result tuples dispatch to :ok or :error. nil dispatches to :error. Any other non-nil value dispatches to :ok.

Examples

iex> Err.match({:ok, 5}, ok: &(&1 * 2), error: fn _ -> 0 end)
10

iex> Err.match({:error, :timeout}, ok: & &1, error: &inspect/1)
":timeout"

iex> Err.match(nil, ok: & &1, error: fn _ -> :missing end)
:missing

iex> Err.match("value", ok: &String.upcase/1, error: fn _ -> :missing end)
"VALUE"

message(exception)

@spec message(struct()) :: String.t()

ok(value)

@spec ok(any()) :: result()

Wraps value in an {:ok, value} tuple.

Examples

iex> Err.ok(%{id: 1, email: "john@example.com"})
{:ok, %{email: "john@example.com", id: 1}}

iex> Err.ok({:ok, 100})
{:ok, {:ok, 100}}

ok_and?(value, predicate)

@spec ok_and?(any(), (any() -> any())) :: boolean()

Returns true when the value is an ok result and its payload satisfies predicate.

Returns false for non-ok values without calling predicate.

Examples

iex> Err.ok_and?({:ok, 10}, &(&1 > 5))
true

iex> Err.ok_and?({:ok, 3}, &(&1 > 5))
false

iex> Err.ok_and?({:error, :timeout}, &(&1 > 5))
false

or_else(first, second)

@spec or_else(value(), value()) :: value()

Returns the first value if it is present/successful, otherwise returns the second value.

For Result types ({:ok, value} or {:error, reason}), returns the first value if it's {:ok, _}, otherwise returns the second value.

For Option types (nil or any value), returns the first value if it's not nil, otherwise returns the second value.

Examples

iex> Err.or_else({:ok, "cache.db"}, {:ok, "disk.db"})
{:ok, "cache.db"}

iex> Err.or_else({:ok, "cache.db"}, {:error, :unavailable})
{:ok, "cache.db"}

iex> Err.or_else({:error, :cache_miss}, {:ok, "disk.db"})
{:ok, "disk.db"}

iex> Err.or_else({:error, :cache_miss}, {:error, :disk_full})
{:error, :disk_full}

iex> Err.or_else("primary", "backup")
"primary"

iex> Err.or_else(nil, "backup")
"backup"

or_else_lazy(value, fun)

@spec or_else_lazy(value(), (any() -> any())) :: value()

Returns the first value if it is present/successful, otherwise calls the function to compute an alternative value.

For Result types ({:ok, value} or {:error, reason}), returns the first value if it's {:ok, _}, otherwise calls the function with the error reason.

For Option types (nil or any value), returns the first value if it's not nil, otherwise calls the function.

This is the lazy version of or_else/2 - the function is only called when needed.

Examples

iex> Err.or_else_lazy({:ok, "cache.db"}, fn _ -> {:ok, "disk.db"} end)
{:ok, "cache.db"}

iex> Err.or_else_lazy({:error, :cache_miss}, fn _reason -> {:ok, "disk.db"} end)
{:ok, "disk.db"}

iex> Err.or_else_lazy({:error, :timeout}, fn reason -> {:error, "Fallback failed: #{reason}"} end)
{:error, "Fallback failed: timeout"}

iex> Err.or_else_lazy("primary", fn _ -> "backup" end)
"primary"

iex> Err.or_else_lazy(nil, fn _ -> "backup" end)
"backup"

partition(results)

@spec partition([value()]) :: {ok_values :: any(), error_values :: any()}

Splits a list of results into ok values and error values.

Returns a tuple {ok_values, error_values} where:

  • ok_values contains all values from {:ok, value} tuples
  • error_values contains all values from {:error, reason} tuples

Any other value is ignored.

Examples

iex> Err.partition([{:ok, 1}, {:error, "a"}, {:ok, 2}])
{[1, 2], ["a"]}

iex> Err.partition([1, nil])
{[], []}

iex> Err.partition([{:ok, "x"}, {:ok, "y"}])
{["x", "y"], []}

iex> Err.partition([{:error, :timeout}, {:error, :crash}])
{[], [:timeout, :crash]}

iex> Err.partition([])
{[], []}

replace(error, new_value)

@spec replace(value(), any()) :: value()

Replaces the value inside an {:ok, value} tuple with a new value.

If the result is {:ok, _}, returns {:ok, new_value}. Otherwise returns the original value unchanged.

Examples

iex> Err.replace({:ok, "old"}, "new")
{:ok, "new"}

iex> Err.replace({:error, :timeout}, 999)
{:error, :timeout}

iex> Err.replace(nil, 999)
nil

iex> Err.replace(100, 999)
100

replace_err(ok, new_error)

@spec replace_err(value(), any()) :: value()

Replaces the error inside an {:error, reason} tuple with a new value.

If the result is {:error, _}, returns {:error, new_error}. Otherwise returns the original value unchanged.

Examples

iex> Err.replace_err({:error, :timeout}, :network_error)
{:error, :network_error}

iex> Err.replace_err({:error, 404}, :not_found)
{:error, :not_found}

iex> Err.replace_err({:ok, 1}, :error)
{:ok, 1}

iex> Err.replace_err(nil, :error)
nil

replace_err_lazy(ok, fun)

@spec replace_err_lazy(any(), (any() -> any())) :: any()

Replaces the error inside an {:error, reason} tuple by calling a function.

If the result is {:error, _}, calls the function and returns {:error, result}. Otherwise returns the original value unchanged without calling the function.

This is the lazy version of replace_err/2 - the function is only called when needed.

Examples

iex> Err.replace_err_lazy({:error, 404}, fn value -> "Status: #{value}" end)
{:error, "Status: 404"}

iex> Err.replace_err_lazy({:ok, 1}, fn _ -> :error end)
{:ok, 1}

iex> Err.replace_err_lazy(nil, fn _ -> :error end)
nil

replace_lazy(error, fun)

@spec replace_lazy(value(), (any() -> any())) :: value()

Replaces the value inside an {:ok, value} tuple by calling a function.

If the result is {:ok, _}, calls the function and returns {:ok, result}. Otherwise returns the original value unchanged without calling the function.

This is the lazy version of replace/2 - the function is only called when needed.

Examples

iex> Err.replace_lazy({:ok, 1}, fn value -> value + 1 end)
{:ok, 2}

iex> Err.replace_lazy({:error, :timeout}, fn value -> value + 1 end)
{:error, :timeout}

iex> Err.replace_lazy(nil, fn value -> value + 1 end)
nil

some_and?(value, predicate)

@spec some_and?(any(), (any() -> any())) :: boolean()

Returns true when the value is present and satisfies predicate.

Returns false for nil without calling predicate.

Examples

iex> Err.some_and?("hello", &(String.length(&1) > 3))
true

iex> Err.some_and?("hi", &(String.length(&1) > 3))
false

iex> Err.some_and?(nil, &(String.length(&1) > 3))
false

tap(value, fun)

@spec tap(value(), (any() -> any())) :: value()

Calls fun with the success value and returns the original value unchanged.

This is useful for logging, tracing, or other side effects in a flow without changing the wrapped value.

Examples

iex> Err.tap({:ok, 5}, fn value -> send(self(), {:seen, value}) end)
{:ok, 5}

iex> Err.tap({:error, :timeout}, fn _ -> raise "should not run" end)
{:error, :timeout}

iex> Err.tap(nil, fn _ -> raise "should not run" end)
nil

tap_err(value, fun)

@spec tap_err(value(), (any() -> any())) :: value()

Calls fun with the error value and returns the original value unchanged.

This is useful for logging, tracing, or metrics on error paths without changing the error.

Examples

iex> Err.tap_err({:error, :timeout}, fn reason -> send(self(), {:seen_error, reason}) end)
{:error, :timeout}

iex> Err.tap_err({:ok, 5}, fn _ -> raise "should not run" end)
{:ok, 5}

iex> Err.tap_err(nil, fn _ -> raise "should not run" end)
nil

try_rescue(fun, rescue_fun \\ fn error -> error end)

@spec try_rescue((-> any()), (Exception.t() -> any())) :: result()

Executes fun and converts rescued exceptions into an error result.

Returns {:ok, value} when the function succeeds. If the function raises, returns {:error, exception} by default or {:error, mapper.(exception)} when a mapper is provided.

This is useful at library boundaries where a raising API needs to be adapted into a result flow.

Examples

iex> Err.try_rescue(fn -> 100 + 23 end)
{:ok, 123}

iex> Err.try_rescue(fn -> raise "boom" end) |> Err.map_err(&Exception.message/1)
{:error, "boom"}

iex> Err.try_rescue(fn -> raise "boom" end, fn error -> %{kind: :runtime_error, message: Exception.message(error)} end)
{:error, %{kind: :runtime_error, message: "boom"}}

unwrap_err_or(value, default)

@spec unwrap_err_or(value(), any()) :: any()

Returns the wrapped error or default when the result is ok or value is present.

For two-element error tuples ({:error, reason}) it returns reason. When the tuple contains additional metadata, it returns the remaining elements as a list.

Accepts nil, any {:ok, value} or {:error, reason} tuple (with or without extra metadata), and other terms.

Examples

iex> Err.unwrap_err_or({:error, :timeout}, :no_error)
:timeout

iex> Err.unwrap_err_or({:error, :boom, %{code: 500}}, :no_error)
[:boom, %{code: 500}]

iex> Err.unwrap_err_or({:ok, 1}, :no_error)
:no_error

iex> Err.unwrap_err_or(nil, :no_error)
:no_error

unwrap_or(value, default)

@spec unwrap_or(value(), any()) :: any()

Returns the wrapped value or default when the result is error or value is empty.

For two-element result tuples ({:ok, value}) it returns value. When the tuple contains additional metadata, it returns the remaining elements as a list.

Accepts nil, any {:ok, value} or {:error, reason} tuple (with or without extra metadata), and other terms.

Examples

iex> Err.unwrap_or({:ok, "config.json"}, "default.json")
"config.json"

iex> Err.unwrap_or({:ok, :user, %{role: :admin}}, [])
[:user, %{role: :admin}]

iex> Err.unwrap_or({:error, :not_found}, "default.json")
"default.json"

iex> Err.unwrap_or(nil, "default.json")
"default.json"

unwrap_or_lazy(tuple, default_fun)

@spec unwrap_or_lazy(value(), (any() -> any())) :: any()

Returns the wrapped value or computes it from default_fun when the result is an error or value is empty.

For successful tuples ({:ok, value}) the unwrapped value is returned. When the tuple contains extra data, the remaining elements are returned as a list. For error tuples the extracted value(s) are passed to default_fun.

The function receives the extracted value(s): a single value for two-element tuples or a list for larger tuples.

This is the lazy version of unwrap_or/2 - the function is only called when needed.

Examples

iex> Err.unwrap_or_lazy({:ok, "config.json"}, fn _ -> "default.json" end)
"config.json"

iex> Err.unwrap_or_lazy({:ok, :admin, %{perms: [:read]}}, fn _ -> [] end)
[:admin, %{perms: [:read]}]

iex> Err.unwrap_or_lazy({:error, :enoent}, fn reason -> "Error: #{reason}" end)
"Error: enoent"

iex> Err.unwrap_or_lazy(nil, fn _ -> %{role: :guest} end)
%{role: :guest}

values(results)

@spec values([value()]) :: list()

Extracts all success values from a list of results.

Returns a list containing all values, except {:error, _} tuples or nil.

Examples

iex> Err.values([{:ok, 1}, {:error, :timeout}, {:ok, 2}])
[1, 2]

iex> Err.values([{:ok, 1}, nil, 2])
[1, 2]

iex> Err.values([{:ok, "a"}, {:ok, "b"}])
["a", "b"]

iex> Err.values([{:error, :x}, {:error, :y}])
[]

iex> Err.values([1])
[1]

iex> Err.values([])
[]

wrap(term, opts \\ [])

zip(left, right)

@spec zip(value(), value()) :: value()

Combines two successful/present values into a pair.

For Result types, returns the first error encountered. When both values are ok, their extracted payloads are returned inside {:ok, {left, right}}.

For Option types, returns {left, right} when both values are present. If either side is nil, returns nil.

Examples

iex> Err.zip({:ok, 1}, {:ok, 2})
{:ok, {1, 2}}

iex> Err.zip({:ok, :user, %{id: 1}}, {:ok, :admin})
{:ok, {[:user, %{id: 1}], :admin}}

iex> Err.zip({:error, :timeout}, {:ok, 2})
{:error, :timeout}

iex> Err.zip("left", "right")
{"left", "right"}

iex> Err.zip(nil, "right")
nil