Raxol.Core.ErrorHandling (Raxol v2.0.1)

View Source

Functional error handling patterns for Raxol.

This module provides composable error handling utilities to replace try/catch blocks with more functional patterns. It implements Result and Option types along with safe execution functions.

Philosophy

Instead of using try/catch for error handling, we use:

  • Result types ({:ok, value} | {:error, reason})

  • Safe execution wrappers
  • Pipeline-friendly error handling
  • Explicit error propagation

Examples

# Instead of try/catch
result = safe_call(fn -> risky_operation() end)

# Chain operations safely
with {:ok, data} <- fetch_data(),
     {:ok, processed} <- process_data(data),
     {:ok, result} <- save_result(processed) do
  {:ok, result}
end

# Safe binary operations
safe_deserialize(binary_data)

Summary

Functions

Ensures cleanup is called regardless of success or failure.

FlatMaps over a Result type.

Maps over a Result type.

Safely calls a module function if it's exported.

Safely performs arithmetic with a fallback for nil values.

Safely executes multiple operations, collecting all results.

Safely executes a function and returns a Result type.

Safely executes a function with a fallback value on error.

Safely executes a function and returns error details with stacktrace for re-raising.

Safely executes a function with error logging.

Safely calls an optional callback on a module. Returns {:ok, nil} if the callback doesn't exist.

Safely deserializes Erlang terms from binary data.

Safely makes a GenServer call with proper error handling.

Safely reads and deserializes a file.

Executes operations until one fails.

Safely serializes a term to binary.

Safely writes a term to a file.

Unwraps a Result or returns a default value.

Unwraps a Result or calls a function to get default.

Ensures a cleanup function is called even if the main function fails.

Types

result(ok)

@type result(ok) :: {:ok, ok} | {:error, term()}

result(ok, error)

@type result(ok, error) :: {:ok, ok} | {:error, error}

Functions

ensure_cleanup(main_fun, cleanup_fun)

@spec ensure_cleanup((-> any()), (-> any())) ::
  {:ok, any()}
  | {:error,
     Exception.t() | {:exit, term()} | {:throw, term()} | {atom(), term()}}

Ensures cleanup is called regardless of success or failure.

flat_map(error, fun)

@spec flat_map(result(a), (a -> result(b))) :: result(b) when a: any(), b: any()

FlatMaps over a Result type.

Examples

{:ok, 5}
|> flat_map(fn x -> {:ok, x * 2} end)
# => {:ok, 10}

map(error, fun)

@spec map(result(a), (a -> b)) :: result(b) when a: any(), b: any()

Maps over a Result type.

Examples

{:ok, 5}
|> map(fn x -> x * 2 end)
# => {:ok, 10}

safe_apply(module, function, args)

@spec safe_apply(module(), atom(), list()) :: {:ok, any()} | {:error, atom()}

Safely calls a module function if it's exported.

Examples

safe_apply(MyModule, :init, [])

safe_arithmetic(fun, value, fallback \\ 0)

@spec safe_arithmetic((number() -> number()), any(), number()) :: number()

Safely performs arithmetic with a fallback for nil values.

Examples

safe_arithmetic(fn x -> x + 10 end, nil, 0)
# => 10 (uses fallback 0, then adds 10)

safe_batch(functions)

@spec safe_batch([(-> any())]) :: [result(any())]

Safely executes multiple operations, collecting all results.

Examples

safe_batch([
  fn -> operation1() end,
  fn -> operation2() end,
  fn -> operation3() end
])
# => [{:ok, result1}, {:error, error2}, {:ok, result3}]

safe_call(fun)

@spec safe_call((-> any())) ::
  {:ok, any()}
  | {:error,
     Exception.t() | {:exit, term()} | {:throw, term()} | {atom(), term()}}

Safely executes a function and returns a Result type.

Examples

iex> safe_call(fn -> 1 + 1 end)
{:ok, 2}

iex> safe_call(fn -> raise "oops" end)
{:error, %RuntimeError{message: "oops"}}

safe_call_with_default(fun, default)

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

Safely executes a function with a fallback value on error.

Examples

iex> safe_call_with_default(fn -> raise "oops" end, 42)
42

safe_call_with_info(fun)

@spec safe_call_with_info((-> any())) ::
  {:ok, any()} | {:error, {atom(), any(), list()}}

Safely executes a function and returns error details with stacktrace for re-raising.

Examples

iex> safe_call_with_info(fn -> 42 end)
{:ok, 42}

iex> safe_call_with_info(fn -> raise "oops" end)
{:error, {:error, %RuntimeError{message: "oops"}, [...]}}

safe_call_with_logging(fun, context)

@spec safe_call_with_logging((-> any()), String.t()) :: result(any())

Safely executes a function with error logging.

Examples

safe_call_with_logging(fn -> process() end, "Processing failed")

safe_callback(module, function, args)

@spec safe_callback(module(), atom(), list()) ::
  {:ok, any()}
  | {:error,
     Exception.t() | {:exit, term()} | {:throw, term()} | {atom(), term()}}

Safely calls an optional callback on a module. Returns {:ok, nil} if the callback doesn't exist.

safe_deserialize(binary)

@spec safe_deserialize(binary()) :: {:ok, term()} | {:error, :invalid_binary}

Safely deserializes Erlang terms from binary data.

Examples

iex> binary = :erlang.term_to_binary({:ok, "data"})
iex> safe_deserialize(binary)
{:ok, {:ok, "data"}}

iex> safe_deserialize("invalid")
{:error, :invalid_binary}

safe_genserver_call(server, message, timeout \\ 5000)

@spec safe_genserver_call(GenServer.server(), any(), timeout()) :: result(any())

Safely makes a GenServer call with proper error handling.

Examples

safe_genserver_call(MyServer, :get_state)

safe_read_term(path)

@spec safe_read_term(Path.t()) :: {:ok, term()} | {:error, atom()}

Safely reads and deserializes a file.

Examples

safe_read_term("/path/to/file")

safe_sequence(functions)

@spec safe_sequence([(-> any())]) :: result([any()])

Executes operations until one fails.

safe_serialize(term)

@spec safe_serialize(term()) :: result(binary())

Safely serializes a term to binary.

safe_write_term(path, term)

@spec safe_write_term(Path.t(), term()) :: result(:ok)

Safely writes a term to a file.

unwrap_or(arg, default)

@spec unwrap_or(result(a), a) :: a when a: any()

Unwraps a Result or returns a default value.

Examples

unwrap_or({:ok, 42}, 0)     # => 42
unwrap_or({:error, _}, 0)   # => 0

unwrap_or_else(arg, fun)

@spec unwrap_or_else(result(a), (-> a)) :: a when a: any()

Unwraps a Result or calls a function to get default.

Examples

unwrap_or_else({:error, :not_found}, fn -> fetch_default() end)

with_cleanup(main_fun, cleanup_fun)

@spec with_cleanup((-> result(a)), (a -> any())) :: result(a) when a: any()

Ensures a cleanup function is called even if the main function fails.

Examples

with_cleanup(
  fn -> open_resource() end,
  fn resource -> close_resource(resource) end
)