toobad 0.1.0

A simple, intuitive error handling library
Documentation
//! Context attachment trait for errors and options.
//!
//! This module provides the [`Context`] trait, which enables attaching descriptive
//! context messages to failures. It is implemented for both `Result<T, E>` and
//! `Option<T>`, allowing users to convert either type into the crate's unified
//! [`Error`] type with a meaningful message describing what operation failed.
//!
//! The trait is particularly useful for adding location-specific information to
//! errors, such as which file or network request failed, without defining custom
//! error types for each scenario.

use crate::{Error, Result};

/// Trait for attaching context to errors.
///
/// This trait provides a way to wrap failures with additional context information,
/// converting plain errors or `None` values into the crate's unified [`Error`] type.
/// The trait is implemented for both `Result<T, E>` and `Option<T>`.
///
/// # Example
///
/// ```rust
/// use toobad::{Context, Result};
///
/// fn read_file() -> Result<String> {
///     std::fs::read_to_string("config.toml")
///         .context("failed to read config file")
/// }
/// ```
pub trait Context<T> {
    /// Attach a context message to a failure.
    ///
    /// Transforms a `Result<T, E>` or `Option<T>` into [`Result`]<T> by wrapping
    /// the error or `None` value with a descriptive message.
    fn context(self, message: &str) -> Result<T>;
}

impl<T, E: std::error::Error + Send + Sync + 'static> Context<T> for std::result::Result<T, E> {
    fn context(self, message: &str) -> Result<T> {
        self.map_err(|e| {
            Error::new(ContextError {
                message: message.into(),
                source: Box::new(e),
            })
        })
    }
}

/// Internal error type that wraps an error with an attached context message.
///
/// This struct combines a user-provided message with the original error source,
/// allowing callers to understand both what operation failed and why.
#[derive(Debug)]
struct ContextError {
    message: String,
    source: Box<dyn std::error::Error + Send + Sync>,
}

impl std::fmt::Display for ContextError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}: {}", self.message, self.source)
    }
}

impl std::error::Error for ContextError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&*self.source)
    }
}

// Option //

impl<T> Context<T> for Option<T> {
    fn context(self, message: &str) -> Result<T> {
        self.ok_or_else(|| {
            Error::new(ContextError {
                message: message.into(),
                source: Box::new(NoneError),
            })
        })
    }
}

/// Internal error type representing a `None` value.
///
/// This error is used when `context()` is called on an `Option` that contains `None`,
/// providing a clear signal that a missing value caused the failure.
#[derive(Debug)]
struct NoneError;

impl std::fmt::Display for NoneError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "value was None")
    }
}

impl std::error::Error for NoneError {}