rootcause/compat/
error_stack05.rs

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