dmsc/core/
error_chain.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18#![allow(non_snake_case)]
19
20//! # Error Chain Support
21//!
22//! This module provides enhanced error handling with error chain support, allowing
23//! errors to be wrapped with additional context while preserving the original error.
24//! This is particularly useful for debugging and error reporting in complex
25//! distributed systems.
26//!
27//! ## Key Features
28//!
29//! - **Error Chaining**: Wrap errors with additional context
30//! - **Error Context**: Add contextual information to errors
31//! - **Error Traversal**: Walk through the error chain
32//! - **Pretty Printing**: Format error chains for better readability
33//! - **Backtrace Support**: Optional backtrace capture for debugging
34//!
35//! ## Usage Examples
36//!
37//! ```rust,ignore
38//! use dmsc::core::error_chain::{DMSCErrorChain, DMSCErrorContext};
39//!
40//! let result = some_operation()
41//!     .map_err(|e| DMSCErrorChain::new(e).context("Failed to perform operation"));
42//! ```
43
44use std::error::Error as StdError;
45use std::fmt;
46
47/// A chain of errors with contextual information.
48#[derive(Debug)]
49pub struct DMSCErrorChain {
50    /// The source error
51    source: Box<dyn StdError + Send + Sync>,
52    /// Contextual message
53    context: String,
54    /// Previous error in the chain (if any)
55    previous: Option<Box<DMSCErrorChain>>,
56}
57
58impl DMSCErrorChain {
59    /// Creates a new error chain from a source error.
60    pub fn new<E>(source: E) -> Self
61    where
62        E: StdError + Send + Sync + 'static,
63    {
64        Self {
65            source: Box::new(source),
66            context: String::new(),
67            previous: None,
68        }
69    }
70
71    /// Creates a new error chain with context.
72    pub fn with_context<E, S>(source: E, context: S) -> Self
73    where
74        E: StdError + Send + Sync + 'static,
75        S: Into<String>,
76    {
77        Self {
78            source: Box::new(source),
79            context: context.into(),
80            previous: None,
81        }
82    }
83
84    /// Adds context to the error chain.
85    pub fn context<S>(mut self, context: S) -> Self
86    where
87        S: Into<String>,
88    {
89        let source = std::mem::replace(&mut self.source, Box::new(std::io::Error::other("error_chain_internal")));
90        Self {
91            source,
92            context: context.into(),
93            previous: Some(Box::new(self)),
94        }
95    }
96
97    /// Gets the source error.
98    pub fn source_error(&self) -> &(dyn StdError + Send + Sync) {
99        &*self.source
100    }
101
102    /// Gets the context message.
103    pub fn get_context(&self) -> &str {
104        &self.context
105    }
106
107    /// Gets the previous error in the chain.
108    pub fn previous(&self) -> Option<&DMSCErrorChain> {
109        self.previous.as_deref()
110    }
111
112    /// Iterates through the error chain.
113    pub fn chain(&self) -> DMSCErrorChainIter<'_> {
114        DMSCErrorChainIter { current: Some(self) }
115    }
116
117    /// Checks if this error chain contains a specific error type.
118    pub fn contains<E>(&self) -> bool
119    where
120        E: StdError + Send + Sync + 'static,
121    {
122        if self.source.is::<E>() {
123            return true;
124        }
125        
126        let mut current = self.previous.as_deref();
127        while let Some(chain) = current {
128            if chain.source.is::<E>() {
129                return true;
130            }
131            current = chain.previous.as_deref();
132        }
133        false
134    }
135
136    /// Gets the root cause of the error chain.
137    pub fn root_cause(&self) -> &(dyn StdError + Send + Sync) {
138        let mut current = self;
139        while let Some(prev) = &current.previous {
140            current = prev;
141        }
142        current.source_error()
143    }
144
145    /// Formats the error chain as a pretty string.
146    pub fn pretty_format(&self) -> String {
147        let mut result = String::new();
148        
149        if !self.context.is_empty() {
150            result.push_str(&format!("Error: {}\n", self.context));
151        }
152        result.push_str(&format!("Source: {}\n", self.source_error()));
153
154        let mut level = 1;
155        let mut current = self.previous.as_deref();
156        while let Some(chain) = current {
157            result.push_str(&format!("\nCaused by (level {level}):\n"));
158            if !chain.context.is_empty() {
159                result.push_str(&format!("  {}\n", chain.get_context()));
160            }
161            result.push_str(&format!("  {}\n", chain.source_error()));
162            level += 1;
163            current = chain.previous.as_deref();
164        }
165
166        result
167    }
168}
169
170impl StdError for DMSCErrorChain {
171    fn source(&self) -> Option<&(dyn StdError + 'static)> {
172        Some(&*self.source)
173    }
174}
175
176impl fmt::Display for DMSCErrorChain {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        if !self.context.is_empty() {
179            write!(f, "{}", self.context)?;
180            if self.previous.is_some() {
181                write!(f, ": ")?;
182            }
183        }
184        
185        if let Some(prev) = &self.previous {
186            write!(f, "{prev}")?;
187        } else {
188            write!(f, "{}", self.source_error())?;
189        }
190        
191        Ok(())
192    }
193}
194
195/// Iterator over the error chain.
196pub struct DMSCErrorChainIter<'a> {
197    current: Option<&'a DMSCErrorChain>,
198}
199
200impl<'a> Iterator for DMSCErrorChainIter<'a> {
201    type Item = &'a DMSCErrorChain;
202
203    fn next(&mut self) -> Option<Self::Item> {
204        let current = self.current?;
205        self.current = current.previous.as_deref();
206        Some(current)
207    }
208}
209
210/// Trait for adding error chain functionality to Result types.
211pub trait DMSCErrorContext<T, E> {
212    /// Adds context to the error if the Result is Err.
213    fn chain_context<S>(self, context: S) -> Result<T, DMSCErrorChain>
214    where
215        S: Into<String>;
216
217    /// Adds context to the error with lazy evaluation.
218    fn with_chain_context<S, F>(self, f: F) -> Result<T, DMSCErrorChain>
219    where
220        S: Into<String>,
221        F: FnOnce() -> S;
222}
223
224impl<T, E> DMSCErrorContext<T, E> for Result<T, E>
225where
226    E: StdError + Send + Sync + 'static,
227{
228    fn chain_context<S>(self, context: S) -> Result<T, DMSCErrorChain>
229    where
230        S: Into<String>,
231    {
232        self.map_err(|e| DMSCErrorChain::with_context(e, context))
233    }
234
235    fn with_chain_context<S, F>(self, f: F) -> Result<T, DMSCErrorChain>
236    where
237        S: Into<String>,
238        F: FnOnce() -> S,
239    {
240        self.map_err(|e| DMSCErrorChain::with_context(e, f()))
241    }
242}
243
244/// Extension trait for adding error context to Option types.
245pub trait DMSCOptionErrorContext<T> {
246    /// Converts None to an error with context.
247    fn ok_or_chain<E>(self, err: E) -> Result<T, DMSCErrorChain>
248    where
249        E: StdError + Send + Sync + 'static;
250
251    /// Converts None to an error with lazy context.
252    fn ok_or_else_chain<E, F>(self, f: F) -> Result<T, DMSCErrorChain>
253    where
254        E: StdError + Send + Sync + 'static,
255        F: FnOnce() -> E;
256}
257
258impl<T> DMSCOptionErrorContext<T> for Option<T> {
259    fn ok_or_chain<E>(self, err: E) -> Result<T, DMSCErrorChain>
260    where
261        E: StdError + Send + Sync + 'static,
262    {
263        self.ok_or_else(|| DMSCErrorChain::new(err))
264    }
265
266    fn ok_or_else_chain<E, F>(self, f: F) -> Result<T, DMSCErrorChain>
267    where
268        E: StdError + Send + Sync + 'static,
269        F: FnOnce() -> E,
270    {
271        self.ok_or_else(|| DMSCErrorChain::new(f()))
272    }
273}
274
275/// Utility functions for error chain operations.
276pub mod utils {
277    use super::*;
278
279    /// Creates a new error chain from a string message.
280    pub fn chain_from_msg<S>(msg: S) -> DMSCErrorChain
281    where
282        S: Into<String>,
283    {
284        DMSCErrorChain::new(std::io::Error::other(msg.into()))
285    }
286
287    /// Wraps an error with context if it matches a predicate.
288    pub fn chain_if<E, F, S>(err: E, predicate: F, context: S) -> DMSCErrorChain
289    where
290        E: StdError + Send + Sync + 'static,
291        F: FnOnce(&E) -> bool,
292        S: Into<String>,
293    {
294        if predicate(&err) {
295            DMSCErrorChain::with_context(err, context)
296        } else {
297            DMSCErrorChain::new(err)
298        }
299    }
300
301    /// Collects multiple errors into a single error chain.
302    pub fn chain_from_multiple<S>(errors: Vec<Box<dyn StdError + Send + Sync>>, context: S) -> DMSCErrorChain
303    where
304        S: Into<String>,
305    {
306        if errors.is_empty() {
307            return chain_from_msg("No errors provided");
308        }
309
310        if errors.len() == 1 {
311            let error = errors.into_iter().next()
312                .ok_or_else(|| std::io::Error::other("errors vector should have at least one element"))
313                .unwrap_or_else(|_| Box::new(std::io::Error::other("errors vector should have at least one element")));
314            return DMSCErrorChain::with_context(MultiError { errors: vec![error] }, context);
315        }
316
317        let combined = MultiError { errors };
318        DMSCErrorChain::with_context(combined, context)
319    }
320
321    #[derive(Debug)]
322    struct MultiError {
323        errors: Vec<Box<dyn StdError + Send + Sync>>,
324    }
325
326    impl fmt::Display for MultiError {
327        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328            writeln!(f, "Multiple errors occurred ({} total):", self.errors.len())?;
329            for (i, err) in self.errors.iter().enumerate() {
330                writeln!(f, "  [{}] {}", i + 1, err)?;
331            }
332            Ok(())
333        }
334    }
335
336    impl StdError for MultiError {
337        fn source(&self) -> Option<&(dyn StdError + 'static)> {
338            self.errors.first().map(|e| &**e as &(dyn StdError + 'static))
339        }
340    }
341}