use crate::error::StatsError;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCode {
E1001, E1002, E1003, E1004,
E2001, E2002, E2003, E2004,
E3001, E3002, E3003, E3004, E3005, E3006,
E4001, E4002, E4003,
E5001, E5002, }
impl ErrorCode {
pub fn description(&self) -> &'static str {
match self {
ErrorCode::E1001 => "Value is outside the valid domain",
ErrorCode::E1002 => "Negative value provided where positive required",
ErrorCode::E1003 => "Probability value must be between 0 and 1",
ErrorCode::E1004 => "Invalid degrees of freedom",
ErrorCode::E2001 => "Array dimensions do not match",
ErrorCode::E2002 => "Matrix must be square",
ErrorCode::E2003 => "Insufficient data points for operation",
ErrorCode::E2004 => "Empty input provided",
ErrorCode::E3001 => "Numerical overflow occurred",
ErrorCode::E3002 => "Numerical underflow occurred",
ErrorCode::E3003 => "Algorithm failed to converge",
ErrorCode::E3004 => "Matrix is singular or near-singular",
ErrorCode::E3005 => "NaN (Not a Number) encountered",
ErrorCode::E3006 => "Infinity encountered",
ErrorCode::E4001 => "Maximum iterations exceeded",
ErrorCode::E4002 => "Required tolerance not achieved",
ErrorCode::E4003 => "Invalid algorithm parameter",
ErrorCode::E5001 => "Memory allocation failed",
ErrorCode::E5002 => "Memory limit exceeded",
}
}
pub fn severity(&self) -> u8 {
match self {
ErrorCode::E3001 | ErrorCode::E3002 | ErrorCode::E5001 | ErrorCode::E5002 => 1,
ErrorCode::E3003 | ErrorCode::E3004 => 2,
ErrorCode::E1001 | ErrorCode::E1002 | ErrorCode::E1003 | ErrorCode::E1004 => 3,
ErrorCode::E2001 | ErrorCode::E2002 | ErrorCode::E2003 | ErrorCode::E2004 => 3,
ErrorCode::E3005 | ErrorCode::E3006 => 3,
ErrorCode::E4001 | ErrorCode::E4002 | ErrorCode::E4003 => 4,
}
}
}
#[derive(Debug)]
pub struct EnhancedError {
pub code: ErrorCode,
pub error: StatsError,
pub context: ErrorContext,
pub suggestions: Vec<RecoverySuggestion>,
pub performance_impact: Option<PerformanceImpact>,
}
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub operation: String,
pub parameters: Vec<(String, String)>,
pub call_depth: usize,
pub timestamp: std::time::SystemTime,
}
impl ErrorContext {
pub fn new(operation: impl Into<String>) -> Self {
Self {
operation: operation.into(),
parameters: Vec::new(),
call_depth: 0,
timestamp: std::time::SystemTime::now(),
}
}
pub fn with_parameter(mut self, name: impl Into<String>, value: impl fmt::Display) -> Self {
self.parameters.push((name.into(), value.to_string()));
self
}
}
#[derive(Debug, Clone)]
pub struct RecoverySuggestion {
pub title: String,
pub description: String,
pub example: Option<String>,
pub complexity: u8,
pub fixes_root_cause: bool,
}
#[derive(Debug, Clone)]
pub struct PerformanceImpact {
pub slowdown_factor: f64,
pub memory_overhead: Option<usize>,
pub description: String,
}
pub struct ErrorBuilder {
code: ErrorCode,
context: ErrorContext,
suggestions: Vec<RecoverySuggestion>,
performance_impact: Option<PerformanceImpact>,
}
impl ErrorBuilder {
pub fn new(code: ErrorCode, operation: impl Into<String>) -> Self {
Self {
code,
context: ErrorContext::new(operation),
suggestions: Vec::new(),
performance_impact: None,
}
}
pub fn parameter(mut self, name: impl Into<String>, value: impl fmt::Display) -> Self {
self.context = self.context.with_parameter(name, value);
self
}
pub fn suggestion(mut self, suggestion: RecoverySuggestion) -> Self {
self.suggestions.push(suggestion);
self
}
pub fn performance_impact(mut self, impact: PerformanceImpact) -> Self {
self.performance_impact = Some(impact);
self
}
pub fn build(self, error: StatsError) -> EnhancedError {
let mut enhanced = EnhancedError {
code: self.code,
error,
context: self.context,
suggestions: self.suggestions,
performance_impact: self.performance_impact,
};
enhanced.add_automatic_suggestions();
enhanced
}
}
impl EnhancedError {
fn add_automatic_suggestions(&mut self) {
match self.code {
ErrorCode::E3005 => {
if self.suggestions.is_empty() {
self.suggestions.push(RecoverySuggestion {
title: "Handle NaN values".to_string(),
description: "Filter out or replace NaN values before computation"
.to_string(),
example: Some("data.iter().filter(|x| !x.is_nan())".to_string()),
complexity: 2,
fixes_root_cause: true,
});
}
}
ErrorCode::E2004 => {
if self.suggestions.is_empty() {
self.suggestions.push(RecoverySuggestion {
title: "Check input data".to_string(),
description: "Ensure data is loaded correctly and not filtered out"
.to_string(),
example: Some(
"assert!(!data.is_empty(), \"Data cannot be empty\");".to_string(),
),
complexity: 1,
fixes_root_cause: true,
});
}
}
ErrorCode::E3003 => {
if self.suggestions.is_empty() {
self.suggestions.push(RecoverySuggestion {
title: "Adjust convergence parameters".to_string(),
description: "Increase max iterations or relax tolerance".to_string(),
example: Some("options.max_iter(1000).tolerance(1e-6)".to_string()),
complexity: 2,
fixes_root_cause: false,
});
}
}
_ => {}
}
}
pub fn detailed_report(&self) -> String {
let mut report = format!("Error {}: {}\n\n", self.code, self.code.description());
report.push_str(&format!("Operation: {}\n", self.context.operation));
if !self.context.parameters.is_empty() {
report.push_str("Parameters:\n");
for (name, value) in &self.context.parameters {
report.push_str(&format!(" - {}: {}\n", name, value));
}
report.push('\n');
}
report.push_str(&format!("Details: {}\n\n", self.error));
if !self.suggestions.is_empty() {
report.push_str("Recovery Suggestions:\n");
for (i, suggestion) in self.suggestions.iter().enumerate() {
report.push_str(&format!(
"{}. {} (complexity: {}/5)\n {}\n",
i + 1,
suggestion.title,
suggestion.complexity,
suggestion.description
));
if let Some(example) = &suggestion.example {
report.push_str(&format!(" Example: {}\n", example));
}
report.push('\n');
}
}
if let Some(impact) = &self.performance_impact {
report.push_str(&format!(
"Performance Impact if ignored:\n - Slowdown: {}x\n - {}\n",
impact.slowdown_factor, impact.description
));
}
report
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[macro_export]
macro_rules! stats_error {
($code:expr, $op:expr, $msg:expr) => {
ErrorBuilder::new($code, $op)
.build(StatsError::computation($msg))
};
($code:expr, $op:expr, $msg:expr, $($param:expr => $value:expr),+) => {
ErrorBuilder::new($code, $op)
$(.parameter($param, $value))+
.build(StatsError::computation($msg))
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_builder() {
let error = ErrorBuilder::new(ErrorCode::E3005, "mean calculation")
.parameter("array_length", 100)
.parameter("nan_count", 5)
.suggestion(RecoverySuggestion {
title: "Remove NaN values".to_string(),
description: "Filter array before calculation".to_string(),
example: None,
complexity: 2,
fixes_root_cause: true,
})
.build(StatsError::computation("NaN values in input"));
assert_eq!(error.code, ErrorCode::E3005);
assert_eq!(error.context.operation, "mean calculation");
assert_eq!(error.context.parameters.len(), 2);
assert!(!error.suggestions.is_empty());
}
#[test]
fn test_error_code_severity() {
assert_eq!(ErrorCode::E3001.severity(), 1); assert_eq!(ErrorCode::E1001.severity(), 3); assert_eq!(ErrorCode::E4001.severity(), 4); }
}