memscope_rs/error/
types.rs

1use std::error::Error;
2use std::fmt;
3
4/// Primary error type for all MemScope operations
5#[derive(Debug)]
6pub struct MemScopeError {
7    /// Error classification
8    pub kind: ErrorKind,
9    /// Error severity level
10    pub severity: ErrorSeverity,
11    /// Additional context information
12    pub context: ErrorContext,
13    /// Underlying cause if any
14    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, // Cannot clone trait objects, so we lose the source
24        }
25    }
26}
27
28/// Classification of error types
29#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub enum ErrorKind {
31    /// Memory allocation or tracking failures
32    MemoryError,
33    /// Configuration or parameter issues
34    ConfigurationError,
35    /// I/O related errors
36    IoError,
37    /// Threading or concurrency issues
38    ConcurrencyError,
39    /// Symbol resolution failures
40    SymbolResolutionError,
41    /// Stack trace capture failures
42    StackTraceError,
43    /// Smart pointer tracking issues
44    SmartPointerError,
45    /// Cache operation failures
46    CacheError,
47    /// Invalid input or state
48    ValidationError,
49    /// Internal logic errors
50    InternalError,
51}
52
53/// Error severity levels for prioritization
54#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub enum ErrorSeverity {
56    /// Low impact, operation can continue
57    Warning,
58    /// Moderate impact, degraded functionality
59    Error,
60    /// High impact, operation must stop
61    Critical,
62    /// System-level failure
63    Fatal,
64}
65
66/// Additional context for error diagnosis
67#[derive(Debug, Clone)]
68pub struct ErrorContext {
69    /// Operation being performed when error occurred
70    pub operation: String,
71    /// Module or component where error originated
72    pub component: String,
73    /// Additional metadata for debugging
74    pub metadata: std::collections::HashMap<String, String>,
75    /// Timestamp when error occurred
76    pub timestamp: std::time::Instant,
77}
78
79impl MemScopeError {
80    /// Create new error with minimal information
81    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    /// Create error with full context
96    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    /// Chain with underlying error
116    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    /// Add metadata for debugging
125    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    /// Check if error is recoverable
135    pub fn is_recoverable(&self) -> bool {
136        matches!(self.severity, ErrorSeverity::Warning | ErrorSeverity::Error)
137    }
138
139    /// Check if error requires immediate attention
140    pub fn is_critical(&self) -> bool {
141        matches!(
142            self.severity,
143            ErrorSeverity::Critical | ErrorSeverity::Fatal
144        )
145    }
146
147    /// Get human-readable error description
148    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    /// Get error age since occurrence
160    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
225/// Convenience type alias for Results
226pub type MemScopeResult<T> = Result<T, MemScopeError>;
227
228/// Macro for creating errors with automatic context
229#[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 for creating results with error context
241#[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}