scirs2_stats/
error_handling_v2.rs

1//! Enhanced error handling system for v1.0.0
2//!
3//! This module provides a unified error handling system with:
4//! - Structured error codes
5//! - Automatic suggestion generation
6//! - Performance impact warnings
7//! - Recovery strategies
8
9use crate::error::StatsError;
10use std::fmt;
11
12/// Error codes for categorization and tracking
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum ErrorCode {
15    // Domain errors (1xxx)
16    E1001, // Value out of domain
17    E1002, // Negative value where positive required
18    E1003, // Probability out of range
19    E1004, // Invalid degrees of freedom
20
21    // Dimension errors (2xxx)
22    E2001, // Array dimension mismatch
23    E2002, // Matrix not square
24    E2003, // Insufficient data points
25    E2004, // Empty input
26
27    // Computation errors (3xxx)
28    E3001, // Numerical overflow
29    E3002, // Numerical underflow
30    E3003, // Convergence failure
31    E3004, // Singular matrix
32    E3005, // NaN encountered
33    E3006, // Infinity encountered
34
35    // Algorithm errors (4xxx)
36    E4001, // Maximum iterations exceeded
37    E4002, // Tolerance not achieved
38    E4003, // Invalid algorithm parameter
39
40    // Memory errors (5xxx)
41    E5001, // Allocation failure
42    E5002, // Memory limit exceeded
43}
44
45impl ErrorCode {
46    /// Get a human-readable description of the error code
47    pub fn description(&self) -> &'static str {
48        match self {
49            ErrorCode::E1001 => "Value is outside the valid domain",
50            ErrorCode::E1002 => "Negative value provided where positive required",
51            ErrorCode::E1003 => "Probability value must be between 0 and 1",
52            ErrorCode::E1004 => "Invalid degrees of freedom",
53
54            ErrorCode::E2001 => "Array dimensions do not match",
55            ErrorCode::E2002 => "Matrix must be square",
56            ErrorCode::E2003 => "Insufficient data points for operation",
57            ErrorCode::E2004 => "Empty input provided",
58
59            ErrorCode::E3001 => "Numerical overflow occurred",
60            ErrorCode::E3002 => "Numerical underflow occurred",
61            ErrorCode::E3003 => "Algorithm failed to converge",
62            ErrorCode::E3004 => "Matrix is singular or near-singular",
63            ErrorCode::E3005 => "NaN (Not a Number) encountered",
64            ErrorCode::E3006 => "Infinity encountered",
65
66            ErrorCode::E4001 => "Maximum iterations exceeded",
67            ErrorCode::E4002 => "Required tolerance not achieved",
68            ErrorCode::E4003 => "Invalid algorithm parameter",
69
70            ErrorCode::E5001 => "Memory allocation failed",
71            ErrorCode::E5002 => "Memory limit exceeded",
72        }
73    }
74
75    /// Get the severity level (1-5, where 1 is most severe)
76    pub fn severity(&self) -> u8 {
77        match self {
78            ErrorCode::E3001 | ErrorCode::E3002 | ErrorCode::E5001 | ErrorCode::E5002 => 1,
79            ErrorCode::E3003 | ErrorCode::E3004 => 2,
80            ErrorCode::E1001 | ErrorCode::E1002 | ErrorCode::E1003 | ErrorCode::E1004 => 3,
81            ErrorCode::E2001 | ErrorCode::E2002 | ErrorCode::E2003 | ErrorCode::E2004 => 3,
82            ErrorCode::E3005 | ErrorCode::E3006 => 3,
83            ErrorCode::E4001 | ErrorCode::E4002 | ErrorCode::E4003 => 4,
84        }
85    }
86}
87
88/// Enhanced error with code, context, and suggestions
89#[derive(Debug)]
90pub struct EnhancedError {
91    /// The error code
92    pub code: ErrorCode,
93    /// The underlying stats error
94    pub error: StatsError,
95    /// Additional context
96    pub context: ErrorContext,
97    /// Recovery suggestions
98    pub suggestions: Vec<RecoverySuggestion>,
99    /// Performance impact if error is ignored
100    pub performance_impact: Option<PerformanceImpact>,
101}
102
103/// Context information for errors
104#[derive(Debug, Clone)]
105pub struct ErrorContext {
106    /// The operation being performed
107    pub operation: String,
108    /// Input parameters that caused the error
109    pub parameters: Vec<(String, String)>,
110    /// Call stack depth
111    pub call_depth: usize,
112    /// Time when error occurred
113    pub timestamp: std::time::SystemTime,
114}
115
116impl ErrorContext {
117    pub fn new(operation: impl Into<String>) -> Self {
118        Self {
119            operation: operation.into(),
120            parameters: Vec::new(),
121            call_depth: 0,
122            timestamp: std::time::SystemTime::now(),
123        }
124    }
125
126    pub fn with_parameter(mut self, name: impl Into<String>, value: impl fmt::Display) -> Self {
127        self.parameters.push((name.into(), value.to_string()));
128        self
129    }
130}
131
132/// Recovery suggestion with actionable steps
133#[derive(Debug, Clone)]
134pub struct RecoverySuggestion {
135    /// Brief title
136    pub title: String,
137    /// Detailed description
138    pub description: String,
139    /// Code example
140    pub example: Option<String>,
141    /// Estimated complexity (1-5)
142    pub complexity: u8,
143    /// Whether this fixes the root cause
144    pub fixes_root_cause: bool,
145}
146
147/// Performance impact information
148#[derive(Debug, Clone)]
149pub struct PerformanceImpact {
150    /// Expected slowdown factor
151    pub slowdown_factor: f64,
152    /// Memory overhead in bytes
153    pub memory_overhead: Option<usize>,
154    /// Description of impact
155    pub description: String,
156}
157
158/// Builder for creating enhanced errors
159pub struct ErrorBuilder {
160    code: ErrorCode,
161    context: ErrorContext,
162    suggestions: Vec<RecoverySuggestion>,
163    performance_impact: Option<PerformanceImpact>,
164}
165
166impl ErrorBuilder {
167    pub fn new(code: ErrorCode, operation: impl Into<String>) -> Self {
168        Self {
169            code,
170            context: ErrorContext::new(operation),
171            suggestions: Vec::new(),
172            performance_impact: None,
173        }
174    }
175
176    pub fn parameter(mut self, name: impl Into<String>, value: impl fmt::Display) -> Self {
177        self.context = self.context.with_parameter(name, value);
178        self
179    }
180
181    pub fn suggestion(mut self, suggestion: RecoverySuggestion) -> Self {
182        self.suggestions.push(suggestion);
183        self
184    }
185
186    pub fn performance_impact(mut self, impact: PerformanceImpact) -> Self {
187        self.performance_impact = Some(impact);
188        self
189    }
190
191    pub fn build(self, error: StatsError) -> EnhancedError {
192        let mut enhanced = EnhancedError {
193            code: self.code,
194            error,
195            context: self.context,
196            suggestions: self.suggestions,
197            performance_impact: self.performance_impact,
198        };
199
200        // Add automatic suggestions based on error code
201        enhanced.add_automatic_suggestions();
202        enhanced
203    }
204}
205
206impl EnhancedError {
207    /// Add automatic suggestions based on the error code
208    fn add_automatic_suggestions(&mut self) {
209        match self.code {
210            ErrorCode::E3005 => {
211                if self.suggestions.is_empty() {
212                    self.suggestions.push(RecoverySuggestion {
213                        title: "Handle NaN values".to_string(),
214                        description: "Filter out or replace NaN values before computation"
215                            .to_string(),
216                        example: Some("data.iter().filter(|x| !x.is_nan())".to_string()),
217                        complexity: 2,
218                        fixes_root_cause: true,
219                    });
220                }
221            }
222            ErrorCode::E2004 => {
223                if self.suggestions.is_empty() {
224                    self.suggestions.push(RecoverySuggestion {
225                        title: "Check input data".to_string(),
226                        description: "Ensure data is loaded correctly and not filtered out"
227                            .to_string(),
228                        example: Some(
229                            "assert!(!data.is_empty(), \"Data cannot be empty\");".to_string(),
230                        ),
231                        complexity: 1,
232                        fixes_root_cause: true,
233                    });
234                }
235            }
236            ErrorCode::E3003 => {
237                if self.suggestions.is_empty() {
238                    self.suggestions.push(RecoverySuggestion {
239                        title: "Adjust convergence parameters".to_string(),
240                        description: "Increase max iterations or relax tolerance".to_string(),
241                        example: Some("options.max_iter(1000).tolerance(1e-6)".to_string()),
242                        complexity: 2,
243                        fixes_root_cause: false,
244                    });
245                }
246            }
247            _ => {}
248        }
249    }
250
251    /// Format the error as a detailed report
252    pub fn detailed_report(&self) -> String {
253        let mut report = format!("Error {}: {}\n\n", self.code, self.code.description());
254
255        report.push_str(&format!("Operation: {}\n", self.context.operation));
256
257        if !self.context.parameters.is_empty() {
258            report.push_str("Parameters:\n");
259            for (name, value) in &self.context.parameters {
260                report.push_str(&format!("  - {}: {}\n", name, value));
261            }
262            report.push('\n');
263        }
264
265        report.push_str(&format!("Details: {}\n\n", self.error));
266
267        if !self.suggestions.is_empty() {
268            report.push_str("Recovery Suggestions:\n");
269            for (i, suggestion) in self.suggestions.iter().enumerate() {
270                report.push_str(&format!(
271                    "{}. {} (complexity: {}/5)\n   {}\n",
272                    i + 1,
273                    suggestion.title,
274                    suggestion.complexity,
275                    suggestion.description
276                ));
277                if let Some(example) = &suggestion.example {
278                    report.push_str(&format!("   Example: {}\n", example));
279                }
280                report.push('\n');
281            }
282        }
283
284        if let Some(impact) = &self.performance_impact {
285            report.push_str(&format!(
286                "Performance Impact if ignored:\n  - Slowdown: {}x\n  - {}\n",
287                impact.slowdown_factor, impact.description
288            ));
289        }
290
291        report
292    }
293}
294
295impl fmt::Display for ErrorCode {
296    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297        write!(f, "{:?}", self)
298    }
299}
300
301/// Helper macros for creating standardized errors
302#[macro_export]
303macro_rules! stats_error {
304    ($code:expr, $op:expr, $msg:expr) => {
305        ErrorBuilder::new($code, $op)
306            .build(StatsError::computation($msg))
307    };
308
309    ($code:expr, $op:expr, $msg:expr, $($param:expr => $value:expr),+) => {
310        ErrorBuilder::new($code, $op)
311            $(.parameter($param, $value))+
312            .build(StatsError::computation($msg))
313    };
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn test_error_builder() {
322        let error = ErrorBuilder::new(ErrorCode::E3005, "mean calculation")
323            .parameter("array_length", 100)
324            .parameter("nan_count", 5)
325            .suggestion(RecoverySuggestion {
326                title: "Remove NaN values".to_string(),
327                description: "Filter array before calculation".to_string(),
328                example: None,
329                complexity: 2,
330                fixes_root_cause: true,
331            })
332            .build(StatsError::computation("NaN values in input"));
333
334        assert_eq!(error.code, ErrorCode::E3005);
335        assert_eq!(error.context.operation, "mean calculation");
336        assert_eq!(error.context.parameters.len(), 2);
337        assert!(!error.suggestions.is_empty());
338    }
339
340    #[test]
341    fn test_error_code_severity() {
342        assert_eq!(ErrorCode::E3001.severity(), 1); // Overflow is severe
343        assert_eq!(ErrorCode::E1001.severity(), 3); // Domain error is moderate
344        assert_eq!(ErrorCode::E4001.severity(), 4); // Max iterations is less severe
345    }
346}