1use std::error::Error;
2use std::fmt;
3
4#[derive(Debug)]
6pub struct MemScopeError {
7 pub kind: ErrorKind,
9 pub severity: ErrorSeverity,
11 pub context: ErrorContext,
13 pub source: Option<Box<dyn Error + Send + Sync>>,
15}
16
17impl Clone for MemScopeError {
18 fn clone(&self) -> Self {
19 Self {
20 kind: self.kind.clone(),
21 severity: self.severity.clone(),
22 context: self.context.clone(),
23 source: None, }
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub enum ErrorKind {
31 MemoryError,
33 ConfigurationError,
35 IoError,
37 ConcurrencyError,
39 SymbolResolutionError,
41 StackTraceError,
43 SmartPointerError,
45 CacheError,
47 ValidationError,
49 InternalError,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub enum ErrorSeverity {
56 Warning,
58 Error,
60 Critical,
62 Fatal,
64}
65
66#[derive(Debug, Clone)]
68pub struct ErrorContext {
69 pub operation: String,
71 pub component: String,
73 pub metadata: std::collections::HashMap<String, String>,
75 pub timestamp: std::time::Instant,
77}
78
79impl MemScopeError {
80 pub fn new(kind: ErrorKind, message: &str) -> Self {
82 Self {
83 kind,
84 severity: ErrorSeverity::Error,
85 context: ErrorContext {
86 operation: message.to_string(),
87 component: "unknown".to_string(),
88 metadata: std::collections::HashMap::new(),
89 timestamp: std::time::Instant::now(),
90 },
91 source: None,
92 }
93 }
94
95 pub fn with_context(
97 kind: ErrorKind,
98 severity: ErrorSeverity,
99 operation: &str,
100 component: &str,
101 ) -> Self {
102 Self {
103 kind,
104 severity,
105 context: ErrorContext {
106 operation: operation.to_string(),
107 component: component.to_string(),
108 metadata: std::collections::HashMap::new(),
109 timestamp: std::time::Instant::now(),
110 },
111 source: None,
112 }
113 }
114
115 pub fn with_source<E>(mut self, source: E) -> Self
117 where
118 E: Error + Send + Sync + 'static,
119 {
120 self.source = Some(Box::new(source));
121 self
122 }
123
124 pub fn with_metadata<K, V>(mut self, key: K, value: V) -> Self
126 where
127 K: Into<String>,
128 V: Into<String>,
129 {
130 self.context.metadata.insert(key.into(), value.into());
131 self
132 }
133
134 pub fn is_recoverable(&self) -> bool {
136 matches!(self.severity, ErrorSeverity::Warning | ErrorSeverity::Error)
137 }
138
139 pub fn is_critical(&self) -> bool {
141 matches!(
142 self.severity,
143 ErrorSeverity::Critical | ErrorSeverity::Fatal
144 )
145 }
146
147 pub fn description(&self) -> String {
149 format!(
150 "[{}:{}] {} in {} ({})",
151 self.severity_str(),
152 self.kind_str(),
153 self.context.operation,
154 self.context.component,
155 self.elapsed_time_str()
156 )
157 }
158
159 pub fn age(&self) -> std::time::Duration {
161 self.context.timestamp.elapsed()
162 }
163
164 fn severity_str(&self) -> &'static str {
165 match self.severity {
166 ErrorSeverity::Warning => "WARN",
167 ErrorSeverity::Error => "ERROR",
168 ErrorSeverity::Critical => "CRITICAL",
169 ErrorSeverity::Fatal => "FATAL",
170 }
171 }
172
173 fn kind_str(&self) -> &'static str {
174 match self.kind {
175 ErrorKind::MemoryError => "MEMORY",
176 ErrorKind::ConfigurationError => "CONFIG",
177 ErrorKind::IoError => "IO",
178 ErrorKind::ConcurrencyError => "CONCURRENCY",
179 ErrorKind::SymbolResolutionError => "SYMBOL",
180 ErrorKind::StackTraceError => "STACKTRACE",
181 ErrorKind::SmartPointerError => "SMARTPTR",
182 ErrorKind::CacheError => "CACHE",
183 ErrorKind::ValidationError => "VALIDATION",
184 ErrorKind::InternalError => "INTERNAL",
185 }
186 }
187
188 fn elapsed_time_str(&self) -> String {
189 let elapsed = self.age();
190 if elapsed.as_secs() > 0 {
191 format!("{}s ago", elapsed.as_secs())
192 } else {
193 format!("{}ms ago", elapsed.as_millis())
194 }
195 }
196}
197
198impl fmt::Display for MemScopeError {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 write!(f, "{}", self.description())?;
201
202 if !self.context.metadata.is_empty() {
203 write!(f, " [")?;
204 for (i, (key, value)) in self.context.metadata.iter().enumerate() {
205 if i > 0 {
206 write!(f, ", ")?;
207 }
208 write!(f, "{}={}", key, value)?;
209 }
210 write!(f, "]")?;
211 }
212
213 Ok(())
214 }
215}
216
217impl Error for MemScopeError {
218 fn source(&self) -> Option<&(dyn Error + 'static)> {
219 self.source
220 .as_ref()
221 .map(|e| e.as_ref() as &(dyn Error + 'static))
222 }
223}
224
225pub type MemScopeResult<T> = Result<T, MemScopeError>;
227
228#[macro_export]
230macro_rules! memscope_error {
231 ($kind:expr, $operation:expr) => {
232 $crate::error::MemScopeError::new($kind, $operation)
233 };
234
235 ($kind:expr, $severity:expr, $operation:expr, $component:expr) => {
236 $crate::error::MemScopeError::with_context($kind, $severity, $operation, $component)
237 };
238}
239
240#[macro_export]
242macro_rules! memscope_bail {
243 ($kind:expr, $operation:expr) => {
244 return Err($crate::memscope_error!($kind, $operation))
245 };
246
247 ($kind:expr, $severity:expr, $operation:expr, $component:expr) => {
248 return Err($crate::memscope_error!(
249 $kind, $severity, $operation, $component
250 ))
251 };
252}
253
254impl Default for ErrorContext {
255 fn default() -> Self {
256 Self {
257 operation: "unknown_operation".to_string(),
258 component: "unknown_component".to_string(),
259 metadata: std::collections::HashMap::new(),
260 timestamp: std::time::Instant::now(),
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_error_creation() {
271 let error = MemScopeError::new(ErrorKind::MemoryError, "allocation failed");
272
273 assert_eq!(error.kind, ErrorKind::MemoryError);
274 assert_eq!(error.severity, ErrorSeverity::Error);
275 assert!(error.is_recoverable());
276 assert!(!error.is_critical());
277 }
278
279 #[test]
280 fn test_error_with_context() {
281 let error = MemScopeError::with_context(
282 ErrorKind::ConfigurationError,
283 ErrorSeverity::Critical,
284 "invalid_config",
285 "memory_tracker",
286 );
287
288 assert_eq!(error.severity, ErrorSeverity::Critical);
289 assert!(!error.is_recoverable());
290 assert!(error.is_critical());
291 assert!(error.description().contains("invalid_config"));
292 }
293
294 #[test]
295 fn test_error_chaining() {
296 use std::io;
297
298 let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
299 let error =
300 MemScopeError::new(ErrorKind::IoError, "config read failed").with_source(io_error);
301
302 assert!(error.source().is_some());
303 }
304
305 #[test]
306 fn test_error_metadata() {
307 let error = MemScopeError::new(ErrorKind::CacheError, "cache miss")
308 .with_metadata("cache_size", "1024")
309 .with_metadata("hit_ratio", "0.85");
310
311 assert_eq!(error.context.metadata.len(), 2);
312 assert_eq!(
313 error.context.metadata.get("cache_size"),
314 Some(&"1024".to_string())
315 );
316 }
317
318 #[test]
319 fn test_error_display() {
320 let error = MemScopeError::with_context(
321 ErrorKind::SymbolResolutionError,
322 ErrorSeverity::Warning,
323 "symbol_lookup",
324 "stack_tracer",
325 )
326 .with_metadata("address", "0x12345678");
327
328 let display_str = format!("{}", error);
329 assert!(display_str.contains("WARN"));
330 assert!(display_str.contains("SYMBOL"));
331 assert!(display_str.contains("symbol_lookup"));
332 assert!(display_str.contains("stack_tracer"));
333 assert!(display_str.contains("address=0x12345678"));
334 }
335
336 #[test]
337 fn test_macro_usage() {
338 let error = memscope_error!(ErrorKind::ValidationError, "invalid input");
339 assert_eq!(error.kind, ErrorKind::ValidationError);
340
341 let error = memscope_error!(
342 ErrorKind::InternalError,
343 ErrorSeverity::Fatal,
344 "system_failure",
345 "core"
346 );
347 assert_eq!(error.severity, ErrorSeverity::Fatal);
348 }
349}