129 lines
3.6 KiB
Elixir
129 lines
3.6 KiB
Elixir
defmodule Towerops.Result do
|
|
@moduledoc """
|
|
Generic result type for representing success or failure outcomes.
|
|
|
|
This module provides a type-safe way to handle operations that can succeed or fail,
|
|
using Elixir's standard {:ok, value} | {:error, reason} pattern with enhanced type safety.
|
|
|
|
## Type Parameters
|
|
|
|
- `ok_value`: The type of value returned on success
|
|
- `error_value`: The type of error returned on failure
|
|
|
|
## Examples
|
|
|
|
# Basic usage with string success and atom error
|
|
@spec fetch_user(id :: String.t()) :: Result.t(User.t(), :not_found | :timeout)
|
|
def fetch_user(id) do
|
|
case Repo.get(User, id) do
|
|
nil -> {:error, :not_found}
|
|
user -> {:ok, user}
|
|
end
|
|
end
|
|
|
|
# Using helper functions
|
|
Result.map({:ok, user}, &User.display_name/1)
|
|
# => {:ok, "John Doe"}
|
|
|
|
Result.map_error({:error, :db_error}, fn _ -> :service_unavailable end)
|
|
# => {:error, :service_unavailable}
|
|
"""
|
|
|
|
@type t(ok_value, error_value) :: {:ok, ok_value} | {:error, error_value}
|
|
|
|
@type t(ok_value) :: t(ok_value, term())
|
|
|
|
@doc """
|
|
Maps the success value using the given function.
|
|
Error values are passed through unchanged.
|
|
|
|
## Examples
|
|
|
|
iex> Result.map({:ok, 5}, fn x -> x * 2 end)
|
|
{:ok, 10}
|
|
|
|
iex> Result.map({:error, :not_found}, fn x -> x * 2 end)
|
|
{:error, :not_found}
|
|
"""
|
|
@spec map(t(a, e), (a -> b)) :: t(b, e) when a: var, b: var, e: var
|
|
def map({:ok, value}, fun) when is_function(fun, 1), do: {:ok, fun.(value)}
|
|
def map({:error, _} = err, fun) when is_function(fun, 1), do: err
|
|
|
|
@doc """
|
|
Maps the error value using the given function.
|
|
Success values are passed through unchanged.
|
|
|
|
## Examples
|
|
|
|
iex> Result.map_error({:error, :timeout}, fn _ -> :network_error end)
|
|
{:error, :network_error}
|
|
|
|
iex> Result.map_error({:ok, 42}, fn _ -> :network_error end)
|
|
{:ok, 42}
|
|
"""
|
|
@spec map_error(t(o, a), (a -> b)) :: t(o, b) when o: var, a: var, b: var
|
|
def map_error({:ok, _} = ok, fun) when is_function(fun, 1), do: ok
|
|
def map_error({:error, err}, fun) when is_function(fun, 1), do: {:error, fun.(err)}
|
|
|
|
@doc """
|
|
Chains together operations that return results.
|
|
If the first result is an error, returns it immediately.
|
|
Otherwise, applies the function to the success value.
|
|
|
|
## Examples
|
|
|
|
iex> Result.and_then({:ok, 10}, fn x -> {:ok, x * 2} end)
|
|
{:ok, 20}
|
|
|
|
iex> Result.and_then({:error, :not_found}, fn x -> {:ok, x * 2} end)
|
|
{:error, :not_found}
|
|
"""
|
|
@spec and_then(t(a, e), (a -> t(b, e))) :: t(b, e) when a: var, b: var, e: var
|
|
def and_then({:ok, value}, fun) when is_function(fun, 1), do: fun.(value)
|
|
def and_then({:error, _} = err, fun) when is_function(fun, 1), do: err
|
|
|
|
@doc """
|
|
Unwraps a result, returning the success value or a default on error.
|
|
|
|
## Examples
|
|
|
|
iex> Result.unwrap_or({:ok, 42}, 0)
|
|
42
|
|
|
|
iex> Result.unwrap_or({:error, :not_found}, 0)
|
|
0
|
|
"""
|
|
@spec unwrap_or(t(a, term()), a) :: a when a: var
|
|
def unwrap_or({:ok, value}, _default), do: value
|
|
def unwrap_or({:error, _}, default), do: default
|
|
|
|
@doc """
|
|
Checks if the result is a success.
|
|
|
|
## Examples
|
|
|
|
iex> Result.ok?({:ok, 42})
|
|
true
|
|
|
|
iex> Result.ok?({:error, :not_found})
|
|
false
|
|
"""
|
|
@spec ok?(t(any(), any())) :: boolean()
|
|
def ok?({:ok, _}), do: true
|
|
def ok?({:error, _}), do: false
|
|
|
|
@doc """
|
|
Checks if the result is an error.
|
|
|
|
## Examples
|
|
|
|
iex> Result.error?({:error, :not_found})
|
|
true
|
|
|
|
iex> Result.error?({:ok, 42})
|
|
false
|
|
"""
|
|
@spec error?(t(any(), any())) :: boolean()
|
|
def error?({:error, _}), do: true
|
|
def error?({:ok, _}), do: false
|
|
end
|