1#[allow(clippy::module_inception)]
118mod error;
119
120pub mod error_kind;
122pub use error_kind::ErrorKind;
123
124pub mod recovery;
126pub use recovery::{
127 hints, ErrorAggregator, ErrorSeverity, RecoverableError, RecoveryHint, RecoveryStrategy,
128};
129
130#[cfg(feature = "async")]
132pub mod async_handling;
133#[cfg(feature = "async")]
134pub use async_handling::{
135 execute_witherror_aggregation, retry_with_exponential_backoff, with_timeout,
136 AsyncCircuitBreaker, AsyncErrorAggregator, AsyncProgressTracker, AsyncRetryExecutor,
137 TimeoutWrapper, TrackedAsyncOperation,
138};
139
140pub mod diagnostics;
142pub use diagnostics::{
143 error, error_with_context, EnvironmentInfo, ErrorDiagnosticReport, ErrorDiagnostics,
144 ErrorOccurrence, ErrorPattern, PerformanceImpact,
145};
146
147pub mod circuitbreaker;
149pub use circuitbreaker::{
150 get_circuitbreaker, list_circuitbreakers, CircuitBreaker, CircuitBreakerConfig,
151 CircuitBreakerStatus, CircuitState, FallbackStrategy, ResilientExecutor, RetryExecutor,
152 RetryPolicy,
153};
154
155pub use error::{
157 chainerror, check_dimensions, check_domain, check_value, converterror, validate, CoreError,
158 CoreResult, ErrorContext, ErrorLocation,
159};
160
161#[must_use]
169#[allow(dead_code)]
170pub fn diagnoseerror(error: &CoreError) -> ErrorDiagnosticReport {
171 diagnoseerror_advanced(error, None, None)
172}
173
174#[must_use]
180#[allow(dead_code)]
181pub fn diagnoseerror_advanced(
182 error: &CoreError,
183 context: Option<&str>,
184 domain: Option<&str>,
185) -> ErrorDiagnosticReport {
186 let diagnostics = ErrorDiagnostics::global();
187 let mut report = diagnostics.analyzeerror(error);
188
189 if let Some(ctx) = context {
191 report.predictions = diagnostics.predict_potentialerrors(ctx);
192 }
193
194 if let Some(dom) = domain {
196 report.domain_strategies = diagnostics.suggest_domain_recovery(error, dom);
197 }
198
199 report
200}
201
202#[allow(dead_code)]
204pub fn recorderror(error: &CoreError, context: String) {
205 let diagnostics = ErrorDiagnostics::global();
206 diagnostics.recorderror(error, context);
207}
208
209#[must_use]
211#[allow(dead_code)]
212pub fn context(context: &str) -> Vec<String> {
213 let diagnostics = ErrorDiagnostics::global();
214 diagnostics.predict_potentialerrors(context)
215}
216
217#[must_use]
219#[allow(dead_code)]
220pub fn get_recovery_strategies(error: &CoreError, domain: &str) -> Vec<String> {
221 let diagnostics = ErrorDiagnostics::global();
222 diagnostics.suggest_domain_recovery(error, domain)
223}
224
225pub mod prelude {
226 pub use super::{
230 error, CircuitBreaker, CoreError, CoreResult, EnvironmentInfo, ErrorAggregator,
231 ErrorContext, ErrorDiagnosticReport, ErrorLocation, ErrorSeverity, RecoverableError,
232 RecoveryStrategy, RetryExecutor,
233 };
234
235 #[cfg(feature = "async")]
236 pub use super::{
237 retry_with_exponential_backoff, with_timeout, AsyncCircuitBreaker, AsyncProgressTracker,
238 AsyncRetryExecutor,
239 };
240
241 pub use crate::{computationerror, dimensionerror, domainerror, error_context, valueerror};
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use crate::error_context;
249 use std::time::Duration;
250
251 #[test]
252 fn testerror_integration() {
253 let error = CoreError::DomainError(error_context!("Test error"));
254 let recoverable = RecoverableError::error(error);
255
256 assert!(!recoverable.retryable);
257 assert_eq!(recoverable.severity, ErrorSeverity::Error);
258 assert!(!recoverable.hints.is_empty());
259 }
260
261 #[test]
262 fn test_retry_executor() {
263 let policy = RetryPolicy {
264 max_retries: 2,
265 initial_delay: Duration::from_millis(1),
266 ..Default::default()
267 };
268 let executor = RetryExecutor::new(policy);
269
270 let attempts = std::cell::RefCell::new(0);
271 let result = executor.execute(|| {
272 let mut count = attempts.borrow_mut();
273 *count += 1;
274 if *count == 1 {
275 Err(CoreError::ComputationError(error_context!(
276 "Temporary failure"
277 )))
278 } else {
279 Ok(42)
280 }
281 });
282
283 assert_eq!(result.expect("Operation failed"), 42);
284 assert_eq!(*attempts.borrow(), 2);
285 }
286
287 #[test]
288 fn testerror_diagnostics() {
289 let error = CoreError::MemoryError(error_context!("Out of memory"));
290 let report = diagnoseerror(&error);
291
292 assert!(matches!(report.error, CoreError::MemoryError(_)));
293 assert_eq!(report.performance_impact, PerformanceImpact::High);
294 assert!(!report.contextual_suggestions.is_empty());
295 }
296
297 #[test]
298 fn test_circuitbreaker() {
299 use crate::error::circuitbreaker::{CircuitBreakerConfig, CircuitState};
300 use std::time::Duration;
301
302 let config = CircuitBreakerConfig {
304 failure_threshold: 1,
305 success_threshold: 1,
306 timeout: Duration::from_secs(30),
307 };
308 let breaker = CircuitBreaker::new(config);
309
310 let result: std::result::Result<(), CoreError> =
312 breaker.execute(|| Err(CoreError::ComputationError(error_context!("Test failure"))));
313 assert!(result.is_err());
314
315 let result = breaker.execute(|| Ok(42));
317 assert!(result.is_err());
318
319 let status = breaker.status();
320 assert_eq!(status.failure_count, 1);
321 assert_eq!(status.state, CircuitState::Open);
322 }
323
324 #[test]
325 fn testerror_aggregator() {
326 let mut aggregator = ErrorAggregator::new();
327
328 aggregator.add_simpleerror(CoreError::ValueError(error_context!("Error 1")));
329 aggregator.add_simpleerror(CoreError::DomainError(error_context!("Error 2")));
330
331 assert_eq!(aggregator.error_count(), 2);
332 assert!(aggregator.haserrors());
333
334 let summary = aggregator.summary();
335 assert!(summary.contains("Collected 2 error(s)"));
336 }
337
338 #[cfg(feature = "async")]
339 #[tokio::test]
340 async fn test_asyncerror_handling() {
341 use super::async_handling::*;
342
343 let result = with_timeout(
345 async {
346 tokio::time::sleep(Duration::from_millis(100)).await;
347 Ok::<i32, CoreError>(42)
348 },
349 Duration::from_millis(50),
350 )
351 .await;
352
353 assert!(result.is_err());
354 assert!(matches!(result.unwrap_err(), CoreError::TimeoutError(_)));
355
356 let executor = AsyncRetryExecutor::new(RecoveryStrategy::LinearBackoff {
358 max_attempts: 2,
359 delay: Duration::from_millis(1),
360 });
361
362 let mut attempts = 0;
363 let result = executor
364 .execute(|| {
365 attempts += 1;
366 async move {
367 if attempts == 1 {
368 Err(CoreError::ComputationError(error_context!("Async failure")))
369 } else {
370 Ok(42)
371 }
372 }
373 })
374 .await;
375
376 assert_eq!(result.expect("Operation failed"), 42);
377 assert_eq!(attempts, 2);
378 }
379}