# `Err`
[🔗](https://github.com/leandrocp/err/blob/v0.2.3/lib/err.ex#L1)

**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

- Works with existing `:ok` / `:error` tuples of any size
- Treats `nil` as absence for Option-style flows
- Normalizes values into result flows with `from_nil/2` and `try_rescue/2`
- Wraps `Task` work with `async/1`, `await/2`, and `await_many/2`
- Composes success and error paths with `map/2`, `map_err/2`, `and_then/2`, and `or_else/2`
- Adds side effects without changing values using `tap/2` and `tap_err/2`
- Keeps branching explicit with `match/2`
- Includes list helpers like `all/1`, `values/1`, and `partition/1`
- Ships with exception helpers like `wrap/1` and `message/1`

## Installation

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

```elixir
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*

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

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

*Normalize `nil` into a result*

```elixir
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*

```elixir
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*

```elixir
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*

```elixir
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*

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

*Transform error values*

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

*Chain operations*

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

*Add side effects without changing the result*

```elixir
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*

```elixir
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*

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

*Eager fallback*

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

*Lazy fallback*

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

*Combine results (fail fast)*

```elixir
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*

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

*Split into ok and error lists*

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

*Check if result is ok*

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

*Check if result is error*

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

## Real-World Example

```elixir
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.

# `option`

```elixir
@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`

```elixir
@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`

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

Either a `t:result/0` or an `t:option/0` type.

# `all`

```elixir
@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`

```elixir
@spec and_then(value(), (any() -&gt; 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`

```elixir
@spec async((-&gt; 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`

```elixir
@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`

```elixir
@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`

```elixir
@spec ensure(value(), (any() -&gt; 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?`

```elixir
@spec err_and?(any(), (any() -&gt; 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`

```elixir
@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!`

```elixir
@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!`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`
*macro* 

```elixir
@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`
*macro* 

```elixir
@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`
*macro* 

```elixir
@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`
*macro* 

```elixir
@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`

```elixir
@spec map(value(), (any() -&gt; 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`

```elixir
@spec map_err(value(), (any() -&gt; 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`

```elixir
@spec match(value(), ok: (any() -&gt; any()), error: (any() -&gt; 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`

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

# `ok`

```elixir
@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?`

```elixir
@spec ok_and?(any(), (any() -&gt; 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`

```elixir
@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`

```elixir
@spec or_else_lazy(value(), (any() -&gt; 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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@spec replace_err_lazy(any(), (any() -&gt; 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`

```elixir
@spec replace_lazy(value(), (any() -&gt; 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?`

```elixir
@spec some_and?(any(), (any() -&gt; 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`

```elixir
@spec tap(value(), (any() -&gt; 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`

```elixir
@spec tap_err(value(), (any() -&gt; 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`

```elixir
@spec try_rescue((-&gt; any()), (Exception.t() -&gt; 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`

```elixir
@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`

```elixir
@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`

```elixir
@spec unwrap_or_lazy(value(), (any() -&gt; 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`

```elixir
@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`

# `zip`

```elixir
@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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
