decrust_core/lib.rs
1/* src/lib.rs */
2#![warn(missing_docs)]
3#![allow(stable_features)]
4#![allow(clippy::result_large_err)]
5#![allow(clippy::doc_lazy_continuation)]
6#![allow(clippy::new_without_default)]
7#![allow(clippy::useless_format)]
8#![allow(clippy::unnecessary_to_owned)]
9#![allow(clippy::too_many_arguments)]
10#![allow(clippy::type_complexity)]
11#![allow(clippy::single_char_add_str)]
12#![allow(clippy::large_enum_variant)]
13#![allow(clippy::useless_vec)]
14#![allow(clippy::assertions_on_constants)]
15#![allow(clippy::await_holding_lock)]
16#![allow(clippy::unwrap_or_default)]
17#![allow(clippy::vec_init_then_push)]
18#![allow(clippy::field_reassign_with_default)]
19#![allow(clippy::overly_complex_bool_expr)]
20#![allow(clippy::len_zero)]
21//! # Decrust: Advanced Error Handling Framework for Rust
22// ~=####====A===r===c===M===o===o===n====S===t===u===d===i===o===s====X|0|$>
23//! Decrust is a comprehensive, production-ready error handling framework that provides
24//! rich error context, automatic error recovery, circuit breaker patterns, and powerful
25//! debugging capabilities. It's designed to make error handling in Rust applications
26//! both robust and developer-friendly.
27//!
28//! ## 🚀 Quick Start
29//!
30//! ```rust
31//! use decrust_core::{DecrustError, DecrustResultExt, DecrustOptionExt, oops, validation_error};
32//!
33//! // Basic error creation with rich context
34//! fn process_user_data(data: Option<&str>) -> Result<String, DecrustError> {
35//! let user_data = data.decrust_ok_or_missing_value("user data")?;
36//!
37//! if user_data.is_empty() {
38//! return Err(validation_error!("user_data", "Data cannot be empty"));
39//! }
40//!
41//! // Simulate an IO operation that might fail
42//! std::fs::read_to_string("config.json")
43//! .map_err(|e| oops!("Failed to read configuration", e))
44//! .and_then(|_| Ok(format!("Processed: {}", user_data)))
45//! }
46//! ```
47//!
48//! ## 🎯 Core Features
49//!
50//! ### 1. **Rich Error Context** 📍
51//! Every error includes comprehensive context with location tracking, severity levels,
52//! and metadata for better debugging and monitoring.
53//!
54//! ```rust
55//! use decrust_core::{error_context, types::ErrorSeverity, oops};
56//!
57//! // Create rich error context with metadata
58//! let context = error_context!(
59//! "Database connection failed",
60//! severity: ErrorSeverity::Critical
61//! ).with_component("database")
62//! .with_correlation_id("req-123")
63//! .with_recovery_suggestion("Check database connectivity");
64//!
65//! // Use in error creation
66//! let io_error = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "Connection refused");
67//! let error = oops!("Database unavailable", io_error, severity: ErrorSeverity::Critical);
68//! ```
69//!
70//! ### 2. **Circuit Breaker Pattern** ⚡
71//! Built-in circuit breaker for handling external service failures gracefully.
72//!
73//! ```rust
74//! use decrust_core::{circuit_breaker::{CircuitBreaker, CircuitBreakerConfig}, DecrustError, Backtrace};
75//! use std::time::Duration;
76//!
77//! // Configure circuit breaker
78//! let config = CircuitBreakerConfig {
79//! failure_threshold: 5,
80//! reset_timeout: Duration::from_secs(30),
81//! operation_timeout: Some(Duration::from_secs(5)),
82//! ..Default::default()
83//! };
84//!
85//! let circuit_breaker = CircuitBreaker::new("external-api", config);
86//!
87//! // Execute operations through circuit breaker
88//! # fn external_api_call() -> Result<String, std::io::Error> { Ok("success".to_string()) }
89//! let result = circuit_breaker.execute(|| {
90//! // Your external service call here
91//! external_api_call().map_err(|e| DecrustError::Oops {
92//! message: "API call failed".to_string(),
93//! source: Box::new(e),
94//! backtrace: Backtrace::generate(),
95//! })
96//! });
97//! ```
98//!
99//! ### 3. **Automatic Error Recovery** 🔄
100//! Smart error recovery with configurable retry strategies and fix suggestions.
101//!
102//! ```rust
103//! use decrust_core::{DecrustError, decrust::{Decrust, AutocorrectableError}, Backtrace};
104//!
105//! let mut decrust = Decrust::new();
106//!
107//! // Register custom fix generators
108//! // decrust.register_fix_generator(Box::new(CustomFixGenerator::new()));
109//!
110//! // Apply fixes automatically
111//! # let error = DecrustError::Validation {
112//! # field: "test".to_string(),
113//! # message: "test".to_string(),
114//! # expected: None,
115//! # actual: None,
116//! # rule: None,
117//! # backtrace: Backtrace::generate()
118//! # };
119//! if let Some(fix) = decrust.suggest_autocorrection(&error, None) {
120//! println!("Suggested fix: {}", fix.description);
121//! }
122//! ```
123//!
124//! ### 4. **Powerful Macros** 🛠️
125//! Ergonomic macros for common error handling patterns.
126//!
127//! ```rust
128//! use decrust_core::{oops, validation_error, error_context, location, types::ErrorSeverity};
129//!
130//! // Quick error creation
131//! # let source_error = std::io::Error::new(std::io::ErrorKind::Other, "test");
132//! let error = oops!("Something went wrong", source_error);
133//!
134//! // Validation errors with suggestions
135//! let validation_err = validation_error!(
136//! "email",
137//! "Invalid email format",
138//! suggestion: "Use format: user@domain.com"
139//! );
140//!
141//! // Rich context with location tracking
142//! let context = error_context!("Operation failed", severity: ErrorSeverity::Error);
143//! let loc = location!(context: "user authentication", function: "login");
144//! ```
145//!
146//! ### 5. **Comprehensive Error Types** 📋
147//! Pre-built error variants for common scenarios with rich metadata.
148//!
149//! ```rust
150//! use decrust_core::{DecrustError, Backtrace, OptionalError};
151//! use std::time::Duration;
152//!
153//! // Network errors with retry information
154//! let network_error = DecrustError::Network {
155//! source: Box::new(std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "refused")),
156//! kind: "HTTP".to_string(),
157//! url: Some("https://api.example.com".to_string()),
158//! backtrace: Backtrace::generate(),
159//! };
160//!
161//! // Configuration errors with suggestions
162//! let config_error = DecrustError::Config {
163//! message: "Invalid database URL format".to_string(),
164//! path: Some("config.toml".into()),
165//! source: OptionalError(None),
166//! backtrace: Backtrace::generate(),
167//! };
168//! ```
169//!
170//! ## 🔧 Advanced Usage Patterns
171//!
172//! ### Creating Custom Error Types
173//!
174//! You can create domain-specific error types that integrate seamlessly with Decrust:
175//!
176//! ```rust
177//! use decrust_core::{DecrustError, DecrustResultExt, types::ErrorSeverity, Backtrace, OptionalError};
178//!
179//! # struct User;
180//! // Define your domain-specific error
181//! #[derive(Debug)]
182//! pub enum UserServiceError {
183//! NotFound { id: String },
184//! InvalidEmail { email: String },
185//! PermissionDenied { user_id: String },
186//! }
187//!
188//! impl std::fmt::Display for UserServiceError {
189//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190//! match self {
191//! UserServiceError::NotFound { id } => write!(f, "User not found: {}", id),
192//! UserServiceError::InvalidEmail { email } => write!(f, "Invalid email format: {}", email),
193//! UserServiceError::PermissionDenied { user_id } => write!(f, "Permission denied for user: {}", user_id),
194//! }
195//! }
196//! }
197//!
198//! impl std::error::Error for UserServiceError {}
199//!
200//! // Convert to DecrustError with rich context
201//! impl From<UserServiceError> for DecrustError {
202//! fn from(err: UserServiceError) -> Self {
203//! match err {
204//! UserServiceError::NotFound { id } => DecrustError::NotFound {
205//! resource_type: "User".to_string(),
206//! identifier: id,
207//! backtrace: Backtrace::generate(),
208//! },
209//! UserServiceError::InvalidEmail { email } => DecrustError::Validation {
210//! field: "email".to_string(),
211//! message: format!("Invalid email format: {}", email),
212//! expected: None,
213//! actual: None,
214//! rule: None,
215//! backtrace: Backtrace::generate(),
216//! },
217//! UserServiceError::PermissionDenied { user_id } => {
218//! DecrustError::ExternalService {
219//! service_name: "UserService".to_string(),
220//! message: format!("Permission denied for user: {}", user_id),
221//! source: OptionalError(None),
222//! backtrace: Backtrace::generate(),
223//! }
224//! }
225//! }
226//! }
227//! }
228//!
229//! // Usage in your application
230//! fn get_user(id: &str) -> Result<User, DecrustError> {
231//! // Your business logic here
232//! if id.is_empty() {
233//! return Err(UserServiceError::NotFound { id: id.to_string() }.into());
234//! }
235//!
236//! // Add rich context to any errors
237//! database_call()
238//! .map_err(|e| DecrustError::Oops {
239//! message: "Database query failed".to_string(),
240//! source: Box::new(e),
241//! backtrace: Backtrace::generate(),
242//! })
243//! .decrust_context_msg("Fetching user from database")?;
244//!
245//! Ok(User)
246//! }
247//! # fn database_call() -> Result<(), std::io::Error> { Ok(()) }
248//! ```
249//!
250//! ### Error Reporting and Monitoring
251//!
252//! ```rust
253//! use decrust_core::{ErrorReporter, ErrorReportConfig, types::ErrorReportFormat, DecrustError, Backtrace};
254//!
255//! // Configure error reporting
256//! let config = ErrorReportConfig {
257//! format: ErrorReportFormat::Json,
258//! include_backtrace: true,
259//! include_rich_context: true,
260//! ..Default::default()
261//! };
262//!
263//! let reporter = ErrorReporter::new();
264//!
265//! // Report errors with rich context
266//! # let error = DecrustError::Validation {
267//! # field: "test".to_string(),
268//! # message: "test".to_string(),
269//! # expected: None,
270//! # actual: None,
271//! # rule: None,
272//! # backtrace: Backtrace::generate()
273//! # };
274//! let report = reporter.report_to_string(&error, &config);
275//! println!("Error Report: {}", report);
276//! ```
277//!
278//! ### Circuit Breaker with Custom Policies
279//!
280//! ```rust
281//! use decrust_core::{circuit_breaker::{CircuitBreaker, CircuitBreakerConfig}, DecrustError};
282//! use std::time::Duration;
283//!
284//! // Advanced circuit breaker configuration
285//! let config = CircuitBreakerConfig {
286//! failure_threshold: 3, // Open after 3 failures
287//! success_threshold_to_close: 2, // Close after 2 successes in half-open
288//! reset_timeout: Duration::from_secs(60), // Try half-open after 60 seconds
289//! operation_timeout: Some(Duration::from_secs(10)), // Individual operation timeout
290//! half_open_max_concurrent_operations: 1, // Only 1 operation in half-open
291//! ..Default::default()
292//! };
293//!
294//! let circuit_breaker = CircuitBreaker::new("payment-service", config);
295//!
296//! // Use with async operations (when std-thread feature is enabled)
297//! let result = circuit_breaker.execute(|| {
298//! // Your potentially failing operation
299//! call_payment_service()
300//! });
301//!
302//! match result {
303//! Ok(response) => println!("Payment successful: {:?}", response),
304//! Err(DecrustError::CircuitBreakerOpen { retry_after, .. }) => {
305//! println!("Circuit breaker is open, retry after: {:?}", retry_after);
306//! }
307//! Err(e) => println!("Payment failed: {}", e),
308//! }
309//! # fn call_payment_service() -> Result<String, DecrustError> { Ok("success".to_string()) }
310//! ```
311//!
312//! ### Object-Safe Extension Trait Usage
313//!
314//! The extension traits are object-safe and support dynamic dispatch:
315//!
316//! ```rust
317//! use decrust_core::{DecrustResultExt, DecrustOptionExt, DecrustError};
318//!
319//! // Object-safe trait usage with dynamic dispatch
320//! fn process_with_dyn_traits(
321//! result: &dyn DecrustResultExt<String, std::io::Error>,
322//! option: &dyn DecrustOptionExt<i32>
323//! ) {
324//! // These work because the traits are object-safe
325//! }
326//!
327//! // Regular usage for better error handling
328//! fn process_data() -> Result<String, DecrustError> {
329//! let result: Result<String, std::io::Error> = Ok("test".to_string());
330//! let option: Option<i32> = Some(42);
331//!
332//! // Add context to results (object-safe methods)
333//! let processed = result.decrust_context_msg("Processing data")?;
334//!
335//! // Convert options to results with meaningful errors (object-safe methods)
336//! let value = option.decrust_ok_or_missing_value("required value")?;
337//!
338//! Ok(format!("{} - {}", processed, value))
339//! }
340//! ```
341//!
342//! ## 📚 Feature Flags
343//!
344//! - `std-thread`: Enables threading support for circuit breaker timeouts
345//! - `serde`: Enables serialization support for error types
346//! - `tracing`: Enables integration with the tracing ecosystem
347//!
348//! ## 🎨 Best Practices
349//!
350//! 1. **Use specific error variants** for different error categories
351//! 2. **Add rich context** with `decrust_context_msg()` for better debugging
352//! 3. **Implement circuit breakers** for external service calls
353//! 4. **Use macros** for common error patterns to reduce boilerplate
354//! 5. **Configure error reporting** for production monitoring
355//! 6. **Create domain-specific error types** that convert to `DecrustError`
356//!
357//! ## 🔗 Integration Examples
358//!
359//! ### With Tokio and Async
360//! ```rust
361//! use decrust_core::{DecrustError, DecrustResultExt, Backtrace};
362//! use std::path::PathBuf;
363//!
364//! // Simulate async file reading without requiring tokio dependency
365//! async fn read_config_async() -> Result<String, DecrustError> {
366//! // Simulate reading a config file
367//! let result = std::fs::read_to_string("Cargo.toml") // Use existing file
368//! .map_err(|e| DecrustError::Io {
369//! source: e,
370//! path: Some("Cargo.toml".into()),
371//! operation: "read config file".to_string(),
372//! backtrace: Backtrace::generate(),
373//! })
374//! .decrust_context_msg("Loading application configuration")?;
375//!
376//! Ok(result)
377//! }
378//!
379//! // Test the async function (without actually running it)
380//! let _future = read_config_async();
381//! ```
382//!
383//! ### With Configuration Parsing
384//! ```rust
385//! use decrust_core::{DecrustError, Backtrace, OptionalError};
386//! use std::path::PathBuf;
387//!
388//! // Simple configuration struct (without serde dependency)
389//! struct AppConfig {
390//! database_url: String,
391//! api_key: String,
392//! }
393//!
394//! fn load_config() -> Result<AppConfig, DecrustError> {
395//! // Simulate reading configuration from Cargo.toml (which exists)
396//! let config_str = std::fs::read_to_string("Cargo.toml")
397//! .map_err(|e| DecrustError::Config {
398//! message: "Failed to read configuration file".to_string(),
399//! path: Some("Cargo.toml".into()),
400//! source: OptionalError::new(Some(Box::new(e))),
401//! backtrace: Backtrace::generate(),
402//! })?;
403//!
404//! // Simulate parsing (just create a dummy config)
405//! let config = AppConfig {
406//! database_url: "postgresql://localhost/mydb".to_string(),
407//! api_key: "dummy_key".to_string(),
408//! };
409//!
410//! // Validate configuration
411//! if config.database_url.is_empty() {
412//! return Err(DecrustError::Validation {
413//! field: "database_url".to_string(),
414//! message: "Database URL cannot be empty".to_string(),
415//! expected: None,
416//! actual: None,
417//! rule: None,
418//! backtrace: Backtrace::generate(),
419//! });
420//! }
421//!
422//! Ok(config)
423//! }
424//!
425//! // Test the function
426//! let _config = load_config();
427//! ```
428// ~=####====A===r===c===M===o===o===n====S===t===u===d===i===o===s====X|0|$>
429// **GitHub:** [ArcMoon Studios](https://github.com/arcmoonstudios)
430// **Copyright:** (c) 2025 ArcMoon Studios
431// **Author:** Lord Xyn
432// **License:** Business Source License 1.1 (BSL-1.1)
433// **License File:** /LICENSE
434// **License Terms:** Non-production use only; commercial/production use requires a paid license.
435// **Change Date:** 2029-05-25 | **Change License:** GPL v3
436// **Contact:** LordXyn@proton.me
437
438pub mod backtrace;
439pub mod circuit_breaker;
440pub mod decrust;
441pub mod reporter;
442pub mod syntax;
443pub mod types;
444
445use std::path::PathBuf;
446use std::time::Duration;
447
448pub use self::backtrace::{
449 AsBacktrace,
450 BacktraceCompat,
451 BacktraceFrame,
452 // FromString, // Will add back if `oops!` macro or FromString trait is used directly
453 // ensure, // Will add back if used
454 BacktraceProvider,
455 BacktraceStatus,
456 DecrustBacktrace as Backtrace, // For Backtrace::generate()
457 GenerateImplicitData,
458 Location,
459 ThreadId,
460 Timestamp,
461};
462
463// Macros are automatically exported at crate root due to #[macro_export]
464// Available macros: implicit_data!, location!, error_context!, oops!, validation_error!
465
466pub use self::circuit_breaker::{
467 CircuitBreaker, CircuitBreakerConfig, CircuitBreakerObserver, CircuitBreakerState,
468 CircuitMetrics, CircuitOperationType, CircuitTransitionEvent,
469};
470
471pub use self::decrust::{
472 AstMissingImportFixGenerator, AstUnusedCodeFixGenerator, AutocorrectableError,
473 ClosureCaptureLifetimeFixGenerator, ConfigMissingKeyFixGenerator, ConfigSyntaxFixGenerator,
474 CrateUsageAnalysis, Decrust, DependencyAnalysisResult, DependencyAnalyzer,
475 DivisionByZeroFixGenerator, InteractiveRecommendation, InvalidArgumentCountFixGenerator,
476 IoMissingDirectoryFixGenerator, IoPermissionFixGenerator, JsonParseFixGenerator,
477 MissingOkErrFixGenerator, NetworkConnectionFixGenerator, NetworkTlsFixGenerator,
478 OptimizationImpact, QuestionMarkPropagationFixGenerator, RecommendationType,
479 RecursiveTypeFixGenerator, ReturnLocalReferenceFixGenerator, RuntimePanicFixGenerator,
480 SecurityImpact, UnnecessaryCloneFixGenerator, UnnecessaryParenthesesFixGenerator,
481 UnsafeUnwrapFixGenerator, UnstableFeatureFixGenerator, UnusedMutFixGenerator,
482 VersionCompatibility, YamlParseFixGenerator,
483};
484
485pub use self::reporter::{ErrorReportConfig, ErrorReporter};
486
487pub use self::syntax::{FixTemplate, SyntaxGenerator, TemplateRegistry};
488
489pub use self::types::{
490 Autocorrection, ErrorCategory, ErrorContext, ErrorReportFormat, ErrorSeverity, ErrorSource,
491 ExtractedParameters, FixDetails, FixType, ParameterExtractor, ParameterSource,
492};
493
494/// A Result type specialized for DecrustError
495pub type Result<T, E = DecrustError> = std::result::Result<T, E>;
496
497// Re-export key types from submodules
498/// A Result type specialized for diagnostic operations that can return multiple errors
499pub type DiagnosticResult<T> = std::result::Result<T, Vec<DecrustError>>;
500
501/// Wrapper for `Option<Box<dyn Error>>` to make it compatible with backtrace
502///
503/// This struct provides a way to handle optional error sources in a way that's
504/// compatible with the backtrace error handling framework. It wraps an optional boxed
505/// error trait object and provides methods to work with it.
506#[derive(Debug)]
507pub struct OptionalError(pub Option<Box<dyn std::error::Error + Send + Sync + 'static>>);
508
509impl Clone for OptionalError {
510 fn clone(&self) -> Self {
511 match &self.0 {
512 Some(err) => {
513 // Create a new error with the string representation of the original error
514 let cloned_err = std::io::Error::other(format!("{}", err));
515 OptionalError(Some(Box::new(cloned_err)))
516 }
517 None => OptionalError(None),
518 }
519 }
520}
521
522impl std::fmt::Display for OptionalError {
523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
524 match &self.0 {
525 Some(err) => write!(f, "{}", err),
526 None => write!(f, "No error"),
527 }
528 }
529}
530
531impl std::error::Error for OptionalError {
532 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
533 match &self.0 {
534 Some(err) => Some(err.as_ref()),
535 None => None,
536 }
537 }
538}
539
540// Implement BacktraceCompat for DecrustError to make backtrace() work
541// Implement std::error::Error for DecrustError
542impl std::error::Error for DecrustError {
543 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
544 match self {
545 DecrustError::Io { source, .. } => Some(source),
546 DecrustError::WithRichContext { source, .. } => Some(source.as_ref()),
547 DecrustError::Oops { source, .. } => Some(source.as_ref()),
548 DecrustError::Parse { source, .. } => Some(source.as_ref()),
549 DecrustError::Network { source, .. } => Some(source.as_ref()),
550 DecrustError::Config { source, .. } => source
551 .0
552 .as_ref()
553 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
554 DecrustError::Internal { source, .. } => source
555 .0
556 .as_ref()
557 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
558 DecrustError::Concurrency { source, .. } => source
559 .0
560 .as_ref()
561 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
562 DecrustError::ExternalService { source, .. } => source
563 .0
564 .as_ref()
565 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)),
566 DecrustError::MultipleErrors { errors, .. } => errors
567 .first()
568 .map(|e| e as &(dyn std::error::Error + 'static)),
569 DecrustError::CircuitBreakerOpen { .. } => None,
570 DecrustError::ResourceExhausted { .. } => None,
571 DecrustError::StateConflict { .. } => None,
572 DecrustError::MissingValue { .. } => None,
573 DecrustError::Validation { .. } => None,
574 DecrustError::NotFound { .. } => None,
575 DecrustError::Timeout { .. } => None,
576 DecrustError::Style { .. } => None,
577 }
578 }
579}
580
581// Implement PartialEq for DecrustError to support testing
582impl PartialEq for DecrustError {
583 fn eq(&self, other: &Self) -> bool {
584 match (self, other) {
585 (DecrustError::Parse { kind: k1, .. }, DecrustError::Parse { kind: k2, .. }) => {
586 k1 == k2
587 }
588 (DecrustError::Oops { message: m1, .. }, DecrustError::Oops { message: m2, .. }) => {
589 m1 == m2
590 }
591 (DecrustError::Network { kind: k1, .. }, DecrustError::Network { kind: k2, .. }) => {
592 k1 == k2
593 }
594 (DecrustError::Style { message: m1, .. }, DecrustError::Style { message: m2, .. }) => {
595 m1 == m2
596 }
597 (
598 DecrustError::Config { message: m1, .. },
599 DecrustError::Config { message: m2, .. },
600 ) => m1 == m2,
601 (DecrustError::Io { operation: op1, .. }, DecrustError::Io { operation: op2, .. }) => {
602 op1 == op2
603 }
604 (
605 DecrustError::Internal { message: m1, .. },
606 DecrustError::Internal { message: m2, .. },
607 ) => m1 == m2,
608 (
609 DecrustError::Concurrency { message: m1, .. },
610 DecrustError::Concurrency { message: m2, .. },
611 ) => m1 == m2,
612 (
613 DecrustError::Timeout { operation: op1, .. },
614 DecrustError::Timeout { operation: op2, .. },
615 ) => op1 == op2,
616 (
617 DecrustError::StateConflict { message: m1, .. },
618 DecrustError::StateConflict { message: m2, .. },
619 ) => m1 == m2,
620 (
621 DecrustError::CircuitBreakerOpen { name: n1, .. },
622 DecrustError::CircuitBreakerOpen { name: n2, .. },
623 ) => n1 == n2,
624 (
625 DecrustError::ResourceExhausted { resource: r1, .. },
626 DecrustError::ResourceExhausted { resource: r2, .. },
627 ) => r1 == r2,
628 (
629 DecrustError::ExternalService {
630 service_name: s1, ..
631 },
632 DecrustError::ExternalService {
633 service_name: s2, ..
634 },
635 ) => s1 == s2,
636 (
637 DecrustError::MissingValue {
638 item_description: i1,
639 ..
640 },
641 DecrustError::MissingValue {
642 item_description: i2,
643 ..
644 },
645 ) => i1 == i2,
646 (
647 DecrustError::MultipleErrors { errors: e1, .. },
648 DecrustError::MultipleErrors { errors: e2, .. },
649 ) => e1 == e2,
650 (
651 DecrustError::Validation {
652 field: f1,
653 message: m1,
654 ..
655 },
656 DecrustError::Validation {
657 field: f2,
658 message: m2,
659 ..
660 },
661 ) => f1 == f2 && m1 == m2,
662 (
663 DecrustError::NotFound {
664 resource_type: r1,
665 identifier: i1,
666 ..
667 },
668 DecrustError::NotFound {
669 resource_type: r2,
670 identifier: i2,
671 ..
672 },
673 ) => r1 == r2 && i1 == i2,
674 (
675 DecrustError::WithRichContext {
676 context: c1,
677 source: s1,
678 },
679 DecrustError::WithRichContext {
680 context: c2,
681 source: s2,
682 },
683 ) => c1.message == c2.message && s1 == s2,
684 _ => false,
685 }
686 }
687}
688
689impl backtrace::BacktraceCompat for DecrustError {
690 fn backtrace(&self) -> Option<&backtrace::DecrustBacktrace> {
691 match self {
692 DecrustError::Io { backtrace, .. } => Some(backtrace),
693 DecrustError::Oops { backtrace, .. } => Some(backtrace),
694 DecrustError::Style { backtrace, .. } => Some(backtrace),
695 DecrustError::Parse { backtrace, .. } => Some(backtrace),
696 DecrustError::Config { backtrace, .. } => Some(backtrace),
697 DecrustError::Timeout { backtrace, .. } => Some(backtrace),
698 DecrustError::Network { backtrace, .. } => Some(backtrace),
699 DecrustError::NotFound { backtrace, .. } => Some(backtrace),
700 DecrustError::Internal { backtrace, .. } => Some(backtrace),
701 DecrustError::Validation { backtrace, .. } => Some(backtrace),
702 DecrustError::Concurrency { backtrace, .. } => Some(backtrace),
703 DecrustError::MissingValue { backtrace, .. } => Some(backtrace),
704 DecrustError::StateConflict { backtrace, .. } => Some(backtrace),
705 DecrustError::MultipleErrors { backtrace, .. } => Some(backtrace),
706 DecrustError::ExternalService { backtrace, .. } => Some(backtrace),
707 DecrustError::ResourceExhausted { backtrace, .. } => Some(backtrace),
708 DecrustError::CircuitBreakerOpen { backtrace, .. } => Some(backtrace),
709 DecrustError::WithRichContext { source, .. } => source.backtrace(),
710 }
711 }
712}
713
714impl OptionalError {
715 /// Creates a new OptionalError from an optional boxed error
716 ///
717 /// # Parameters
718 /// * `opt` - An optional boxed error trait object
719 pub fn new(opt: Option<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
720 OptionalError(opt)
721 }
722
723 /// Checks if this OptionalError contains an actual error
724 ///
725 /// # Returns
726 /// `true` if there is an error, `false` otherwise
727 pub fn has_error(&self) -> bool {
728 self.0.is_some()
729 }
730}
731
732impl From<Option<Box<dyn std::error::Error + Send + Sync + 'static>>> for OptionalError {
733 fn from(opt: Option<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
734 OptionalError(opt)
735 }
736}
737
738impl AsRef<Option<Box<dyn std::error::Error + Send + Sync + 'static>>> for OptionalError {
739 fn as_ref(&self) -> &Option<Box<dyn std::error::Error + Send + Sync + 'static>> {
740 &self.0
741 }
742}
743
744/// Unified error type for Decrust.
745#[derive(Debug)]
746#[allow(clippy::result_large_err)]
747pub enum DecrustError {
748 /// I/O related errors
749 Io {
750 /// The underlying I/O error
751 source: std::io::Error,
752 /// Optional path to the file or resource that caused the error
753 path: Option<PathBuf>,
754 /// Description of the operation that failed
755 operation: String,
756 /// Backtrace captured at the error site
757 backtrace: Backtrace,
758 },
759
760 /// Parsing errors (JSON, YAML, etc.)
761 Parse {
762 /// The underlying parsing error
763 source: Box<dyn std::error::Error + Send + Sync + 'static>,
764 /// The type of data being parsed (e.g., "JSON", "YAML")
765 kind: String,
766 /// Additional context information about the parsing operation
767 context_info: String,
768 /// Backtrace captured at the error site
769 backtrace: Backtrace,
770 },
771
772 /// Network related errors
773 Network {
774 /// The underlying network error
775 source: Box<dyn std::error::Error + Send + Sync + 'static>,
776 /// Optional URL that was being accessed
777 url: Option<String>,
778 /// The type of network operation (e.g., "HTTP", "TCP")
779 kind: String,
780 /// Backtrace captured at the error site
781 backtrace: Backtrace,
782 },
783
784 /// Configuration related errors
785 Config {
786 /// Error message describing the configuration issue
787 message: String,
788 /// Optional path to the configuration file
789 path: Option<PathBuf>,
790 /// Optional underlying error that caused the configuration issue
791 source: OptionalError,
792 /// Backtrace captured at the error site
793 backtrace: Backtrace,
794 },
795
796 /// Validation errors
797 Validation {
798 /// Name of the field that failed validation
799 field: String,
800 /// Description of the validation error
801 message: String,
802 /// Expected value or format (for backwards compatibility, optional)
803 #[doc(hidden)]
804 expected: Option<String>,
805 /// Actual value that was provided (for backwards compatibility, optional)
806 #[doc(hidden)]
807 actual: Option<String>,
808 /// Validation rule that was violated (for backwards compatibility, optional)
809 #[doc(hidden)]
810 rule: Option<String>,
811 /// Backtrace captured at the error site
812 backtrace: Backtrace,
813 },
814
815 /// Internal errors
816 Internal {
817 /// Description of the internal error
818 message: String,
819 /// Optional underlying error
820 source: OptionalError,
821 /// Component that generated the error (for backwards compatibility, optional)
822 #[doc(hidden)]
823 component: Option<String>,
824 /// Backtrace captured at the error site
825 backtrace: Backtrace,
826 },
827
828 /// Circuit breaker is open
829 CircuitBreakerOpen {
830 /// Name of the circuit breaker
831 name: String,
832 /// Optional duration after which the circuit breaker might transition to half-open
833 retry_after: Option<Duration>,
834 /// Number of consecutive failures that caused the circuit to open (for backwards compatibility, optional)
835 #[doc(hidden)]
836 failure_count: Option<u32>,
837 /// Last error message that contributed to opening the circuit (for backwards compatibility, optional)
838 #[doc(hidden)]
839 last_error: Option<String>,
840 /// Backtrace captured at the error site
841 backtrace: Backtrace,
842 },
843
844 /// Operation timed out
845 Timeout {
846 /// Name of the operation that timed out
847 operation: String,
848 /// Duration after which the operation timed out
849 duration: Duration,
850 /// Backtrace captured at the error site
851 backtrace: Backtrace,
852 },
853
854 /// Resource exhaustion
855 ResourceExhausted {
856 /// Name of the resource that was exhausted
857 resource: String,
858 /// The limit of the resource
859 limit: String,
860 /// The current value that exceeded the limit
861 current: String,
862 /// Backtrace captured at the error site
863 backtrace: Backtrace,
864 },
865
866 /// Resource not found
867 NotFound {
868 /// Type of resource that was not found (e.g., "User", "File")
869 resource_type: String,
870 /// Identifier of the resource that was not found
871 identifier: String,
872 /// Backtrace captured at the error site
873 backtrace: Backtrace,
874 },
875
876 /// State conflict
877 StateConflict {
878 /// Description of the state conflict
879 message: String,
880 /// Backtrace captured at the error site
881 backtrace: Backtrace,
882 },
883
884 /// Concurrency related errors
885 Concurrency {
886 /// Description of the concurrency error
887 message: String,
888 /// Optional underlying error
889 source: OptionalError,
890 /// Backtrace captured at the error site
891 backtrace: Backtrace,
892 },
893
894 /// External service errors
895 ExternalService {
896 /// Name of the external service that caused the error
897 service_name: String,
898 /// Description of the error from the external service
899 message: String,
900 /// Optional underlying error from the external service
901 source: OptionalError,
902 /// Backtrace captured at the error site
903 backtrace: Backtrace,
904 },
905
906 /// Missing value errors
907 MissingValue {
908 /// Description of the missing value or item
909 item_description: String,
910 /// Backtrace captured at the error site
911 backtrace: Backtrace,
912 },
913
914 /// Multiple errors
915 MultipleErrors {
916 /// Collection of errors that occurred
917 errors: Vec<DecrustError>,
918 /// Backtrace captured at the error site
919 backtrace: Backtrace,
920 },
921
922 /// Error with rich context
923 WithRichContext {
924 /// Rich context information attached to the error
925 context: types::ErrorContext,
926 /// The original error that is being wrapped with context
927 source: Box<DecrustError>,
928 },
929
930 /// Style and formatting errors
931 Style {
932 /// The style error message
933 message: String,
934 /// Backtrace captured at the error site
935 backtrace: Backtrace,
936 },
937
938 /// General purpose error wrapper
939 Oops {
940 /// Custom error message
941 message: String,
942 /// The underlying error being wrapped
943 source: Box<dyn std::error::Error + Send + Sync + 'static>,
944 /// Backtrace captured at the error site
945 backtrace: Backtrace,
946 },
947}
948
949impl Clone for DecrustError {
950 fn clone(&self) -> Self {
951 match self {
952 Self::Io {
953 source,
954 path,
955 operation,
956 ..
957 } => Self::Io {
958 source: std::io::Error::new(source.kind(), format!("{}", source)),
959 path: path.clone(),
960 operation: operation.clone(),
961 backtrace: Backtrace::generate(),
962 },
963 Self::Parse {
964 source,
965 kind,
966 context_info,
967 ..
968 } => Self::Parse {
969 source: Box::new(std::io::Error::new(
970 std::io::ErrorKind::InvalidData,
971 format!("{}", source),
972 )),
973 kind: kind.clone(),
974 context_info: context_info.clone(),
975 backtrace: Backtrace::generate(),
976 },
977 Self::Network {
978 source, url, kind, ..
979 } => Self::Network {
980 source: Box::new(std::io::Error::other(format!("{}", source))),
981 url: url.clone(),
982 kind: kind.clone(),
983 backtrace: Backtrace::generate(),
984 },
985 Self::Config {
986 message,
987 path,
988 source,
989 ..
990 } => Self::Config {
991 message: message.clone(),
992 path: path.clone(),
993 source: source.clone(),
994 backtrace: Backtrace::generate(),
995 },
996 Self::Validation {
997 field,
998 message,
999 expected,
1000 actual,
1001 rule,
1002 ..
1003 } => Self::Validation {
1004 field: field.clone(),
1005 message: message.clone(),
1006 expected: expected.clone(),
1007 actual: actual.clone(),
1008 rule: rule.clone(),
1009 backtrace: Backtrace::generate(),
1010 },
1011 Self::Internal {
1012 message,
1013 source,
1014 component,
1015 ..
1016 } => Self::Internal {
1017 message: message.clone(),
1018 source: source.clone(),
1019 component: component.clone(),
1020 backtrace: Backtrace::generate(),
1021 },
1022 Self::CircuitBreakerOpen {
1023 name,
1024 retry_after,
1025 failure_count,
1026 last_error,
1027 ..
1028 } => Self::CircuitBreakerOpen {
1029 name: name.clone(),
1030 retry_after: *retry_after,
1031 failure_count: *failure_count,
1032 last_error: last_error.clone(),
1033 backtrace: Backtrace::generate(),
1034 },
1035 Self::Timeout {
1036 operation,
1037 duration,
1038 ..
1039 } => Self::Timeout {
1040 operation: operation.clone(),
1041 duration: *duration,
1042 backtrace: Backtrace::generate(),
1043 },
1044 Self::ResourceExhausted {
1045 resource,
1046 limit,
1047 current,
1048 ..
1049 } => Self::ResourceExhausted {
1050 resource: resource.clone(),
1051 limit: limit.clone(),
1052 current: current.clone(),
1053 backtrace: Backtrace::generate(),
1054 },
1055 Self::NotFound {
1056 resource_type,
1057 identifier,
1058 ..
1059 } => Self::NotFound {
1060 resource_type: resource_type.clone(),
1061 identifier: identifier.clone(),
1062 backtrace: Backtrace::generate(),
1063 },
1064 Self::StateConflict { message, .. } => Self::StateConflict {
1065 message: message.clone(),
1066 backtrace: Backtrace::generate(),
1067 },
1068 Self::Concurrency {
1069 message, source, ..
1070 } => Self::Concurrency {
1071 message: message.clone(),
1072 source: source.clone(),
1073 backtrace: Backtrace::generate(),
1074 },
1075 Self::ExternalService {
1076 service_name,
1077 message,
1078 source,
1079 ..
1080 } => Self::ExternalService {
1081 service_name: service_name.clone(),
1082 message: message.clone(),
1083 source: source.clone(),
1084 backtrace: Backtrace::generate(),
1085 },
1086 Self::MissingValue {
1087 item_description, ..
1088 } => Self::MissingValue {
1089 item_description: item_description.clone(),
1090 backtrace: Backtrace::generate(),
1091 },
1092 Self::MultipleErrors { errors, .. } => Self::MultipleErrors {
1093 errors: errors.clone(),
1094 backtrace: Backtrace::generate(),
1095 },
1096 Self::WithRichContext { context, source } => {
1097 // Explicitly list fields, no 'backtrace' field here
1098 Self::WithRichContext {
1099 context: context.clone(),
1100 source: Box::new((**source).clone()),
1101 }
1102 }
1103 Self::Style { message, .. } => Self::Style {
1104 message: message.clone(),
1105 backtrace: Backtrace::generate(),
1106 },
1107 Self::Oops {
1108 message, source, ..
1109 } => Self::Oops {
1110 message: message.clone(),
1111 source: Box::new(std::io::Error::other(format!("{}", source))),
1112 backtrace: Backtrace::generate(),
1113 },
1114 }
1115 }
1116}
1117
1118impl std::fmt::Display for DecrustError {
1119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1120 match self {
1121 DecrustError::Io {
1122 source,
1123 path,
1124 operation,
1125 ..
1126 } => {
1127 write!(
1128 f,
1129 "I/O error during operation '{}' on path '{}': {}",
1130 operation,
1131 path.as_ref()
1132 .map(|p| p.to_string_lossy().to_string())
1133 .unwrap_or_else(|| "N/A".to_string()),
1134 source
1135 )
1136 }
1137 DecrustError::Parse {
1138 source,
1139 kind,
1140 context_info,
1141 ..
1142 } => {
1143 write!(f, "{} parsing error: {} ({})", kind, source, context_info)
1144 }
1145 DecrustError::Network {
1146 source, url, kind, ..
1147 } => {
1148 write!(
1149 f,
1150 "{} network error: {} (URL: {})",
1151 kind,
1152 source,
1153 url.as_deref().unwrap_or("N/A")
1154 )
1155 }
1156 DecrustError::Config {
1157 message,
1158 path,
1159 source,
1160 ..
1161 } => {
1162 if let Some(p) = path {
1163 if let Some(s) = &source.0 {
1164 write!(
1165 f,
1166 "Configuration error in '{}': {} ({})",
1167 p.display(),
1168 message,
1169 s
1170 )
1171 } else {
1172 write!(f, "Configuration error in '{}': {}", p.display(), message)
1173 }
1174 } else if let Some(s) = &source.0 {
1175 write!(f, "Configuration error: {} ({})", message, s)
1176 } else {
1177 write!(f, "Configuration error: {}", message)
1178 }
1179 }
1180 DecrustError::Validation { field, message, .. } => {
1181 write!(f, "Validation error for '{}': {}", field, message)
1182 }
1183 DecrustError::Internal {
1184 message, source, ..
1185 } => {
1186 if let Some(s) = &source.0 {
1187 write!(f, "Internal error: {} ({})", message, s)
1188 } else {
1189 write!(f, "Internal error: {}", message)
1190 }
1191 }
1192 DecrustError::CircuitBreakerOpen {
1193 name, retry_after, ..
1194 } => {
1195 if let Some(duration) = retry_after {
1196 write!(
1197 f,
1198 "Circuit breaker '{}' is open. Retry after {:?}",
1199 name, duration
1200 )
1201 } else {
1202 write!(f, "Circuit breaker '{}' is open", name)
1203 }
1204 }
1205 DecrustError::Timeout {
1206 operation,
1207 duration,
1208 ..
1209 } => {
1210 write!(
1211 f,
1212 "Operation '{}' timed out after {:?}",
1213 operation, duration
1214 )
1215 }
1216 DecrustError::ResourceExhausted {
1217 resource,
1218 limit,
1219 current,
1220 ..
1221 } => {
1222 write!(
1223 f,
1224 "Resource '{}' exhausted: {} (limit: {})",
1225 resource, current, limit
1226 )
1227 }
1228 DecrustError::NotFound {
1229 resource_type,
1230 identifier,
1231 ..
1232 } => {
1233 write!(f, "{} not found: {}", resource_type, identifier)
1234 }
1235 DecrustError::StateConflict { message, .. } => {
1236 write!(f, "State conflict: {}", message)
1237 }
1238 DecrustError::Concurrency {
1239 message, source, ..
1240 } => {
1241 if let Some(s) = &source.0 {
1242 write!(f, "Concurrency error: {} ({})", message, s)
1243 } else {
1244 write!(f, "Concurrency error: {}", message)
1245 }
1246 }
1247 DecrustError::ExternalService {
1248 service_name,
1249 message,
1250 source,
1251 ..
1252 } => {
1253 if let Some(s) = &source.0 {
1254 write!(
1255 f,
1256 "External service '{}' error: {} ({})",
1257 service_name, message, s
1258 )
1259 } else {
1260 write!(f, "External service '{}' error: {}", service_name, message)
1261 }
1262 }
1263 DecrustError::MissingValue {
1264 item_description, ..
1265 } => {
1266 write!(f, "Missing value: {}", item_description)
1267 }
1268 DecrustError::MultipleErrors { errors, .. } => {
1269 write!(f, "Multiple errors ({} total):", errors.len())?;
1270 for (i, err) in errors.iter().enumerate() {
1271 write!(f, "\n {}. {}", i + 1, err)?;
1272 }
1273 Ok(())
1274 }
1275 DecrustError::WithRichContext {
1276 context, source, ..
1277 } => {
1278 write!(f, "{}: {}", context.message, source)
1279 }
1280 DecrustError::Style { message, .. } => {
1281 write!(f, "Style issue: {}", message)
1282 }
1283 DecrustError::Oops {
1284 message, source, ..
1285 } => {
1286 write!(f, "{}: {}", message, source)
1287 }
1288 }
1289 }
1290}
1291
1292impl DecrustError {
1293 /// Adds rich context to an error
1294 ///
1295 /// This wraps the error in a WithRichContext variant, which allows for additional
1296 /// information to be attached to the error.
1297 ///
1298 /// # Parameters
1299 /// * `context` - The error context to add
1300 ///
1301 /// # Returns
1302 /// A new error with the context attached
1303 pub fn add_context(self, context: types::ErrorContext) -> Self {
1304 // Create the WithRichContext variant directly
1305 DecrustError::WithRichContext {
1306 context,
1307 source: Box::new(self),
1308 }
1309 }
1310
1311 /// Adds a simple message context to an error
1312 ///
1313 /// This is a convenience method that creates a simple ErrorContext with just a message
1314 /// and adds it to the error.
1315 ///
1316 /// # Parameters
1317 /// * `message` - The message to add as context
1318 ///
1319 /// # Returns
1320 /// A new error with the context attached
1321 pub fn add_context_msg(self, message: impl Into<String>) -> Self {
1322 let error_context = types::ErrorContext::new(message);
1323 self.add_context(error_context)
1324 }
1325
1326 /// Gets the category of this error
1327 ///
1328 /// # Returns
1329 /// The ErrorCategory that best describes this error
1330 pub fn category(&self) -> types::ErrorCategory {
1331 match self {
1332 DecrustError::Io { .. } => types::ErrorCategory::Io,
1333 DecrustError::Parse { .. } => types::ErrorCategory::Parsing,
1334 DecrustError::Network { .. } => types::ErrorCategory::Network,
1335 DecrustError::Config { .. } => types::ErrorCategory::Configuration,
1336 DecrustError::Validation { .. } => types::ErrorCategory::Validation,
1337 DecrustError::Internal { .. } => types::ErrorCategory::Internal,
1338 DecrustError::CircuitBreakerOpen { .. } => types::ErrorCategory::CircuitBreaker,
1339 DecrustError::Timeout { .. } => types::ErrorCategory::Timeout,
1340 DecrustError::ResourceExhausted { .. } => types::ErrorCategory::ResourceExhaustion,
1341 DecrustError::NotFound { .. } => types::ErrorCategory::NotFound,
1342 DecrustError::StateConflict { .. } => types::ErrorCategory::StateConflict,
1343 DecrustError::Concurrency { .. } => types::ErrorCategory::Concurrency,
1344 DecrustError::ExternalService { .. } => types::ErrorCategory::ExternalService,
1345 DecrustError::MultipleErrors { .. } => types::ErrorCategory::Multiple,
1346 DecrustError::WithRichContext { source, .. } => source.category(),
1347 DecrustError::Style { .. } => types::ErrorCategory::Style,
1348 DecrustError::Oops { .. } => types::ErrorCategory::Unspecified,
1349 DecrustError::MissingValue { .. } => types::ErrorCategory::Validation,
1350 }
1351 }
1352
1353 /// Gets the severity of this error
1354 ///
1355 /// # Returns
1356 /// The ErrorSeverity level of this error
1357 pub fn severity(&self) -> types::ErrorSeverity {
1358 if let DecrustError::WithRichContext { context, .. } = self {
1359 context.severity
1360 } else {
1361 types::ErrorSeverity::Error
1362 }
1363 }
1364
1365 /// Gets the rich context attached to this error, if any
1366 ///
1367 /// # Returns
1368 /// Some(context) if this is a WithRichContext error, None otherwise
1369 pub fn get_rich_context(&self) -> Option<&types::ErrorContext> {
1370 match self {
1371 DecrustError::WithRichContext { context, .. } => Some(context),
1372 _ => None,
1373 }
1374 }
1375}
1376
1377/// Extension trait for Result types to add context to errors
1378///
1379/// This trait provides methods to add context to errors in a Result,
1380/// making it easier to provide additional information about the error.
1381///
1382/// This trait is object-safe and can be used with dynamic dispatch.
1383pub trait DecrustResultExt<T, EOrig> {
1384 /// Adds a simple message context to an error in a Result
1385 ///
1386 /// # Parameters
1387 /// * `message` - The message to add as context
1388 ///
1389 /// # Returns
1390 /// A new Result with the error wrapped in a WithRichContext variant if it was an error
1391 fn decrust_context_msg(self, message: &str) -> Result<T, DecrustError>;
1392
1393 /// Adds a simple message context to an error in a Result (owned string version)
1394 ///
1395 /// # Parameters
1396 /// * `message` - The message to add as context
1397 ///
1398 /// # Returns
1399 /// A new Result with the error wrapped in a WithRichContext variant if it was an error
1400 fn decrust_context_msg_owned(self, message: String) -> Result<T, DecrustError>;
1401
1402 /// Adds rich context to an error in a Result
1403 ///
1404 /// # Parameters
1405 /// * `context` - The error context to add
1406 ///
1407 /// # Returns
1408 /// A new Result with the error wrapped in a WithRichContext variant if it was an error
1409 fn decrust_context_rich(self, context: types::ErrorContext) -> Result<T, DecrustError>;
1410}
1411
1412impl<T, E> DecrustResultExt<T, E> for std::result::Result<T, E>
1413where
1414 E: Into<DecrustError>,
1415{
1416 #[track_caller]
1417 fn decrust_context_msg(self, message: &str) -> Result<T, DecrustError> {
1418 match self {
1419 Ok(value) => Ok(value),
1420 Err(err) => {
1421 let decrust_err: DecrustError = err.into();
1422 Err(DecrustError::WithRichContext {
1423 context: types::ErrorContext::new(message),
1424 source: Box::new(decrust_err),
1425 })
1426 }
1427 }
1428 }
1429
1430 #[track_caller]
1431 fn decrust_context_msg_owned(self, message: String) -> Result<T, DecrustError> {
1432 match self {
1433 Ok(value) => Ok(value),
1434 Err(err) => {
1435 let decrust_err: DecrustError = err.into();
1436 Err(DecrustError::WithRichContext {
1437 context: types::ErrorContext::new(message),
1438 source: Box::new(decrust_err),
1439 })
1440 }
1441 }
1442 }
1443
1444 #[track_caller]
1445 fn decrust_context_rich(self, context: types::ErrorContext) -> Result<T, DecrustError> {
1446 match self {
1447 Ok(value) => Ok(value),
1448 Err(err) => {
1449 let decrust_err: DecrustError = err.into();
1450 Err(DecrustError::WithRichContext {
1451 context,
1452 source: Box::new(decrust_err),
1453 })
1454 }
1455 }
1456 }
1457}
1458
1459/// Extension trait for Option types to convert to Result with DecrustError
1460///
1461/// This trait provides methods to convert an Option to a Result, with a MissingValue
1462/// error if the Option is None.
1463///
1464/// This trait is object-safe and can be used with dynamic dispatch.
1465pub trait DecrustOptionExt<T> {
1466 /// Converts an Option to a Result, with a MissingValue error if None
1467 ///
1468 /// # Parameters
1469 /// * `item_description` - Description of the missing value for the error message
1470 ///
1471 /// # Returns
1472 /// Ok(value) if the Option is Some(value), Err(DecrustError::MissingValue) otherwise
1473 fn decrust_ok_or_missing_value(self, item_description: &str) -> Result<T, DecrustError>;
1474
1475 /// Converts an Option to a Result, with a MissingValue error if None (owned string version)
1476 ///
1477 /// # Parameters
1478 /// * `item_description` - Description of the missing value for the error message
1479 ///
1480 /// # Returns
1481 /// Ok(value) if the Option is Some(value), Err(DecrustError::MissingValue) otherwise
1482 fn decrust_ok_or_missing_value_owned(self, item_description: String)
1483 -> Result<T, DecrustError>;
1484}
1485
1486impl<T> DecrustOptionExt<T> for Option<T> {
1487 #[track_caller]
1488 fn decrust_ok_or_missing_value(self, item_description: &str) -> Result<T, DecrustError> {
1489 match self {
1490 Some(v) => Ok(v),
1491 None => Err(DecrustError::MissingValue {
1492 item_description: item_description.to_string(),
1493 backtrace: Backtrace::generate(),
1494 }),
1495 }
1496 }
1497
1498 #[track_caller]
1499 fn decrust_ok_or_missing_value_owned(
1500 self,
1501 item_description: String,
1502 ) -> Result<T, DecrustError> {
1503 match self {
1504 Some(v) => Ok(v),
1505 None => Err(DecrustError::MissingValue {
1506 item_description,
1507 backtrace: Backtrace::generate(),
1508 }),
1509 }
1510 }
1511}
1512
1513/// Extension trait for Results that are known to always be Err
1514pub trait InfallibleResultExt<E> {
1515 /// Extract the error value from a Result that is known to always be Err
1516 ///
1517 /// This is a stable alternative to the nightly-only `into_err()` method.
1518 /// Use this when you have a Result<T, E> where T is an uninhabited type
1519 /// or when you know the Result will always be Err.
1520 fn extract_err(self) -> E;
1521}
1522
1523impl<E> InfallibleResultExt<E> for Result<std::convert::Infallible, E> {
1524 fn extract_err(self) -> E {
1525 match self {
1526 Ok(infallible) => match infallible {},
1527 Err(e) => e,
1528 }
1529 }
1530}
1531
1532/// Convenience trait for backward compatibility with generic string types
1533///
1534/// **Note:** This trait is NOT object-safe due to the use of `impl Into<String>`.
1535/// Use `DecrustResultExt` for object-safe operations.
1536pub trait DecrustResultExtConvenience<T, EOrig> {
1537 /// Convenience method for adding context with any string-like type
1538 ///
1539 /// **Warning:** This method makes the trait NOT object-safe.
1540 fn decrust_context<S: Into<String>>(self, message: S) -> Result<T, DecrustError>;
1541}
1542
1543impl<T, E> DecrustResultExtConvenience<T, E> for std::result::Result<T, E>
1544where
1545 E: Into<DecrustError>,
1546{
1547 fn decrust_context<S: Into<String>>(self, message: S) -> Result<T, DecrustError> {
1548 self.decrust_context_msg_owned(message.into())
1549 }
1550}
1551
1552/// Convenience trait for backward compatibility with generic string types
1553///
1554/// **Note:** This trait is NOT object-safe due to the use of `impl Into<String>`.
1555/// Use `DecrustOptionExt` for object-safe operations.
1556pub trait DecrustOptionExtConvenience<T> {
1557 /// Convenience method for converting to Result with any string-like type
1558 ///
1559 /// **Warning:** This method makes the trait NOT object-safe.
1560 fn decrust_ok_or_missing<S: Into<String>>(self, item_description: S)
1561 -> Result<T, DecrustError>;
1562}
1563
1564impl<T> DecrustOptionExtConvenience<T> for Option<T> {
1565 fn decrust_ok_or_missing<S: Into<String>>(
1566 self,
1567 item_description: S,
1568 ) -> Result<T, DecrustError> {
1569 self.decrust_ok_or_missing_value_owned(item_description.into())
1570 }
1571}
1572
1573/// Implementation of From<std::io::Error> for DecrustError to support extension traits
1574impl From<std::io::Error> for DecrustError {
1575 fn from(err: std::io::Error) -> Self {
1576 DecrustError::Io {
1577 source: err,
1578 path: None,
1579 operation: "I/O operation".to_string(),
1580 backtrace: Backtrace::generate(),
1581 }
1582 }
1583}
1584
1585/// Implementation of From<Box<dyn std::error::Error>> for DecrustError to support generic error handling
1586impl From<Box<dyn std::error::Error + Send + Sync + 'static>> for DecrustError {
1587 fn from(err: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
1588 DecrustError::Oops {
1589 message: "Generic error occurred".to_string(),
1590 source: err,
1591 backtrace: Backtrace::generate(),
1592 }
1593 }
1594}
1595
1596/// Implementation of From<Box<dyn std::error::Error>> for DecrustError (non-Send+Sync version)
1597impl From<Box<dyn std::error::Error>> for DecrustError {
1598 fn from(err: Box<dyn std::error::Error>) -> Self {
1599 // Convert to Send+Sync version by creating a new error with the message
1600 let message = format!("Generic error occurred: {}", err);
1601 DecrustError::Internal {
1602 message,
1603 source: OptionalError(None), // Can't store non-Send+Sync error
1604 component: None,
1605 backtrace: Backtrace::generate(),
1606 }
1607 }
1608}
1609
1610#[cfg(test)]
1611mod tests {
1612 use super::*;
1613 use backtrace::BacktraceCompat; // Ensure BacktraceCompat is in scope for tests
1614 // GenerateImplicitData is not needed in tests unless you call Backtrace::generate() directly.
1615
1616 #[test]
1617 fn test_error_creation_and_context() {
1618 let source_opt: Option<Box<dyn std::error::Error + Send + Sync + 'static>> = None;
1619 // Create the error directly
1620 let err = DecrustError::Internal {
1621 message: "Test error".to_string(),
1622 source: OptionalError(source_opt),
1623 component: None,
1624 backtrace: Backtrace::generate(),
1625 };
1626
1627 assert_eq!(err.category(), types::ErrorCategory::Internal);
1628
1629 // Create a Result with the error and use the extension trait
1630 let err_with_context_res: Result<(), DecrustError> =
1631 Err(err).decrust_context_msg("Additional context");
1632 assert!(err_with_context_res.is_err());
1633 let err_with_context = err_with_context_res.unwrap_err();
1634
1635 if let DecrustError::WithRichContext {
1636 context, source, ..
1637 } = &err_with_context
1638 {
1639 assert_eq!(context.message, "Additional context");
1640 // source is &Box<DecrustError>, so we need to dereference it properly
1641 if let DecrustError::Internal { message, .. } = source.as_ref() {
1642 assert_eq!(message, "Test error");
1643 } else {
1644 panic!("Expected Internal error variant, got {:?}", source);
1645 }
1646 } else {
1647 panic!(
1648 "Expected WithRichContext error variant, got {:?}",
1649 err_with_context
1650 );
1651 }
1652 }
1653
1654 #[test]
1655 fn test_error_clone() {
1656 let io_err_orig = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
1657 let original_err = DecrustError::Io {
1658 source: io_err_orig,
1659 path: Some(PathBuf::from("/path/to/file")),
1660 operation: "read_file".to_string(),
1661 backtrace: Backtrace::generate(),
1662 };
1663
1664 let cloned_err = original_err.clone();
1665
1666 assert_eq!(cloned_err.category(), types::ErrorCategory::Io);
1667
1668 // Use `ref` for non-Copy fields in pattern to avoid moving
1669 if let DecrustError::Io {
1670 ref path,
1671 ref operation,
1672 ref source,
1673 ..
1674 } = cloned_err
1675 {
1676 assert_eq!(*path, Some(PathBuf::from("/path/to/file")));
1677 assert_eq!(*operation, "read_file");
1678 assert_eq!(source.kind(), std::io::ErrorKind::NotFound);
1679 } else {
1680 panic!("Expected Io error variant");
1681 }
1682 assert!(BacktraceCompat::backtrace(&cloned_err).is_some());
1683 }
1684
1685 #[test]
1686 fn test_option_ext() {
1687 let opt_value: Option<i32> = Some(42);
1688 let result = opt_value.decrust_ok_or_missing_value("test value");
1689 assert!(result.is_ok());
1690 assert_eq!(result.unwrap(), 42);
1691
1692 let opt_none: Option<i32> = None;
1693 let result = opt_none.decrust_ok_or_missing_value("test value");
1694 assert!(result.is_err());
1695
1696 if let Err(DecrustError::MissingValue {
1697 item_description, ..
1698 }) = result
1699 {
1700 assert_eq!(item_description, "test value");
1701 } else {
1702 panic!("Expected MissingValue error variant");
1703 }
1704
1705 // Test the owned version
1706 let opt_none2: Option<i32> = None;
1707 let result2 = opt_none2.decrust_ok_or_missing_value_owned("owned test value".to_string());
1708 assert!(result2.is_err());
1709
1710 if let Err(DecrustError::MissingValue {
1711 item_description, ..
1712 }) = result2
1713 {
1714 assert_eq!(item_description, "owned test value");
1715 } else {
1716 panic!("Expected MissingValue error variant");
1717 }
1718
1719 // Test the convenience method
1720 let opt_none3: Option<i32> = None;
1721 let result3 = opt_none3.decrust_ok_or_missing("convenience test value");
1722 assert!(result3.is_err());
1723
1724 if let Err(DecrustError::MissingValue {
1725 item_description, ..
1726 }) = result3
1727 {
1728 assert_eq!(item_description, "convenience test value");
1729 } else {
1730 panic!("Expected MissingValue error variant");
1731 }
1732 }
1733
1734 #[test]
1735 fn test_object_safety() {
1736 // Test that the main traits are object-safe (dyn-compatible)
1737 let result: Result<i32, DecrustError> = Ok(42);
1738 let option: Option<i32> = Some(42);
1739
1740 // These should compile without errors, proving the traits are object-safe
1741 let _result_trait: &dyn DecrustResultExt<i32, DecrustError> = &result;
1742 let _option_trait: &dyn DecrustOptionExt<i32> = &option;
1743
1744 // Test that we can actually use the object-safe methods
1745 fn use_dyn_result_trait(_r: &dyn DecrustResultExt<i32, DecrustError>) {
1746 // This function signature proves the trait is object-safe
1747 }
1748
1749 fn use_dyn_option_trait(_o: &dyn DecrustOptionExt<i32>) {
1750 // This function signature proves the trait is object-safe
1751 }
1752
1753 use_dyn_result_trait(&result);
1754 use_dyn_option_trait(&option);
1755
1756 assert!(true);
1757 }
1758
1759 #[test]
1760 fn test_infallible_result_ext() {
1761 // Test the stable alternative to nightly-only into_err()
1762 fn always_fails() -> Result<std::convert::Infallible, String> {
1763 Err("This always fails".to_string())
1764 }
1765
1766 let error: String = always_fails().extract_err();
1767 assert_eq!(error, "This always fails");
1768
1769 // Test with DecrustError
1770 fn always_fails_decrust() -> Result<std::convert::Infallible, DecrustError> {
1771 Err(DecrustError::Oops {
1772 message: "Test oops error".to_string(),
1773 source: Box::new(std::io::Error::other("test")),
1774 backtrace: Backtrace::generate(),
1775 })
1776 }
1777
1778 let error: DecrustError = always_fails_decrust().extract_err();
1779 if let DecrustError::Oops { message, .. } = error {
1780 assert_eq!(message, "Test oops error");
1781 } else {
1782 panic!("Expected Oops error variant");
1783 }
1784 }
1785
1786 #[test]
1787 fn test_multiple_errors() {
1788 // Create validation errors directly
1789 let err1 = DecrustError::Validation {
1790 field: "username".to_string(),
1791 message: "Username too short".to_string(),
1792 expected: None,
1793 actual: None,
1794 rule: None,
1795 backtrace: Backtrace::generate(),
1796 };
1797
1798 let err2 = DecrustError::Validation {
1799 field: "password".to_string(),
1800 message: "Password too weak".to_string(),
1801 expected: None,
1802 actual: None,
1803 rule: None,
1804 backtrace: Backtrace::generate(),
1805 };
1806
1807 // Create multiple errors directly
1808 let multi_err = DecrustError::MultipleErrors {
1809 errors: vec![err1, err2.clone()],
1810 backtrace: Backtrace::generate(),
1811 };
1812
1813 if let DecrustError::MultipleErrors { errors, .. } = multi_err {
1814 assert_eq!(errors.len(), 2);
1815 if let DecrustError::Validation { field, .. } = &errors[0] {
1816 assert_eq!(field, "username");
1817 } else {
1818 panic!("Expected Validation error variant for errors[0]");
1819 }
1820 if let DecrustError::Validation { field, .. } = &errors[1] {
1821 assert_eq!(field, "password");
1822 } else {
1823 panic!("Expected Validation error variant for errors[1]");
1824 }
1825 } else {
1826 panic!("Expected MultipleErrors error variant");
1827 }
1828 }
1829
1830 #[test]
1831 fn test_whatever_error() {
1832 let original_io_error = std::io::Error::other("some io problem");
1833 // Create a Oops variant directly
1834 let err = DecrustError::Oops {
1835 message: "A oops message".to_string(),
1836 source: Box::new(original_io_error)
1837 as Box<dyn std::error::Error + Send + Sync + 'static>,
1838 backtrace: Backtrace::generate(),
1839 };
1840
1841 if let DecrustError::Oops {
1842 message, source, ..
1843 } = err
1844 {
1845 // Use .. for backtrace if not asserted
1846 assert_eq!(message, "A oops message");
1847 assert_eq!(source.to_string(), "some io problem");
1848 } else {
1849 panic!("Expected Oops error variant");
1850 }
1851 }
1852
1853 #[test]
1854 fn test_io_error_display() {
1855 let path_buf = PathBuf::from("/my/file.txt");
1856 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "original os error");
1857 let ak_err = DecrustError::Io {
1858 source: io_err,
1859 path: Some(path_buf),
1860 operation: "reading".to_string(),
1861 backtrace: Backtrace::generate(),
1862 };
1863 assert_eq!(
1864 ak_err.to_string(),
1865 "I/O error during operation 'reading' on path '/my/file.txt': original os error"
1866 );
1867 }
1868
1869 #[test]
1870 fn test_io_error_display_no_path() {
1871 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "original os error");
1872 let ak_err = DecrustError::Io {
1873 source: io_err,
1874 path: None,
1875 operation: "reading".to_string(),
1876 backtrace: Backtrace::generate(),
1877 };
1878 assert_eq!(
1879 ak_err.to_string(),
1880 "I/O error during operation 'reading' on path 'N/A': original os error"
1881 );
1882 }
1883}