error-forge 0.9.7

A comprehensive Rust error management framework with async support, error recovery patterns, and resilience features for building robust, fault-tolerant applications with rich error handling, formatting, and tracing capabilities.
Documentation
use std::backtrace::Backtrace;
use std::error::Error as StdError;
use std::fmt;
use std::io;
use std::path::PathBuf;

#[cfg(feature = "serde")]
use serde::Serialize;

/// Type alias for error-forge results.
pub type Result<T> = std::result::Result<T, crate::error::AppError>;

/// Base trait for all custom error variants.
pub trait ForgeError: std::error::Error + Send + Sync + 'static {
    /// Returns the kind of error, typically matching the enum variant
    fn kind(&self) -> &'static str;

    /// Returns a human-readable caption for the error
    fn caption(&self) -> &'static str;

    /// Returns true if the operation can be retried
    fn is_retryable(&self) -> bool {
        false
    }

    /// Returns true if the error is fatal and should terminate the program
    fn is_fatal(&self) -> bool {
        false
    }

    /// Returns an appropriate HTTP status code for the error
    fn status_code(&self) -> u16 {
        500
    }

    /// Returns an appropriate process exit code for the error
    fn exit_code(&self) -> i32 {
        1
    }

    /// Returns a user-facing message that can be shown to end users
    fn user_message(&self) -> String {
        self.to_string()
    }

    /// Returns a detailed technical message for developers/logs
    fn dev_message(&self) -> String {
        format!("[{}] {}", self.kind(), self)
    }

    /// Returns a backtrace if available
    fn backtrace(&self) -> Option<&Backtrace> {
        None
    }

    /// Registers the error with the central error registry
    fn register(&self) {
        crate::macros::call_error_hook(
            self.caption(),
            self.kind(),
            self.is_fatal(),
            self.is_retryable(),
        );
    }
}

/// Example error enum that can be replaced by the define_errors! macro.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum AppError {
    /// Configuration-related errors
    Config {
        message: String,
        retryable: bool,
        fatal: bool,
        status: u16,
    },

    /// Filesystem-related errors with optional path and source error
    Filesystem {
        path: Option<PathBuf>,
        #[cfg_attr(feature = "serde", serde(skip))]
        source: io::Error,
        retryable: bool,
        fatal: bool,
        status: u16,
    },

    /// Network-related errors
    Network {
        endpoint: String,
        #[cfg_attr(feature = "serde", serde(skip))]
        source: Option<Box<dyn StdError + Send + Sync>>,
        retryable: bool,
        fatal: bool,
        status: u16,
    },

    /// Generic errors for anything not covered by specific variants
    Other {
        message: String,
        retryable: bool,
        fatal: bool,
        status: u16,
    },
}

impl std::fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Config { message, .. } => write!(f, "⚙️ Configuration Error: {message}"),
            Self::Filesystem { path, source, .. } => {
                if let Some(p) = path {
                    write!(f, "💾 Filesystem Error at {p:?}: {source}")
                } else {
                    write!(f, "💾 Filesystem Error: {source}")
                }
            }
            Self::Network {
                endpoint, source, ..
            } => {
                if let Some(src) = source {
                    write!(f, "🌐 Network Error on {endpoint}: {src}")
                } else {
                    write!(f, "🌐 Network Error on {endpoint}")
                }
            }
            Self::Other { message, .. } => write!(f, "🚨 Error: {message}"),
        }
    }
}

impl std::error::Error for AppError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            AppError::Filesystem { source, .. } => Some(source),
            AppError::Network {
                source: Some(src), ..
            } => Some(src.as_ref()),
            _ => None,
        }
    }
}

impl From<io::Error> for AppError {
    fn from(e: io::Error) -> Self {
        AppError::Filesystem {
            path: None,
            source: e,
            retryable: false,
            fatal: true,
            status: 500,
        }
    }
}

impl ForgeError for AppError {
    fn kind(&self) -> &'static str {
        match self {
            Self::Config { .. } => "Config",
            Self::Filesystem { .. } => "Filesystem",
            Self::Network { .. } => "Network",
            Self::Other { .. } => "Other",
        }
    }

    fn caption(&self) -> &'static str {
        match self {
            Self::Config { .. } => "⚙️ Configuration",
            Self::Filesystem { .. } => "💾 Filesystem",
            Self::Network { .. } => "🌐 Network",
            Self::Other { .. } => "🚨 Error",
        }
    }

    fn is_retryable(&self) -> bool {
        match self {
            Self::Config { retryable, .. } => *retryable,
            Self::Filesystem { retryable, .. } => *retryable,
            Self::Network { retryable, .. } => *retryable,
            Self::Other { retryable, .. } => *retryable,
        }
    }

    fn is_fatal(&self) -> bool {
        match self {
            Self::Config { fatal, .. } => *fatal,
            Self::Filesystem { fatal, .. } => *fatal,
            Self::Network { fatal, .. } => *fatal,
            Self::Other { fatal, .. } => *fatal,
        }
    }

    fn status_code(&self) -> u16 {
        match self {
            Self::Config { status, .. } => *status,
            Self::Filesystem { status, .. } => *status,
            Self::Network { status, .. } => *status,
            Self::Other { status, .. } => *status,
        }
    }
}

