Skip to main content

numrs2/error/
context.rs

1//! Error context and metadata for enhanced error reporting
2
3use std::collections::HashMap;
4use std::fmt;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7/// Error severity levels
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub enum ErrorSeverity {
10    /// Low severity - warnings, missing optional features
11    Low,
12    /// Medium severity - recoverable errors, invalid input
13    Medium,
14    /// High severity - mathematical errors, dimension mismatches
15    High,
16    /// Critical severity - memory allocation failures, system errors
17    Critical,
18}
19
20impl fmt::Display for ErrorSeverity {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            ErrorSeverity::Low => write!(f, "LOW"),
24            ErrorSeverity::Medium => write!(f, "MEDIUM"),
25            ErrorSeverity::High => write!(f, "HIGH"),
26            ErrorSeverity::Critical => write!(f, "CRITICAL"),
27        }
28    }
29}
30
31/// Location information for errors
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct ErrorLocation {
34    /// Source file name
35    pub file: String,
36    /// Line number
37    pub line: u32,
38    /// Function or method name
39    pub function: String,
40    /// Optional module path
41    pub module_path: Option<String>,
42}
43
44impl ErrorLocation {
45    /// Create a new error location
46    pub fn new(file: &str, line: u32, function: &str) -> Self {
47        Self {
48            file: file.to_string(),
49            line,
50            function: function.to_string(),
51            module_path: None,
52        }
53    }
54
55    /// Create an error location with module path
56    pub fn with_module(file: &str, line: u32, function: &str, module_path: &str) -> Self {
57        Self {
58            file: file.to_string(),
59            line,
60            function: function.to_string(),
61            module_path: Some(module_path.to_string()),
62        }
63    }
64}
65
66impl fmt::Display for ErrorLocation {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        if let Some(ref module) = self.module_path {
69            write!(
70                f,
71                "{}::{} ({}:{})",
72                module, self.function, self.file, self.line
73            )
74        } else {
75            write!(f, "{} ({}:{})", self.function, self.file, self.line)
76        }
77    }
78}
79
80/// Operation context for errors
81#[derive(Debug, Clone)]
82pub struct OperationContext {
83    /// The operation being performed
84    pub operation: Option<String>,
85    /// Input parameters or relevant data
86    pub parameters: HashMap<String, String>,
87    /// Array shapes involved in the operation
88    pub shapes: Vec<Vec<usize>>,
89    /// Data types involved
90    pub dtypes: Vec<String>,
91    /// Memory usage information
92    pub memory_info: Option<MemoryInfo>,
93    /// Thread information
94    pub thread_info: Option<ThreadInfo>,
95    /// Performance hints
96    pub performance_hints: Vec<String>,
97    /// Timestamp when the operation started
98    pub timestamp: u64,
99}
100
101impl Default for OperationContext {
102    fn default() -> Self {
103        Self {
104            operation: None,
105            parameters: HashMap::new(),
106            shapes: Vec::new(),
107            dtypes: Vec::new(),
108            memory_info: None,
109            thread_info: None,
110            performance_hints: Vec::new(),
111            timestamp: SystemTime::now()
112                .duration_since(UNIX_EPOCH)
113                .unwrap_or_default()
114                .as_millis() as u64,
115        }
116    }
117}
118
119impl OperationContext {
120    /// Create a new operation context
121    pub fn new(operation: &str) -> Self {
122        Self {
123            operation: Some(operation.to_string()),
124            ..Default::default()
125        }
126    }
127
128    /// Add a parameter to the context
129    pub fn with_parameter<K, V>(mut self, key: K, value: V) -> Self
130    where
131        K: Into<String>,
132        V: fmt::Display,
133    {
134        self.parameters.insert(key.into(), value.to_string());
135        self
136    }
137
138    /// Add array shape information
139    pub fn with_shape(mut self, shape: Vec<usize>) -> Self {
140        self.shapes.push(shape);
141        self
142    }
143
144    /// Add array shapes information
145    pub fn with_shapes(mut self, shapes: &[Vec<usize>]) -> Self {
146        self.shapes.extend_from_slice(shapes);
147        self
148    }
149
150    /// Add data type information
151    pub fn with_dtype(mut self, dtype: &str) -> Self {
152        self.dtypes.push(dtype.to_string());
153        self
154    }
155
156    /// Add memory information
157    pub fn with_memory_info(mut self, memory_info: MemoryInfo) -> Self {
158        self.memory_info = Some(memory_info);
159        self
160    }
161
162    /// Add thread information
163    pub fn with_thread_info(mut self, thread_info: ThreadInfo) -> Self {
164        self.thread_info = Some(thread_info);
165        self
166    }
167
168    /// Add a performance hint
169    pub fn with_performance_hint(mut self, hint: &str) -> Self {
170        self.performance_hints.push(hint.to_string());
171        self
172    }
173}
174
175/// Memory usage information
176#[derive(Debug, Clone)]
177pub struct MemoryInfo {
178    /// Total bytes allocated
179    pub total_allocated: usize,
180    /// Peak memory usage
181    pub peak_usage: usize,
182    /// Available memory
183    pub available_memory: Option<usize>,
184    /// Memory pressure level
185    pub pressure_level: MemoryPressure,
186}
187
188/// Memory pressure levels
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum MemoryPressure {
191    /// Plenty of memory available
192    Low,
193    /// Moderate memory usage
194    Medium,
195    /// High memory usage, consider optimization
196    High,
197    /// Critical memory usage, operations may fail
198    Critical,
199}
200
201/// Thread execution information
202#[derive(Debug, Clone)]
203pub struct ThreadInfo {
204    /// Current thread ID
205    pub thread_id: String,
206    /// Whether the operation is running in parallel
207    pub is_parallel: bool,
208    /// Number of threads being used
209    pub thread_count: Option<usize>,
210    /// Thread pool information
211    pub pool_info: Option<String>,
212}
213
214/// Enhanced error wrapper with context
215#[derive(Debug)]
216pub struct ErrorContext<E> {
217    /// The underlying error
218    error: E,
219    /// Operation context when the error occurred
220    context: OperationContext,
221    /// Location where the error occurred
222    location: Option<ErrorLocation>,
223    /// Error chain (parent errors)
224    chain: Vec<Box<dyn std::error::Error + Send + Sync>>,
225    /// Suggested recovery actions
226    recovery_suggestions: Vec<String>,
227}
228
229impl<E> ErrorContext<E> {
230    /// Create a new error context
231    pub fn new(error: E, context: OperationContext) -> Self {
232        Self {
233            error,
234            context,
235            location: None,
236            chain: Vec::new(),
237            recovery_suggestions: Vec::new(),
238        }
239    }
240
241    /// Add location information
242    pub fn with_location(mut self, location: ErrorLocation) -> Self {
243        self.location = Some(location);
244        self
245    }
246
247    /// Add a parent error to the chain
248    pub fn with_source<S>(mut self, source: S) -> Self
249    where
250        S: std::error::Error + Send + Sync + 'static,
251    {
252        self.chain.push(Box::new(source));
253        self
254    }
255
256    /// Add a recovery suggestion
257    pub fn with_suggestion(mut self, suggestion: &str) -> Self {
258        self.recovery_suggestions.push(suggestion.to_string());
259        self
260    }
261
262    /// Get the underlying error
263    pub fn error(&self) -> &E {
264        &self.error
265    }
266
267    /// Get the operation context
268    pub fn context(&self) -> &OperationContext {
269        &self.context
270    }
271
272    /// Get the error location
273    pub fn location(&self) -> Option<&ErrorLocation> {
274        self.location.as_ref()
275    }
276
277    /// Get the error chain
278    pub fn chain(&self) -> &[Box<dyn std::error::Error + Send + Sync>] {
279        &self.chain
280    }
281
282    /// Get recovery suggestions
283    pub fn recovery_suggestions(&self) -> &[String] {
284        &self.recovery_suggestions
285    }
286
287    /// Consume the wrapper and return the underlying error
288    pub fn into_inner(self) -> E {
289        self.error
290    }
291}
292
293impl<E: fmt::Display> fmt::Display for ErrorContext<E> {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        write!(f, "{}", self.error)?;
296
297        if let Some(ref location) = self.location {
298            write!(f, " at {}", location)?;
299        }
300
301        if let Some(ref operation) = self.context.operation {
302            write!(f, " during '{}'", operation)?;
303        }
304
305        if !self.context.shapes.is_empty() {
306            write!(f, " [shapes: {:?}]", self.context.shapes)?;
307        }
308
309        if !self.recovery_suggestions.is_empty() {
310            write!(f, "\nSuggestions: {}", self.recovery_suggestions.join(", "))?;
311        }
312
313        Ok(())
314    }
315}
316
317impl<E: std::error::Error + 'static> std::error::Error for ErrorContext<E> {
318    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
319        self.chain
320            .first()
321            .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
322    }
323}
324
325/// Macro for creating error location from current position
326#[macro_export]
327macro_rules! error_location {
328    () => {
329        $crate::error::ErrorLocation::with_module(
330            file!(),
331            line!(),
332            stringify!($crate::error::error_location),
333            module_path!(),
334        )
335    };
336    ($func:expr) => {
337        $crate::error::ErrorLocation::with_module(file!(), line!(), $func, module_path!())
338    };
339}
340
341/// Macro for creating operation context with current function
342#[macro_export]
343macro_rules! operation_context {
344    ($op:expr) => {
345        $crate::error::OperationContext::new($op)
346    };
347    ($op:expr, $($key:expr => $value:expr),*) => {
348        {
349            let mut ctx = $crate::error::OperationContext::new($op);
350            $(
351                ctx = ctx.with_parameter($key, $value);
352            )*
353            ctx
354        }
355    };
356}
357
358#[cfg(test)]
359mod tests {
360    use super::*;
361
362    #[test]
363    fn test_error_location() {
364        let location = ErrorLocation::new("test.rs", 42, "test_function");
365        assert_eq!(location.file, "test.rs");
366        assert_eq!(location.line, 42);
367        assert_eq!(location.function, "test_function");
368    }
369
370    #[test]
371    fn test_operation_context() {
372        let ctx = OperationContext::new("matrix_multiply")
373            .with_parameter("method", "BLAS")
374            .with_shape(vec![100, 50])
375            .with_shape(vec![50, 75])
376            .with_dtype("f64");
377
378        assert_eq!(ctx.operation, Some("matrix_multiply".to_string()));
379        assert_eq!(ctx.shapes.len(), 2);
380        assert_eq!(ctx.dtypes, vec!["f64"]);
381        assert!(ctx.parameters.contains_key("method"));
382    }
383
384    #[test]
385    fn test_error_context() {
386        let error = "Test error";
387        let ctx = OperationContext::new("test_operation");
388        let location = ErrorLocation::new("test.rs", 100, "test_function");
389
390        let error_ctx = ErrorContext::new(error, ctx)
391            .with_location(location)
392            .with_suggestion("Try a different approach");
393
394        assert_eq!(*error_ctx.error(), "Test error");
395        assert!(error_ctx.location().is_some());
396        assert_eq!(error_ctx.recovery_suggestions().len(), 1);
397    }
398
399    #[test]
400    fn test_memory_info() {
401        let memory = MemoryInfo {
402            total_allocated: 1024,
403            peak_usage: 2048,
404            available_memory: Some(8192),
405            pressure_level: MemoryPressure::Medium,
406        };
407
408        let ctx = OperationContext::default().with_memory_info(memory);
409        assert!(ctx.memory_info.is_some());
410        assert_eq!(
411            ctx.memory_info
412                .expect("memory_info was just set and should be Some")
413                .total_allocated,
414            1024
415        );
416    }
417}