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}