rootcause/compat/
error_stack06.rs

1//! Bidirectional integration with the [`error-stack`] 0.6.x error handling
2//! library.
3//!
4//! This module specifically supports `error-stack` version 0.6.x. To enable
5//! this integration, add the `compat-error-stack06` feature flag to your
6//! `Cargo.toml`.
7//!
8//! # Overview
9//!
10//! This module provides seamless interoperability between rootcause [`Report`]s
11//! and [`error_stack::Report`], supporting conversions in both directions.
12//! This is useful when:
13//! - Migrating from error-stack to rootcause incrementally
14//! - Working with libraries that use error-stack for error handling
15//! - Integrating rootcause into an existing error-stack-based codebase
16//! - Calling error-stack-based APIs from rootcause code (and vice versa)
17//!
18//! Conversions preserve error information and formatting, allowing you to mix
19//! both error handling approaches seamlessly.
20//!
21//! # Converting from error-stack to Rootcause
22//!
23//! Use the [`IntoRootcause`] trait to convert error-stack
24//! reports into rootcause reports:
25//!
26//! ```
27//! use std::io;
28//!
29//! use rootcause::prelude::*;
30//!
31//! fn error_stack_function() -> Result<String, error_stack::Report<io::Error>> {
32//!     Err(error_stack::report!(io::Error::from(
33//!         io::ErrorKind::NotFound
34//!     )))
35//! }
36//!
37//! fn rootcause_function() -> Result<String, Report> {
38//!     // Convert error_stack result to rootcause result
39//!     let value = error_stack_function().into_rootcause()?;
40//!     Ok(value)
41//! }
42//! ```
43//!
44//! You can also convert individual [`error_stack::Report`] values:
45//!
46//! ```
47//! use rootcause::prelude::*;
48//!
49//! let es_report = error_stack::report!(std::io::Error::from(std::io::ErrorKind::NotFound));
50//! let report: Report<_> = es_report.into_rootcause();
51//!
52//! // The report preserves error-stack's formatting
53//! println!("{}", report);
54//! ```
55//!
56//! # Converting from Rootcause to error-stack
57//!
58//! Use the [`IntoErrorStack`] trait to convert rootcause reports into
59//! error-stack reports:
60//!
61//! ```
62//! use rootcause::{compat::error_stack06::IntoErrorStack, prelude::*};
63//!
64//! fn rootcause_function() -> Result<String, Report> {
65//!     Err(report!("database connection failed"))
66//! }
67//!
68//! // The ? operator automatically converts Result<T, Report> to Result<T, error_stack::Report>
69//! fn error_stack_function()
70//! -> Result<String, error_stack::Report<rootcause::compat::ReportAsError>> {
71//!     rootcause_function().into_error_stack()?;
72//!     Ok("success".to_string())
73//! }
74//! ```
75//!
76//! You can also convert individual [`Report`] values:
77//!
78//! ```
79//! use rootcause::{compat::error_stack06::IntoErrorStack, prelude::*};
80//!
81//! let report = report!("operation failed").attach("debug info");
82//! let es_report: error_stack::Report<_> = report.into_error_stack();
83//!
84//! // The error-stack report preserves the report's formatting
85//! println!("{}", es_report);
86//! ```
87//!
88//! Or using the `From` trait directly:
89//!
90//! ```
91//! use rootcause::prelude::*;
92//!
93//! let report: Report = report!("operation failed");
94//! let es_report: error_stack::Report<_> = report.into();
95//!
96//! // The error-stack report preserves the report's formatting
97//! println!("{}", es_report);
98//! ```
99//!
100//! The `From` trait also works with the `?` operator for automatic conversion:
101//!
102//! ```
103//! use rootcause::prelude::*;
104//!
105//! fn rootcause_function() -> Result<String, Report> {
106//!     Err(report!("something failed"))
107//! }
108//!
109//! fn error_stack_function()
110//! -> Result<String, error_stack::Report<rootcause::compat::ReportAsError>> {
111//!     // The ? operator automatically converts Report to error_stack::Report
112//!     rootcause_function()?;
113//!     Ok("success".to_string())
114//! }
115//! ```
116//!
117//! # Handler Behavior
118//!
119//! The [`ErrorStackHandler`] delegates to error-stack's own formatting
120//! implementation, ensuring that converted reports display exactly as they
121//! would in pure error-stack code. This includes:
122//! - Display formatting via [`error_stack::Report`]'s `Display` implementation
123//! - Debug formatting via [`error_stack::Report`]'s `Debug` implementation
124//! - Source chain navigation via the context's `source` method
125//!
126//! When converting from rootcause to error-stack, the entire [`Report`]
127//! structure (including all contexts and attachments) is preserved and
128//! formatted according to rootcause's formatting rules.
129//!
130//! [`error-stack`]: error_stack
131
132use core::marker::PhantomData;
133
134use rootcause_internals::handlers::{ContextFormattingStyle, ContextHandler, FormattingFunction};
135
136use crate::{
137    Report,
138    compat::{IntoRootcause, ReportAsError},
139    markers::{self, SendSync},
140};
141
142/// A custom handler for [`error_stack::Report`] that delegates to
143/// error-stack's own formatting.
144///
145/// This handler ensures that [`error_stack::Report`] objects display
146/// identically whether they're used directly or wrapped in a rootcause
147/// [`Report`]. You typically don't need to use this handler directly - it's
148/// used automatically by the [`IntoRootcause`] trait.
149///
150/// # Implementation Details
151///
152/// - **Display**: Uses [`error_stack::Report`]'s `Display` implementation
153/// - **Debug**: Uses [`error_stack::Report`]'s `Debug` implementation
154/// - **Source**: Uses the current context's `source` method to traverse the
155///   error chain
156/// - **Formatting style**: Matches the report's formatting function (Display or
157///   Debug)
158///
159/// # Examples
160///
161/// ```
162/// use rootcause::{Report, compat::error_stack06::ErrorStackHandler};
163///
164/// let es_report = error_stack::Report::new(std::io::Error::from(std::io::ErrorKind::NotFound));
165/// let report: Report<_> = Report::new_custom::<ErrorStackHandler<_>>(es_report);
166/// ```
167///
168/// # Type Parameters
169///
170/// - `C`: The context type of the error-stack report, which must implement
171///   `Error + Send + Sync + 'static`
172pub struct ErrorStackHandler<C>(PhantomData<C>);
173
174impl<C> ContextHandler<error_stack::Report<C>> for ErrorStackHandler<C>
175where
176    C: core::error::Error + Send + Sync + 'static,
177{
178    fn source(value: &error_stack::Report<C>) -> Option<&(dyn core::error::Error + 'static)> {
179        value.current_context().source()
180    }
181
182    fn display(
183        value: &error_stack::Report<C>,
184        formatter: &mut core::fmt::Formatter<'_>,
185    ) -> core::fmt::Result {
186        core::fmt::Display::fmt(value, formatter)
187    }
188
189    fn debug(
190        value: &error_stack::Report<C>,
191        formatter: &mut core::fmt::Formatter<'_>,
192    ) -> core::fmt::Result {
193        core::fmt::Debug::fmt(value, formatter)
194    }
195
196    fn preferred_formatting_style(
197        _value: &error_stack::Report<C>,
198        formatting_function: FormattingFunction,
199    ) -> ContextFormattingStyle {
200        ContextFormattingStyle {
201            function: formatting_function,
202        }
203    }
204}
205
206impl<C> IntoRootcause for error_stack::Report<C>
207where
208    C: core::error::Error + Send + Sync + 'static,
209{
210    type Output = crate::Report<Self>;
211
212    fn into_rootcause(self) -> Self::Output {
213        Report::new_custom::<ErrorStackHandler<C>>(self)
214    }
215}
216
217impl<T, C> IntoRootcause for Result<T, error_stack::Report<C>>
218where
219    C: core::error::Error + Send + Sync + 'static,
220{
221    type Output = Result<T, Report<error_stack::Report<C>>>;
222
223    #[inline(always)]
224    fn into_rootcause(self) -> Self::Output {
225        self.map_err(|e| e.into_rootcause())
226    }
227}
228
229/// A trait for converting rootcause [`Report`]s into [`error_stack::Report`].
230///
231/// This trait provides the `.into_error_stack()` method for converting
232/// rootcause reports into error-stack reports. It's implemented for both
233/// [`Report`] and [`Result<T, Report>`], making it easy to call
234/// error-stack-based APIs from rootcause code.
235///
236/// The conversion wraps the entire report structure inside an
237/// [`error_stack::Report`], preserving all contexts, attachments, and
238/// formatting behavior.
239///
240/// # Examples
241///
242/// ## Converting a Result
243///
244/// ```
245/// use rootcause::{compat::error_stack06::IntoErrorStack, prelude::*};
246///
247/// fn uses_rootcause() -> Result<i32, Report> {
248///     Err(report!("failed"))
249/// }
250///
251/// fn uses_error_stack() -> Result<i32, error_stack::Report<rootcause::compat::ReportAsError>> {
252///     let value = uses_rootcause().into_error_stack()?;
253///     Ok(value)
254/// }
255/// ```
256///
257/// ## Converting a Report
258///
259/// ```
260/// use rootcause::{compat::error_stack06::IntoErrorStack, prelude::*};
261///
262/// let report = report!("operation failed").attach("debug info");
263/// let es_report: error_stack::Report<_> = report.into_error_stack();
264///
265/// // The error-stack report displays the full rootcause structure
266/// println!("{}", es_report);
267/// ```
268///
269/// ## Using `From` Instead
270///
271/// You can also use the `From` trait for explicit conversions:
272///
273/// ```
274/// use rootcause::prelude::*;
275///
276/// let report: Report = report!("error");
277/// let es_report: error_stack::Report<_> = report.into();
278/// ```
279///
280/// # Type Parameters
281///
282/// - `C`: The context type parameter of the rootcause report. When converting,
283///   the report will be wrapped as an
284///   [`error_stack::Report<ReportAsError<C>>`].
285pub trait IntoErrorStack<C: ?Sized> {
286    /// The type produced by the conversion.
287    ///
288    /// - For [`Report`]: produces
289    ///   [`error_stack::Report<ReportAsError<C>>`](error_stack::Report)
290    /// - For [`Result<T, Report>`]: produces `Result<T,
291    ///   error_stack::Report<ReportAsError<C>>>`
292    type Output;
293
294    /// Converts this value into an error-stack type.
295    ///
296    /// For [`Report`], this wraps the report in an error-stack report using
297    /// [`ReportAsError`] as the context type. For [`Result<T, Report>`], this
298    /// converts the error variant while preserving the success value.
299    ///
300    /// The report is wrapped in a [`ReportAsError`] adapter that implements
301    /// [`core::error::Error`], allowing it to be used as the context type in
302    /// an error-stack report.
303    ///
304    /// # Examples
305    ///
306    /// ```
307    /// use rootcause::{compat::error_stack06::IntoErrorStack, prelude::*};
308    ///
309    /// // Convert a result
310    /// let result: Result<i32, Report> = Ok(42);
311    /// let converted: Result<i32, error_stack::Report<_>> = result.into_error_stack();
312    /// assert_eq!(converted.unwrap(), 42);
313    ///
314    /// // Convert a report
315    /// let report: Report = report!("failed");
316    /// let es_report: error_stack::Report<_> = report.into_error_stack();
317    /// ```
318    fn into_error_stack(self) -> Self::Output;
319}
320
321impl<C: ?Sized, O> IntoErrorStack<C> for Report<C, O, SendSync> {
322    type Output = error_stack::Report<ReportAsError<C>>;
323
324    fn into_error_stack(self) -> Self::Output {
325        error_stack::Report::from(self)
326    }
327}
328
329impl<T, C: ?Sized, O> IntoErrorStack<C> for Result<T, Report<C, O, SendSync>> {
330    type Output = Result<T, error_stack::Report<ReportAsError<C>>>;
331
332    fn into_error_stack(self) -> Self::Output {
333        self.map_err(|e| e.into_error_stack())
334    }
335}
336
337impl<C: ?Sized, O> From<Report<C, O, markers::SendSync>> for error_stack::Report<ReportAsError<C>> {
338    fn from(report: Report<C, O, markers::SendSync>) -> Self {
339        error_stack::Report::from(ReportAsError(report.into_cloneable()))
340    }
341}