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&lt;Box&lt;dyn std::error::Error&gt;&gt; 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&lt;Box&lt;dyn std::error::Error&gt;&gt; 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}