use crate::{UtilsError, UtilsResult};
use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct EnhancedError {
pub error: UtilsError,
pub context: Vec<String>,
pub stack_trace: Vec<String>,
pub timestamp: std::time::SystemTime,
pub error_id: String,
pub metadata: HashMap<String, String>,
}
impl EnhancedError {
pub fn new(error: UtilsError) -> Self {
Self {
error,
context: Vec::new(),
stack_trace: Self::capture_stack_trace(),
timestamp: std::time::SystemTime::now(),
error_id: Self::generate_error_id(),
metadata: HashMap::new(),
}
}
pub fn with_context<S: Into<String>>(mut self, context: S) -> Self {
self.context.push(context.into());
self
}
pub fn with_metadata<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
fn generate_error_id() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
format!("ERR-{timestamp:016x}")
}
fn capture_stack_trace() -> Vec<String> {
vec![
"stack_trace: enhanced_error.rs:capture_stack_trace".to_string(),
"stack_trace: error_handling.rs:new".to_string(),
]
}
pub fn format_detailed(&self) -> String {
let mut output = String::new();
output.push_str(&format!("Error ID: {}\n", self.error_id));
output.push_str(&format!("Timestamp: {:?}\n", self.timestamp));
output.push_str(&format!("Error: {}\n", self.error));
if !self.context.is_empty() {
output.push_str("Context:\n");
for (i, ctx) in self.context.iter().enumerate() {
output.push_str(&format!(" {}: {ctx}\n", i + 1));
}
}
if !self.metadata.is_empty() {
output.push_str("Metadata:\n");
for (key, value) in &self.metadata {
output.push_str(&format!(" {key}: {value}\n"));
}
}
if !self.stack_trace.is_empty() {
output.push_str("Stack Trace:\n");
for frame in &self.stack_trace {
output.push_str(&format!(" {frame}\n"));
}
}
output
}
}
impl fmt::Display for EnhancedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (ID: {})", self.error, self.error_id)
}
}
impl std::error::Error for EnhancedError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.error)
}
}
pub struct ErrorContext {
operation: String,
parameters: HashMap<String, String>,
location: Option<String>,
}
impl ErrorContext {
pub fn new<S: Into<String>>(operation: S) -> Self {
Self {
operation: operation.into(),
parameters: HashMap::new(),
location: None,
}
}
pub fn with_param<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
self.parameters.insert(key.into(), value.into());
self
}
pub fn at_location<S: Into<String>>(mut self, location: S) -> Self {
self.location = Some(location.into());
self
}
pub fn wrap_error<E: Into<UtilsError>>(self, error: E) -> EnhancedError {
let mut enhanced = EnhancedError::new(error.into());
enhanced = enhanced.with_context(format!("Operation: {}", self.operation));
if let Some(location) = self.location {
enhanced = enhanced.with_context(format!("Location: {location}"));
}
for (key, value) in self.parameters {
enhanced = enhanced.with_metadata(key, value);
}
enhanced
}
}
#[derive(Debug, Clone)]
pub struct ErrorAggregator {
errors: Vec<EnhancedError>,
max_errors: usize,
continue_on_error: bool,
}
impl ErrorAggregator {
pub fn new(max_errors: usize, continue_on_error: bool) -> Self {
Self {
errors: Vec::new(),
max_errors,
continue_on_error,
}
}
pub fn add_error(&mut self, error: EnhancedError) -> UtilsResult<()> {
self.errors.push(error);
if self.errors.len() >= self.max_errors {
if self.continue_on_error {
self.errors.remove(0);
} else {
return Err(UtilsError::InvalidParameter(
"Maximum error count reached".to_string(),
));
}
}
Ok(())
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn error_count(&self) -> usize {
self.errors.len()
}
pub fn get_errors(&self) -> &[EnhancedError] {
&self.errors
}
pub fn clear(&mut self) {
self.errors.clear();
}
pub fn generate_summary(&self) -> ErrorSummary {
let mut summary = ErrorSummary::default();
for error in &self.errors {
summary.total_errors += 1;
match &error.error {
UtilsError::ShapeMismatch { .. } => summary.shape_errors += 1,
UtilsError::InvalidParameter(_) => summary.parameter_errors += 1,
UtilsError::EmptyInput => summary.input_errors += 1,
UtilsError::InvalidRandomState(_) => summary.random_state_errors += 1,
UtilsError::InsufficientData { .. } => summary.data_errors += 1,
}
}
summary
}
pub fn export_errors(&self) -> Vec<HashMap<String, String>> {
self.errors
.iter()
.map(|error| {
let mut export = HashMap::new();
export.insert("id".to_string(), error.error_id.clone());
export.insert("error".to_string(), error.error.to_string());
export.insert("timestamp".to_string(), format!("{:?}", error.timestamp));
export.insert("context".to_string(), error.context.join("; "));
for (key, value) in &error.metadata {
export.insert(format!("meta_{key}"), value.clone());
}
export
})
.collect()
}
}
#[derive(Debug, Default, Clone)]
pub struct ErrorSummary {
pub total_errors: usize,
pub shape_errors: usize,
pub parameter_errors: usize,
pub input_errors: usize,
pub random_state_errors: usize,
pub data_errors: usize,
}
pub struct ErrorRecovery;
impl ErrorRecovery {
pub fn recover_shape_mismatch(expected: &[usize], actual: &[usize]) -> Option<Vec<usize>> {
if expected.len() == actual.len() {
let expected_size: usize = expected.iter().product();
let actual_size: usize = actual.iter().product();
if expected_size == actual_size {
return Some(expected.to_vec());
}
}
if expected.len() == 1 && actual.len() == 2 {
let total_size: usize = actual.iter().product();
return Some(vec![total_size]);
}
if expected.len() == 2 && actual.len() == 1 {
let size = actual[0];
for i in 1..=(size as f64).sqrt() as usize + 1 {
if size % i == 0 {
return Some(vec![i, size / i]);
}
}
}
None
}
pub fn recover_insufficient_data(
required: usize,
available: usize,
) -> Option<RecoveryStrategy> {
if available == 0 {
return Some(RecoveryStrategy::GenerateSyntheticData(required));
}
if available < required {
if available >= required / 2 {
return Some(RecoveryStrategy::ReduceRequirement(available));
} else {
return Some(RecoveryStrategy::AugmentData(required - available));
}
}
None
}
pub fn recover_invalid_parameter(param_name: &str, param_value: &str) -> Option<String> {
match param_name {
"n_components" | "n_clusters" | "max_iter" => {
if let Ok(val) = param_value.parse::<i32>() {
if val <= 0 {
return Some("1".to_string());
}
}
Some("10".to_string())
}
"random_state" => {
Some("42".to_string())
}
"tolerance" | "alpha" | "learning_rate" => {
if let Ok(val) = param_value.parse::<f64>() {
if val <= 0.0 {
return Some("0.01".to_string());
}
}
Some("0.01".to_string())
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub enum RecoveryStrategy {
GenerateSyntheticData(usize),
ReduceRequirement(usize),
AugmentData(usize),
ReshapeData(Vec<usize>),
UseDefaultParameter(String),
}
pub struct ErrorReporter {
errors: Arc<Mutex<Vec<EnhancedError>>>,
enabled: bool,
}
impl Default for ErrorReporter {
fn default() -> Self {
Self::new()
}
}
impl ErrorReporter {
pub fn new() -> Self {
Self {
errors: Arc::new(Mutex::new(Vec::new())),
enabled: true,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn report_error(&self, error: EnhancedError) {
if !self.enabled {
return;
}
if let Ok(mut errors) = self.errors.lock() {
errors.push(error);
if errors.len() > 1000 {
errors.remove(0);
}
}
}
pub fn get_statistics(&self) -> Option<ErrorStatistics> {
let errors = self.errors.lock().ok()?;
if errors.is_empty() {
return None;
}
let mut stats = ErrorStatistics {
total_errors: errors.len(),
..Default::default()
};
for error in errors.iter() {
match &error.error {
UtilsError::ShapeMismatch { .. } => stats.shape_errors += 1,
UtilsError::InvalidParameter(_) => stats.parameter_errors += 1,
UtilsError::EmptyInput => stats.input_errors += 1,
UtilsError::InvalidRandomState(_) => stats.random_state_errors += 1,
UtilsError::InsufficientData { .. } => stats.data_errors += 1,
}
}
let now = std::time::SystemTime::now();
let one_hour_ago = now - std::time::Duration::from_secs(3600);
let one_day_ago = now - std::time::Duration::from_secs(86400);
stats.errors_last_hour = errors.iter().filter(|e| e.timestamp > one_hour_ago).count();
stats.errors_last_day = errors.iter().filter(|e| e.timestamp > one_day_ago).count();
Some(stats)
}
pub fn clear(&self) {
if let Ok(mut errors) = self.errors.lock() {
errors.clear();
}
}
}
#[derive(Debug, Default, Clone)]
pub struct ErrorStatistics {
pub total_errors: usize,
pub shape_errors: usize,
pub parameter_errors: usize,
pub input_errors: usize,
pub random_state_errors: usize,
pub data_errors: usize,
pub errors_last_hour: usize,
pub errors_last_day: usize,
}
pub fn create_error<E: Into<UtilsError>>(error: E, operation: &str) -> EnhancedError {
ErrorContext::new(operation).wrap_error(error)
}
pub fn create_error_at<E: Into<UtilsError>>(
error: E,
operation: &str,
location: &str,
) -> EnhancedError {
ErrorContext::new(operation)
.at_location(location)
.wrap_error(error)
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enhanced_error() {
let base_error = UtilsError::InvalidParameter("test".to_string());
let enhanced = EnhancedError::new(base_error)
.with_context("Processing data")
.with_metadata("operation", "test_operation");
assert!(!enhanced.error_id.is_empty());
assert_eq!(enhanced.context.len(), 1);
assert_eq!(enhanced.metadata.len(), 1);
let formatted = enhanced.format_detailed();
assert!(formatted.contains("Error ID:"));
assert!(formatted.contains("Processing data"));
}
#[test]
fn test_error_context() {
let context = ErrorContext::new("test_operation")
.with_param("param1", "value1")
.at_location("test_file.rs:123");
let base_error = UtilsError::EmptyInput;
let enhanced = context.wrap_error(base_error);
assert!(enhanced.context.len() >= 2);
assert!(enhanced.metadata.contains_key("param1"));
}
#[test]
fn test_error_aggregator() {
let mut aggregator = ErrorAggregator::new(3, false);
assert!(!aggregator.has_errors());
assert_eq!(aggregator.error_count(), 0);
let error1 = EnhancedError::new(UtilsError::EmptyInput);
let error2 = EnhancedError::new(UtilsError::InvalidParameter("test".to_string()));
aggregator
.add_error(error1)
.expect("operation should succeed");
aggregator
.add_error(error2)
.expect("operation should succeed");
assert!(aggregator.has_errors());
assert_eq!(aggregator.error_count(), 2);
let summary = aggregator.generate_summary();
assert_eq!(summary.total_errors, 2);
assert_eq!(summary.input_errors, 1);
assert_eq!(summary.parameter_errors, 1);
}
#[test]
fn test_error_recovery() {
let recovery = ErrorRecovery::recover_shape_mismatch(&[10], &[2, 5]);
assert_eq!(recovery, Some(vec![10]));
let strategy = ErrorRecovery::recover_insufficient_data(100, 50);
match strategy {
Some(RecoveryStrategy::ReduceRequirement(50)) => (),
_ => panic!("Expected ReduceRequirement strategy"),
}
let fixed = ErrorRecovery::recover_invalid_parameter("n_clusters", "-5");
assert_eq!(fixed, Some("1".to_string()));
}
#[test]
fn test_error_reporter() {
let reporter = ErrorReporter::new();
let error = EnhancedError::new(UtilsError::EmptyInput);
reporter.report_error(error);
let stats = reporter.get_statistics().expect("operation should succeed");
assert_eq!(stats.total_errors, 1);
assert_eq!(stats.input_errors, 1);
}
#[test]
fn test_convenience_functions() {
let error = create_error(UtilsError::EmptyInput, "test_operation");
assert!(error.context.iter().any(|c| c.contains("test_operation")));
let error_with_location =
create_error_at(UtilsError::EmptyInput, "test_operation", "test_file.rs:123");
assert!(error_with_location.context.len() >= 2);
}
}