/// Constructor methods for AppError
impl AppError {
    /// Create a new Config error
    pub fn config(message: impl Into<String>) -> Self {
        let instance = Self::Config {
            message: message.into(),
            retryable: false,
            fatal: false,
            status: 500,
        };
        crate::macros::call_error_hook(
            instance.caption(),
            instance.kind(),
            instance.is_fatal(),
            instance.is_retryable(),
        );
        instance
    }

    /// Create a new Filesystem error
    pub fn filesystem(path: impl Into<String>, source: impl Into<Option<io::Error>>) -> Self {
        // Convert the source parameter
        let source = match source.into() {
            Some(err) => err,
            None => io::Error::other("File operation failed"),
        };

        let instance = Self::Filesystem {
            path: Some(path.into().into()),
            source,
            retryable: false,
            fatal: false,
            status: 500,
        };
        crate::macros::call_error_hook(
            instance.caption(),
            instance.kind(),
            instance.is_fatal(),
            instance.is_retryable(),
        );
        instance
    }

    /// Create a filesystem error with specific source error
    pub fn filesystem_with_source(path: impl Into<PathBuf>, source: io::Error) -> Self {
        let instance = Self::Filesystem {
            path: Some(path.into()),
            source,
            retryable: false,
            fatal: false,
            status: 500,
        };
        crate::macros::call_error_hook(
            instance.caption(),
            instance.kind(),
            instance.is_fatal(),
            instance.is_retryable(),
        );
        instance
    }

    /// Create a new Network error
    pub fn network(
        endpoint: impl Into<String>,
        source: impl Into<Option<Box<dyn StdError + Send + Sync>>>,
    ) -> Self {
        // Convert the source parameter
        let source = source.into();

        let instance = Self::Network {
            endpoint: endpoint.into(),
            source,
            retryable: true,
            fatal: false,
            status: 503,
        };
        crate::macros::call_error_hook(
            instance.caption(),
            instance.kind(),
            instance.is_fatal(),
            instance.is_retryable(),
        );
        instance
    }

    /// Create a network error with specific source error
    pub fn network_with_source(
        endpoint: impl Into<String>,
        source: Option<Box<dyn StdError + Send + Sync>>,
    ) -> Self {
        let instance = Self::Network {
            endpoint: endpoint.into(),
            source,
            retryable: true,
            fatal: false,
            status: 503,
        };
        crate::macros::call_error_hook(
            instance.caption(),
            instance.kind(),
            instance.is_fatal(),
            instance.is_retryable(),
        );
        instance
    }

    /// Create a new generic error
    pub fn other(message: impl Into<String>) -> Self {
        let instance = Self::Other {
            message: message.into(),
            retryable: false,
            fatal: false,
            status: 500,
        };
        crate::macros::call_error_hook(
            instance.caption(),
            instance.kind(),
            instance.is_fatal(),
            instance.is_retryable(),
        );
        instance
    }

    /// Set whether this error is retryable
    pub fn with_retryable(mut self, retryable: bool) -> Self {
        match &mut self {
            Self::Config { retryable: r, .. } => *r = retryable,
            Self::Filesystem { retryable: r, .. } => *r = retryable,
            Self::Network { retryable: r, .. } => *r = retryable,
            Self::Other { retryable: r, .. } => *r = retryable,
        }
        self
    }

    /// Set whether this error is fatal
    pub fn with_fatal(mut self, fatal: bool) -> Self {
        match &mut self {
            Self::Config { fatal: f, .. } => *f = fatal,
            Self::Filesystem { fatal: f, .. } => *f = fatal,
            Self::Network { fatal: f, .. } => *f = fatal,
            Self::Other { fatal: f, .. } => *f = fatal,
        }
        self
    }

    /// Set the HTTP status code for this error
    pub fn with_status(mut self, status: u16) -> Self {
        match &mut self {
            Self::Config { status: s, .. } => *s = status,
            Self::Filesystem { status: s, .. } => *s = status,
            Self::Network { status: s, .. } => *s = status,
            Self::Other { status: s, .. } => *s = status,
        }
        self
    }

    /// Add a code to this error
    pub fn with_code(self, code: impl Into<String>) -> crate::registry::CodedError<Self> {
        crate::registry::CodedError::new(self, code.into())
    }

    /// Add context to this error
    pub fn context<C: std::fmt::Display + std::fmt::Debug + Send + Sync + 'static>(
        self,
        context: C,
    ) -> crate::context::ContextError<Self, C> {
        crate::context::ContextError::new(self, context)
    }
}