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