error2 0.13.2

A simple error handle library for Rust
Documentation
use std::error::Error;

use crate::{Error2, Location, transform::SourceToTarget};

/// Type conversion for errors, `Option`, and `Result`.
///
/// The `Context` trait provides `.context()` and `.with_context()` methods for
/// converting between types. It performs three core transformations:
///
/// 1. `Result<T, Source> -> Result<T, Target>` - Convert error types
/// 2. `Option<T> -> Result<T, E>` - Convert `None` to an error
/// 3. `E -> Result<T, E>` - Convert an error directly to `Result`
///
/// # Converting Result Error Types
///
/// Convert from one error type to another:
///
/// ```
/// use std::io;
///
/// use error2::prelude::*;
///
/// #[derive(Debug, Error2)]
/// pub enum AppError {
///     #[error2(display("file error"))]
///     FileError {
///         source: io::Error,
///         backtrace: Backtrace,
///     },
/// }
///
/// fn read_file(path: &str) -> Result<String, AppError> {
///     // Result<String, io::Error> -> Result<String, AppError>
///     std::fs::read_to_string(path).context(FileError2)
/// }
/// ```
///
/// The context struct (e.g., `FileError2`) is automatically generated by the
/// `#[derive(Error2)]` macro.
///
/// # Converting Option to Result
///
/// Convert `Option<T>` to `Result<T, E>`:
///
/// ```
/// use std::collections::HashMap;
///
/// use error2::prelude::*;
///
/// #[derive(Debug, Error2)]
/// #[error2(display("key not found: {key}"))]
/// struct NotFound {
///     key: String,
///     backtrace: Backtrace,
/// }
///
/// # fn main() -> Result<(), NotFound> {
/// let mut map = HashMap::new();
/// map.insert("name", "Alice");
///
/// // Option<&str> -> Result<&str, NotFound>
/// let name = map.get("name").context(NotFound2 { key: "name" })?;
///
/// assert_eq!(*name, "Alice");
/// # Ok(())
/// # }
/// ```
///
/// # Converting Error to Result
///
/// Convert an error value directly to `Result`:
///
/// ```
/// use std::io::{self, ErrorKind};
///
/// use error2::prelude::*;
///
/// #[derive(Debug, Error2)]
/// #[error2(display("read line error"))]
/// struct ReadLineError {
///     source: io::Error,
///     backtrace: Backtrace,
/// }
///
/// # fn read_line() -> Result<Vec<u8>, io::Error> { Ok(vec![]) }
/// fn process_line() -> Result<Option<Vec<u8>>, ReadLineError> {
///     let bytes = match read_line() {
///         Ok(bytes) => bytes,
///         Err(ref e) if e.kind() == ErrorKind::TimedOut => return Ok(None),
///         Err(e) => {
///             // io::Error -> Result<_, ReadLineError>
///             return e.context(ReadLineError2);
///         }
///     };
///     Ok(Some(bytes))
/// }
/// ```
///
/// # Lazy Evaluation
///
/// Use `.with_context()` when creating context is expensive:
///
/// ```
/// # use error2::prelude::*;
/// # #[derive(Debug, Error2)]
/// # enum AppError {
/// #     #[error2(display("error: {msg}"))]
/// #     Error { msg: String, backtrace: Backtrace },
/// # }
/// # fn expensive_debug_info() -> String { String::new() }
/// # fn may_fail() -> Option<i32> { Some(42) }
/// # fn example() -> Result<i32, AppError> {
/// let value = may_fail().with_context(|| Error2 {
///     msg: expensive_debug_info(), // Only called if None
/// })?;
/// # Ok(value)
/// # }
/// ```
pub trait Context<T, M, Source, Middle, Target, C>: Sized
where
    Source: Into<Middle>,
    C: SourceToTarget<M, Source, Middle, Target>,
{
    /// Converts error or `None` into `Target` error type.
    ///
    /// Automatically captures the caller's location for backtrace.
    #[inline]
    #[track_caller]
    fn context(self, context: C) -> Result<T, Target> {
        self.context_and_location(context, Location::caller())
    }

    /// Converts with explicit location (rarely needed).
    fn context_and_location(self, context: C, location: Location) -> Result<T, Target>;

    /// Converts using lazy evaluation for context creation.
    ///
    /// The closure is only called if an error occurs.
    #[inline]
    #[track_caller]
    fn with_context<F>(self, f: F) -> Result<T, Target>
    where
        F: FnOnce() -> C,
    {
        self.with_context_and_location(f, Location::caller())
    }

    /// Lazy conversion with explicit location.
    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
    where
        F: FnOnce() -> C;
}

impl<T, M, Source, Middle, Target, C> Context<T, M, Source, Middle, Target, C> for Source
where
    Source: Error + Into<Middle>,
    Middle: Error,
    Target: Error2,
    C: SourceToTarget<M, Source, Middle, Target>,
{
    #[inline]
    fn context_and_location(self, context: C, location: Location) -> Result<T, Target> {
        Err(context.source_to_target(self, location))
    }

    #[inline]
    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
    where
        F: FnOnce() -> C,
    {
        let context = f();
        Err(context.source_to_target(self, location))
    }
}

impl<T, M, Target, C> Context<T, M, (), (), Target, C> for Option<T>
where
    Target: Error2,
    C: SourceToTarget<M, (), (), Target>,
{
    #[inline]
    fn context_and_location(self, context: C, location: Location) -> Result<T, Target> {
        match self {
            Some(t) => Ok(t),
            None => Err(context.source_to_target((), location)),
        }
    }

    #[inline]
    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
    where
        F: FnOnce() -> C,
    {
        match self {
            Some(t) => Ok(t),
            None => {
                let context = f();
                Err(context.source_to_target((), location))
            }
        }
    }
}

impl<T, M, Source, Middle, Target, C> Context<T, M, Source, Middle, Target, C> for Result<T, Source>
where
    Source: Error + Into<Middle>,
    Middle: Error,
    Target: Error2,
    C: SourceToTarget<M, Source, Middle, Target>,
{
    #[inline]
    fn context_and_location(self, context: C, location: Location) -> Result<T, Target> {
        match self {
            Ok(t) => Ok(t),
            Err(source) => source.context_and_location(context, location),
        }
    }

    #[inline]
    fn with_context_and_location<F>(self, f: F, location: Location) -> Result<T, Target>
    where
        F: FnOnce() -> C,
    {
        match self {
            Ok(t) => Ok(t),
            Err(source) => source.with_context_and_location(f, location),
        }
    }
}