Raxol.Core.ErrorHandlingStandard (Raxol v2.0.1)

View Source

Standardized error handling patterns for the Raxol application.

This module defines the standard error handling patterns that should be used consistently across all Raxol modules.

Error Tuple Convention

All functions that can fail should return:

  • {:ok, result} on success
  • {:error, reason} or {:error, reason, context} on failure

Error Types

Use standardized error atoms:

  • :invalid_argument - Invalid function arguments
  • :not_found - Resource not found
  • :permission_denied - Insufficient permissions
  • :timeout - Operation timed out
  • :connection_failed - Network/connection error
  • :invalid_state - Invalid state for operation
  • :resource_exhausted - Resource limits exceeded

Pattern Examples

# Basic error handling
def process_data(data) do
  with {:ok, validated} <- validate(data),
       {:ok, transformed} <- transform(validated),
       {:ok, result} <- save(transformed) do
    {:ok, result}
  else
    {:error, _reason} = error ->
      Log.error("Failed to process data")
      error
  end
end

# Error with context
def fetch_user(id) do
  case Repo.get(User, id) do
    nil -> {:error, :not_found, %{user_id: id}}
    user -> {:ok, user}
  end
end

Summary

Functions

Aggregates multiple errors into a single error result.

Chains multiple operations that return result tuples.

Ensures a result is returned, converting exceptions to errors.

Flat maps over a result tuple.

Maps over a result tuple, applying a function to the success value.

Converts various error formats to standard error tuples.

Wraps a function that might raise an exception into a result tuple.

Logs and returns the error, useful in pipelines.

Validates required fields in a map or struct.

Provides a default value for error cases.

Retries an operation with exponential backoff.

Types

error_result()

@type error_result() :: {:error, standard_error()} | {:error, standard_error(), map()}

ok_result(type)

@type ok_result(type) :: {:ok, type}

result(type)

@type result(type) :: ok_result(type) | error_result()

standard_error()

@type standard_error() ::
  :invalid_argument
  | :not_found
  | :permission_denied
  | :timeout
  | :connection_failed
  | :invalid_state
  | :resource_exhausted
  | :internal_error
  | :not_implemented
  | :conflict
  | :precondition_failed

Functions

aggregate_errors(results)

@spec aggregate_errors([result(any())]) ::
  {:ok, [any()]} | {:error, :multiple_errors, map()}

Aggregates multiple errors into a single error result.

Example

results = [
  {:ok, 1},
  {:error, :not_found, %{id: 2}},
  {:error, :timeout}
]

aggregate_errors(results)
# => {:error, :multiple_errors, %{errors: [...]}}

chain_operations(operations)

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

Chains multiple operations that return result tuples.

Example

chain_operations([
  fn -> validate_input(input) end,
  fn validated -> process(validated) end,
  fn processed -> save(processed) end
])

ensure_result(fun)

@spec ensure_result((-> any())) :: result(any())

Ensures a result is returned, converting exceptions to errors.

Example

ensure_result(fn ->
  User.get!(123)
end)
# Returns {:ok, user} or {:error, :internal_error, %{...}}

flat_map_ok(error, fun)

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

Flat maps over a result tuple.

Example

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

map_ok(error, fun)

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

Maps over a result tuple, applying a function to the success value.

Example

{:ok, 5}
|> map_ok(&(&1 * 2))
# => {:ok, 10}

normalize_error(other)

@spec normalize_error(term()) :: error_result()

Converts various error formats to standard error tuples.

safe_call(fun, error_type \\ :internal_error)

@spec safe_call((-> any()), standard_error()) ::
  {:ok, any()} | {:error, standard_error(), map()}

Wraps a function that might raise an exception into a result tuple.

tap_error(error, message)

@spec tap_error(result(any()), String.t()) :: result(any())

Logs and returns the error, useful in pipelines.

Example

{:error, :not_found, %{id: 123}}
|> tap_error("User lookup failed")
# Logs: "User lookup failed: {:error, :not_found, %{id: 123}}"
# Returns: {:error, :not_found, %{id: 123}}

validate_required(data, required_fields)

@spec validate_required(map(), [atom()]) ::
  {:ok, map()} | {:error, :invalid_argument, map()}

Validates required fields in a map or struct.

Example

validate_required(%{name: "John"}, [:name, :email])
# => {:error, :invalid_argument, %{missing_fields: [:email]}}

with_default(arg, default)

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

Provides a default value for error cases.

Example

{:error, :not_found}
|> with_default("default")
# => "default"

with_retry(fun, opts \\ [])

@spec with_retry(
  (-> result(any())),
  keyword()
) :: result(any())

Retries an operation with exponential backoff.

Options

  • :max_attempts - Maximum number of attempts (default: 3)
  • :initial_delay - Initial delay in ms (default: 100)
  • :max_delay - Maximum delay in ms (default: 5000)
  • :jitter - Add random jitter to delay (default: true)