error_forge/
error.rs

1use std::fmt;
2use std::path::PathBuf;
3use std::io;
4use std::error::Error as StdError;
5use std::backtrace::Backtrace;
6
7#[cfg(feature = "serde")]
8use serde::Serialize;
9
10/// Type alias for error-forge results.
11pub type Result<T> = std::result::Result<T, crate::error::AppError>;
12
13/// Base trait for all custom error variants.
14pub trait ForgeError: std::error::Error + Send + Sync + 'static {
15    /// Returns the kind of error, typically matching the enum variant
16    fn kind(&self) -> &'static str;
17    
18    /// Returns a human-readable caption for the error
19    fn caption(&self) -> &'static str;
20    
21    /// Returns true if the operation can be retried
22    fn is_retryable(&self) -> bool {
23        false
24    }
25    
26    /// Returns true if the error is fatal and should terminate the program
27    fn is_fatal(&self) -> bool {
28        true
29    }
30    
31    /// Returns an appropriate HTTP status code for the error
32    fn status_code(&self) -> u16 {
33        500
34    }
35    
36    /// Returns an appropriate process exit code for the error
37    fn exit_code(&self) -> i32 {
38        1
39    }
40    
41    /// Returns a user-facing message that can be shown to end users
42    fn user_message(&self) -> String {
43        self.to_string()
44    }
45    
46    /// Returns a detailed technical message for developers/logs
47    fn dev_message(&self) -> String {
48        format!("[{}] {}", self.kind(), self)
49    }
50    
51    /// Returns a backtrace if available
52    fn backtrace(&self) -> Option<&Backtrace> {
53        None
54    }
55    
56    /// Registers the error with the central error registry
57    fn register(&self) {
58        crate::macros::call_error_hook(self.caption(), self.kind(), self.is_fatal(), self.is_retryable());
59    }
60}
61
62/// Example error enum that can be replaced by the define_errors! macro.
63#[derive(Debug)]
64#[cfg_attr(feature = "serde", derive(Serialize))]
65pub enum AppError {
66    /// Configuration-related errors
67    Config { message: String },
68    
69    /// Filesystem-related errors with optional path and source error
70    Filesystem { path: Option<PathBuf>, #[cfg_attr(feature = "serde", serde(skip))] source: io::Error },
71    
72    /// Network-related errors
73    Network { endpoint: String, #[cfg_attr(feature = "serde", serde(skip))] source: Option<Box<dyn StdError + Send + Sync>> },
74    
75    /// Generic errors for anything not covered by specific variants
76    Other { message: String },
77}
78
79impl std::fmt::Display for AppError {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            Self::Config { message } => write!(f, "⚙️ Configuration Error: {}", message),
83            Self::Filesystem { path, source } => {
84                if let Some(p) = path {
85                    write!(f, "💾 Filesystem Error at {:?}: {}", p, source)
86                } else {
87                    write!(f, "💾 Filesystem Error: {}", source)
88                }
89            },
90            Self::Network { endpoint, source } => {
91                if let Some(src) = source {
92                    write!(f, "🌐 Network Error on {}: {}", endpoint, src)
93                } else {
94                    write!(f, "🌐 Network Error on {}", endpoint)
95                }
96            },
97            Self::Other { message } => write!(f, "🚨 Error: {}", message),
98        }
99    }
100}
101
102impl std::error::Error for AppError {
103    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
104        match self {
105            AppError::Filesystem { source, .. } => Some(source),
106            AppError::Network { source: Some(src), .. } => Some(src.as_ref()),
107            _ => None,
108        }
109    }
110}
111
112impl From<io::Error> for AppError {
113    fn from(e: io::Error) -> Self {
114        AppError::Filesystem { path: None, source: e }
115    }
116}
117
118impl ForgeError for AppError {
119    fn kind(&self) -> &'static str {
120        match self {
121            Self::Config { .. } => "Config",
122            Self::Filesystem { .. } => "Filesystem",
123            Self::Network { .. } => "Network",
124            Self::Other { .. } => "Other",
125        }
126    }
127    
128    fn caption(&self) -> &'static str {
129        match self {
130            Self::Config { .. } => "⚙️ Configuration",
131            Self::Filesystem { .. } => "💾 Filesystem",
132            Self::Network { .. } => "🌐 Network",
133            Self::Other { .. } => "🚨 Error",
134        }
135    }
136    
137    fn is_retryable(&self) -> bool {
138        matches!(self, Self::Network { .. })
139    }
140    
141    fn status_code(&self) -> u16 {
142        match self {
143            Self::Config { .. } => 500,
144            Self::Filesystem { .. } => 500,
145            Self::Network { .. } => 503,
146            Self::Other { .. } => 500,
147        }
148    }
149}
150
151/// Constructor methods for AppError
152impl AppError {
153    /// Create a new Config error
154    pub fn config(message: impl Into<String>) -> Self {
155        let instance = Self::Config { message: message.into() };
156        crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
157        instance
158    }
159    
160    /// Create a new Filesystem error
161    pub fn filesystem(path: impl Into<PathBuf>, source: io::Error) -> Self {
162        let instance = Self::Filesystem { path: Some(path.into()), source };
163        crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
164        instance
165    }
166    
167    /// Create a new Network error
168    pub fn network(endpoint: impl Into<String>, source: Option<Box<dyn StdError + Send + Sync>>) -> Self {
169        let instance = Self::Network { endpoint: endpoint.into(), source };
170        crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
171        instance
172    }
173    
174    /// Create a new generic error
175    pub fn other(message: impl Into<String>) -> Self {
176        let instance = Self::Other { message: message.into() };
177        crate::macros::call_error_hook(instance.caption(), instance.kind(), instance.is_fatal(), instance.is_retryable());
178        instance
179    }
180}