1use std::collections::HashMap;
4use std::fmt;
5use std::time::{SystemTime, UNIX_EPOCH};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
9pub enum ErrorSeverity {
10 Low,
12 Medium,
14 High,
16 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#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct ErrorLocation {
34 pub file: String,
36 pub line: u32,
38 pub function: String,
40 pub module_path: Option<String>,
42}
43
44impl ErrorLocation {
45 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 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#[derive(Debug, Clone)]
82pub struct OperationContext {
83 pub operation: Option<String>,
85 pub parameters: HashMap<String, String>,
87 pub shapes: Vec<Vec<usize>>,
89 pub dtypes: Vec<String>,
91 pub memory_info: Option<MemoryInfo>,
93 pub thread_info: Option<ThreadInfo>,
95 pub performance_hints: Vec<String>,
97 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 pub fn new(operation: &str) -> Self {
122 Self {
123 operation: Some(operation.to_string()),
124 ..Default::default()
125 }
126 }
127
128 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 pub fn with_shape(mut self, shape: Vec<usize>) -> Self {
140 self.shapes.push(shape);
141 self
142 }
143
144 pub fn with_shapes(mut self, shapes: &[Vec<usize>]) -> Self {
146 self.shapes.extend_from_slice(shapes);
147 self
148 }
149
150 pub fn with_dtype(mut self, dtype: &str) -> Self {
152 self.dtypes.push(dtype.to_string());
153 self
154 }
155
156 pub fn with_memory_info(mut self, memory_info: MemoryInfo) -> Self {
158 self.memory_info = Some(memory_info);
159 self
160 }
161
162 pub fn with_thread_info(mut self, thread_info: ThreadInfo) -> Self {
164 self.thread_info = Some(thread_info);
165 self
166 }
167
168 pub fn with_performance_hint(mut self, hint: &str) -> Self {
170 self.performance_hints.push(hint.to_string());
171 self
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct MemoryInfo {
178 pub total_allocated: usize,
180 pub peak_usage: usize,
182 pub available_memory: Option<usize>,
184 pub pressure_level: MemoryPressure,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum MemoryPressure {
191 Low,
193 Medium,
195 High,
197 Critical,
199}
200
201#[derive(Debug, Clone)]
203pub struct ThreadInfo {
204 pub thread_id: String,
206 pub is_parallel: bool,
208 pub thread_count: Option<usize>,
210 pub pool_info: Option<String>,
212}
213
214#[derive(Debug)]
216pub struct ErrorContext<E> {
217 error: E,
219 context: OperationContext,
221 location: Option<ErrorLocation>,
223 chain: Vec<Box<dyn std::error::Error + Send + Sync>>,
225 recovery_suggestions: Vec<String>,
227}
228
229impl<E> ErrorContext<E> {
230 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 pub fn with_location(mut self, location: ErrorLocation) -> Self {
243 self.location = Some(location);
244 self
245 }
246
247 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 pub fn with_suggestion(mut self, suggestion: &str) -> Self {
258 self.recovery_suggestions.push(suggestion.to_string());
259 self
260 }
261
262 pub fn error(&self) -> &E {
264 &self.error
265 }
266
267 pub fn context(&self) -> &OperationContext {
269 &self.context
270 }
271
272 pub fn location(&self) -> Option<&ErrorLocation> {
274 self.location.as_ref()
275 }
276
277 pub fn chain(&self) -> &[Box<dyn std::error::Error + Send + Sync>] {
279 &self.chain
280 }
281
282 pub fn recovery_suggestions(&self) -> &[String] {
284 &self.recovery_suggestions
285 }
286
287 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_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_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}