#[allow(clippy::module_inception)]
mod error;
pub mod error_kind;
pub use error_kind::ErrorKind;
pub mod recovery;
pub use recovery::{
hints, ErrorAggregator, ErrorSeverity, RecoverableError, RecoveryHint, RecoveryStrategy,
};
#[cfg(feature = "async")]
pub mod async_handling;
#[cfg(feature = "async")]
pub use async_handling::{
execute_witherror_aggregation, retry_with_exponential_backoff, with_timeout,
AsyncCircuitBreaker, AsyncErrorAggregator, AsyncProgressTracker, AsyncRetryExecutor,
TimeoutWrapper, TrackedAsyncOperation,
};
pub mod diagnostics;
pub use diagnostics::{
error, error_with_context, EnvironmentInfo, ErrorDiagnosticReport, ErrorDiagnostics,
ErrorOccurrence, ErrorPattern, PerformanceImpact,
};
pub mod circuitbreaker;
pub use circuitbreaker::{
get_circuitbreaker, list_circuitbreakers, CircuitBreaker, CircuitBreakerConfig,
CircuitBreakerStatus, CircuitState, FallbackStrategy, ResilientExecutor, RetryExecutor,
RetryPolicy,
};
pub use error::{
chainerror, check_dimensions, check_domain, check_value, converterror, validate, CoreError,
CoreResult, ErrorContext, ErrorLocation,
};
#[must_use]
#[allow(dead_code)]
pub fn diagnoseerror(error: &CoreError) -> ErrorDiagnosticReport {
diagnoseerror_advanced(error, None, None)
}
#[must_use]
#[allow(dead_code)]
pub fn diagnoseerror_advanced(
error: &CoreError,
context: Option<&str>,
domain: Option<&str>,
) -> ErrorDiagnosticReport {
let diagnostics = ErrorDiagnostics::global();
let mut report = diagnostics.analyzeerror(error);
if let Some(ctx) = context {
report.predictions = diagnostics.predict_potentialerrors(ctx);
}
if let Some(dom) = domain {
report.domain_strategies = diagnostics.suggest_domain_recovery(error, dom);
}
report
}
#[allow(dead_code)]
pub fn recorderror(error: &CoreError, context: String) {
let diagnostics = ErrorDiagnostics::global();
diagnostics.recorderror(error, context);
}
#[must_use]
#[allow(dead_code)]
pub fn context(context: &str) -> Vec<String> {
let diagnostics = ErrorDiagnostics::global();
diagnostics.predict_potentialerrors(context)
}
#[must_use]
#[allow(dead_code)]
pub fn get_recovery_strategies(error: &CoreError, domain: &str) -> Vec<String> {
let diagnostics = ErrorDiagnostics::global();
diagnostics.suggest_domain_recovery(error, domain)
}
pub mod prelude {
pub use super::{
error, CircuitBreaker, CoreError, CoreResult, EnvironmentInfo, ErrorAggregator,
ErrorContext, ErrorDiagnosticReport, ErrorLocation, ErrorSeverity, RecoverableError,
RecoveryStrategy, RetryExecutor,
};
#[cfg(feature = "async")]
pub use super::{
retry_with_exponential_backoff, with_timeout, AsyncCircuitBreaker, AsyncProgressTracker,
AsyncRetryExecutor,
};
pub use crate::{computationerror, dimensionerror, domainerror, error_context, valueerror};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error_context;
use std::time::Duration;
#[test]
fn testerror_integration() {
let error = CoreError::DomainError(error_context!("Test error"));
let recoverable = RecoverableError::error(error);
assert!(!recoverable.retryable);
assert_eq!(recoverable.severity, ErrorSeverity::Error);
assert!(!recoverable.hints.is_empty());
}
#[test]
fn test_retry_executor() {
let policy = RetryPolicy {
max_retries: 2,
initial_delay: Duration::from_millis(1),
..Default::default()
};
let executor = RetryExecutor::new(policy);
let attempts = std::cell::RefCell::new(0);
let result = executor.execute(|| {
let mut count = attempts.borrow_mut();
*count += 1;
if *count == 1 {
Err(CoreError::ComputationError(error_context!(
"Temporary failure"
)))
} else {
Ok(42)
}
});
assert_eq!(result.expect("Operation failed"), 42);
assert_eq!(*attempts.borrow(), 2);
}
#[test]
fn testerror_diagnostics() {
let error = CoreError::MemoryError(error_context!("Out of memory"));
let report = diagnoseerror(&error);
assert!(matches!(report.error, CoreError::MemoryError(_)));
assert_eq!(report.performance_impact, PerformanceImpact::High);
assert!(!report.contextual_suggestions.is_empty());
}
#[test]
fn test_circuitbreaker() {
use crate::error::circuitbreaker::{CircuitBreakerConfig, CircuitState};
use std::time::Duration;
let config = CircuitBreakerConfig {
failure_threshold: 1,
success_threshold: 1,
timeout: Duration::from_secs(30),
};
let breaker = CircuitBreaker::new(config);
let result: std::result::Result<(), CoreError> =
breaker.execute(|| Err(CoreError::ComputationError(error_context!("Test failure"))));
assert!(result.is_err());
let result = breaker.execute(|| Ok(42));
assert!(result.is_err());
let status = breaker.status();
assert_eq!(status.failure_count, 1);
assert_eq!(status.state, CircuitState::Open);
}
#[test]
fn testerror_aggregator() {
let mut aggregator = ErrorAggregator::new();
aggregator.add_simpleerror(CoreError::ValueError(error_context!("Error 1")));
aggregator.add_simpleerror(CoreError::DomainError(error_context!("Error 2")));
assert_eq!(aggregator.error_count(), 2);
assert!(aggregator.haserrors());
let summary = aggregator.summary();
assert!(summary.contains("Collected 2 error(s)"));
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_asyncerror_handling() {
use super::async_handling::*;
let result = with_timeout(
async {
tokio::time::sleep(Duration::from_millis(100)).await;
Ok::<i32, CoreError>(42)
},
Duration::from_millis(50),
)
.await;
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), CoreError::TimeoutError(_)));
let executor = AsyncRetryExecutor::new(RecoveryStrategy::LinearBackoff {
max_attempts: 2,
delay: Duration::from_millis(1),
});
let mut attempts = 0;
let result = executor
.execute(|| {
attempts += 1;
async move {
if attempts == 1 {
Err(CoreError::ComputationError(error_context!("Async failure")))
} else {
Ok(42)
}
}
})
.await;
assert_eq!(result.expect("Operation failed"), 42);
assert_eq!(attempts, 2);
}
}