1use crate::error::StatsError;
10use std::fmt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum ErrorCode {
15 E1001, E1002, E1003, E1004, E2001, E2002, E2003, E2004, E3001, E3002, E3003, E3004, E3005, E3006, E4001, E4002, E4003, E5001, E5002, }
44
45impl ErrorCode {
46 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 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#[derive(Debug)]
90pub struct EnhancedError {
91 pub code: ErrorCode,
93 pub error: StatsError,
95 pub context: ErrorContext,
97 pub suggestions: Vec<RecoverySuggestion>,
99 pub performance_impact: Option<PerformanceImpact>,
101}
102
103#[derive(Debug, Clone)]
105pub struct ErrorContext {
106 pub operation: String,
108 pub parameters: Vec<(String, String)>,
110 pub call_depth: usize,
112 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#[derive(Debug, Clone)]
134pub struct RecoverySuggestion {
135 pub title: String,
137 pub description: String,
139 pub example: Option<String>,
141 pub complexity: u8,
143 pub fixes_root_cause: bool,
145}
146
147#[derive(Debug, Clone)]
149pub struct PerformanceImpact {
150 pub slowdown_factor: f64,
152 pub memory_overhead: Option<usize>,
154 pub description: String,
156}
157
158pub 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 enhanced.add_automatic_suggestions();
202 enhanced
203 }
204}
205
206impl EnhancedError {
207 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 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#[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); assert_eq!(ErrorCode::E1001.severity(), 3); assert_eq!(ErrorCode::E4001.severity(), 4); }
346}