rootcause/compat/boxed_error.rs
1//! Convert rootcause [`Report`]s into boxed error trait objects.
2//!
3//! # Overview
4//!
5//! This module provides conversion utilities for transforming rootcause
6//! [`Report`]s into standard Rust error trait objects (`Box<dyn Error>`). This
7//! is useful when:
8//! - Integrating with APIs that expect `Box<dyn Error>`
9//! - Converting to standardized error types for external consumption
10//! - Working with error handling patterns that use trait objects
11//! - Providing a uniform error interface across different error types
12//!
13//! The conversion preserves the report's formatting and error information while
14//! wrapping it in a standard error trait object that can be used with any code
15//! expecting `Box<dyn Error>`.
16//!
17//! # Converting from Rootcause to Boxed Errors
18//!
19//! Use the [`IntoBoxedError`] trait to convert reports into boxed error trait
20//! objects:
21//!
22//! ```
23//! use std::error::Error;
24//!
25//! use rootcause::{compat::boxed_error::IntoBoxedError, prelude::*};
26//!
27//! fn rootcause_function() -> Result<String, Report> {
28//! Err(report!("database connection failed"))
29//! }
30//!
31//! fn boxed_error_function() -> Result<String, Box<dyn Error + Send + Sync>> {
32//! // Convert Result<T, Report> to Result<T, Box<dyn Error + Send + Sync>>
33//! let value = rootcause_function().into_boxed_error()?;
34//! Ok(value)
35//! }
36//! ```
37//!
38//! You can also convert individual [`Report`] values:
39//!
40//! ```
41//! use std::error::Error;
42//!
43//! use rootcause::{compat::boxed_error::IntoBoxedError, prelude::*};
44//!
45//! let report: Report = report!("operation failed").attach("debug info");
46//! let boxed_err: Box<dyn Error + Send + Sync> = report.into_boxed_error();
47//!
48//! // The boxed error preserves the report's formatting
49//! println!("{}", boxed_err);
50//! ```
51//!
52//! # Thread Safety
53//!
54//! The type of boxed error depends on the thread safety marker:
55//! - [`SendSync`] reports convert to `Box<dyn Error + Send + Sync>`
56//! - [`Local`] reports convert to `Box<dyn Error>`
57//!
58//! This ensures that the resulting boxed error respects the thread safety
59//! constraints of the original report.
60//!
61//! ```
62//! use std::{error::Error, rc::Rc};
63//!
64//! use rootcause::{compat::boxed_error::IntoBoxedError, markers::Local, prelude::*};
65//!
66//! // SendSync report becomes Box<dyn Error + Send + Sync>
67//! let send_sync_report: Report = report!("network error");
68//! let send_sync_boxed: Box<dyn Error + Send + Sync> = send_sync_report.into_boxed_error();
69//!
70//! // Local report becomes Box<dyn Error>
71//! let local_report: Report<_, _, Local> =
72//! report!("local error").into_local().attach(Rc::new("data"));
73//! let local_boxed: Box<dyn Error> = local_report.into_boxed_error();
74//! ```
75//!
76//! # Converting from Boxed Errors to Rootcause
77//!
78//! Use the [`IntoRootcause`] trait to convert boxed errors into reports:
79//!
80//! ```
81//! use std::error::Error;
82//!
83//! use rootcause::prelude::*;
84//!
85//! fn uses_boxed_error() -> Result<String, Box<dyn Error + Send + Sync>> {
86//! Err("something failed".into())
87//! }
88//!
89//! fn uses_rootcause() -> Result<String, Report> {
90//! // Convert to rootcause report
91//! let value = uses_boxed_error().into_rootcause()?;
92//! Ok(value)
93//! }
94//! ```
95//!
96//! # Using `From` Trait
97//!
98//! The `From` trait is also implemented for direct conversions:
99//!
100//! ```
101//! use std::error::Error;
102//!
103//! use rootcause::prelude::*;
104//!
105//! let report: Report = report!("failed");
106//! let boxed_err: Box<dyn Error + Send + Sync> = report.into();
107//!
108//! // The ? operator works automatically
109//! fn convert_automatically() -> Result<(), Box<dyn Error + Send + Sync>> {
110//! let _report: Report = report!("error");
111//! // Automatic conversion via ?
112//! Err(_report)?
113//! }
114//! ```
115
116use alloc::boxed::Box;
117use core::error::Error;
118
119use rootcause_internals::handlers::{ContextFormattingStyle, ContextHandler, FormattingFunction};
120
121use super::{IntoRootcause, ReportAsError};
122use crate::{
123 Report,
124 markers::{self, Dynamic, Local, SendSync},
125};
126
127/// A custom handler for boxed error trait objects that delegates to the
128/// underlying error's formatting.
129///
130/// This handler ensures that boxed errors (`Box<dyn Error>` and `Box<dyn Error
131/// + Send + Sync>`) display identically whether they're used directly or
132/// wrapped in a rootcause [`Report`]. You typically don't need to use this
133/// handler directly - it's used automatically by the [`IntoRootcause`] trait.
134///
135/// # Implementation Details
136///
137/// - **Display**: Uses the boxed error's `Display` implementation
138/// - **Debug**: Uses the boxed error's `Debug` implementation
139/// - **Source**: Uses the boxed error's `source` method to traverse the error
140/// chain
141/// - **Formatting style**: Matches the report's formatting function (Display or
142/// Debug)
143///
144/// # Examples
145///
146/// ```
147/// use rootcause::{Report, compat::boxed_error::BoxedErrorHandler};
148///
149/// let boxed: Box<dyn std::error::Error + Send + Sync> =
150/// Box::new(std::io::Error::from(std::io::ErrorKind::NotFound));
151/// let report = Report::new_sendsync_custom::<BoxedErrorHandler>(boxed);
152/// ```
153#[derive(Copy, Clone, Debug)]
154pub struct BoxedErrorHandler;
155
156impl ContextHandler<Box<dyn Error + Send + Sync>> for BoxedErrorHandler {
157 fn source(boxed_error: &Box<dyn Error + Send + Sync>) -> Option<&(dyn Error + 'static)> {
158 boxed_error.source()
159 }
160
161 fn display(
162 boxed_error: &Box<dyn Error + Send + Sync>,
163 formatter: &mut core::fmt::Formatter<'_>,
164 ) -> core::fmt::Result {
165 core::fmt::Display::fmt(boxed_error, formatter)
166 }
167
168 fn debug(
169 boxed_error: &Box<dyn Error + Send + Sync>,
170 formatter: &mut core::fmt::Formatter<'_>,
171 ) -> core::fmt::Result {
172 core::fmt::Debug::fmt(boxed_error, formatter)
173 }
174
175 fn preferred_formatting_style(
176 _value: &Box<dyn Error + Send + Sync>,
177 formatting_function: FormattingFunction,
178 ) -> ContextFormattingStyle {
179 ContextFormattingStyle {
180 function: formatting_function,
181 }
182 }
183}
184
185impl ContextHandler<Box<dyn Error>> for BoxedErrorHandler {
186 fn source(boxed_error: &Box<dyn Error>) -> Option<&(dyn Error + 'static)> {
187 boxed_error.source()
188 }
189
190 fn display(
191 boxed_error: &Box<dyn Error>,
192 formatter: &mut core::fmt::Formatter<'_>,
193 ) -> core::fmt::Result {
194 core::fmt::Display::fmt(boxed_error, formatter)
195 }
196
197 fn debug(
198 boxed_error: &Box<dyn Error>,
199 formatter: &mut core::fmt::Formatter<'_>,
200 ) -> core::fmt::Result {
201 core::fmt::Debug::fmt(boxed_error, formatter)
202 }
203
204 fn preferred_formatting_style(
205 _value: &Box<dyn Error>,
206 formatting_function: FormattingFunction,
207 ) -> ContextFormattingStyle {
208 ContextFormattingStyle {
209 function: formatting_function,
210 }
211 }
212}
213
214/// A trait for converting rootcause [`Report`]s into boxed error trait objects.
215///
216/// This trait provides the `.into_boxed_error()` method for converting
217/// rootcause reports into standard Rust error trait objects. It's implemented
218/// for both [`Report`] and [`Result<T, Report>`], making it easy to integrate
219/// with APIs that expect `Box<dyn Error>`.
220///
221/// The specific type of boxed error depends on the thread safety marker:
222/// - [`SendSync`] reports convert to `Box<dyn Error + Send + Sync>`
223/// - [`Local`] reports convert to `Box<dyn Error>`
224///
225/// # Examples
226///
227/// ## Converting a Result with SendSync Report
228///
229/// ```
230/// use std::error::Error;
231///
232/// use rootcause::{compat::boxed_error::IntoBoxedError, prelude::*};
233///
234/// fn uses_rootcause() -> Result<i32, Report> {
235/// Err(report!("failed"))
236/// }
237///
238/// fn uses_boxed_error() -> Result<i32, Box<dyn Error + Send + Sync>> {
239/// let value = uses_rootcause().into_boxed_error()?;
240/// Ok(value)
241/// }
242/// ```
243///
244/// ## Converting a Local Report
245///
246/// ```
247/// use std::{error::Error, rc::Rc};
248///
249/// use rootcause::{compat::boxed_error::IntoBoxedError, markers::Local, prelude::*};
250///
251/// let local_report: Report<_, _, Local> = report!("error").into_local().attach(Rc::new("data"));
252/// let boxed_err: Box<dyn Error> = local_report.into_boxed_error();
253///
254/// // The boxed error displays the full report structure
255/// println!("{}", boxed_err);
256/// ```
257///
258/// ## Using `From` Instead
259///
260/// You can also use the `From` trait for explicit conversions:
261///
262/// ```
263/// use std::error::Error;
264///
265/// use rootcause::prelude::*;
266///
267/// let report: Report = report!("error");
268/// let boxed_err: Box<dyn Error + Send + Sync> = report.into();
269/// ```
270pub trait IntoBoxedError {
271 /// The type produced by the conversion.
272 ///
273 /// - For [`Report<_, _, SendSync>`]: produces `Box<dyn Error + Send +
274 /// Sync>`
275 /// - For [`Report<_, _, Local>`]: produces `Box<dyn Error>`
276 /// - For [`Result<T, Report<_, _, SendSync>>`]: produces `Result<T, Box<dyn
277 /// Error + Send + Sync>>`
278 /// - For [`Result<T, Report<_, _, Local>>`]: produces `Result<T, Box<dyn
279 /// Error>>`
280 type Output;
281
282 /// Converts this value into a boxed error type.
283 ///
284 /// For [`Report`], this wraps the report in a boxed error trait object. For
285 /// [`Result<T, Report>`], this converts the error variant while preserving
286 /// the success value.
287 ///
288 /// The thread safety of the resulting boxed error matches the thread safety
289 /// of the original report.
290 ///
291 /// # Examples
292 ///
293 /// ```
294 /// use std::error::Error;
295 ///
296 /// use rootcause::{compat::boxed_error::IntoBoxedError, prelude::*};
297 ///
298 /// // Convert a result
299 /// let result: Result<i32, Report> = Ok(42);
300 /// let converted: Result<i32, Box<dyn Error + Send + Sync>> = result.into_boxed_error();
301 /// assert_eq!(converted.unwrap(), 42);
302 ///
303 /// // Convert a report
304 /// let report: Report = report!("failed");
305 /// let boxed_err: Box<dyn Error + Send + Sync> = report.into_boxed_error();
306 /// ```
307 fn into_boxed_error(self) -> Self::Output;
308}
309
310impl<C: ?Sized, O> IntoBoxedError for Report<C, O, SendSync> {
311 type Output = Box<dyn Error + Send + Sync>;
312
313 fn into_boxed_error(self) -> Self::Output {
314 Box::new(ReportAsError(self.into_dynamic().into_cloneable()))
315 }
316}
317
318impl<C: ?Sized, O> IntoBoxedError for Report<C, O, Local> {
319 type Output = Box<dyn Error>;
320
321 fn into_boxed_error(self) -> Self::Output {
322 Box::new(ReportAsError(self.into_dynamic().into_cloneable()))
323 }
324}
325
326impl<T, C: ?Sized, O> IntoBoxedError for Result<T, Report<C, O, SendSync>> {
327 type Output = Result<T, Box<dyn Error + Send + Sync>>;
328
329 fn into_boxed_error(self) -> Self::Output {
330 self.map_err(|r| r.into_boxed_error())
331 }
332}
333
334impl<T, C: ?Sized, O> IntoBoxedError for Result<T, Report<C, O, Local>> {
335 type Output = Result<T, Box<dyn Error>>;
336
337 fn into_boxed_error(self) -> Self::Output {
338 self.map_err(|r| r.into_boxed_error())
339 }
340}
341
342impl<C: ?Sized, O> From<Report<C, O, SendSync>> for Box<dyn Error + Send + Sync> {
343 fn from(report: Report<C, O, SendSync>) -> Self {
344 report.into_boxed_error()
345 }
346}
347
348impl<C: ?Sized, O> From<Report<C, O, Local>> for Box<dyn Error> {
349 fn from(report: Report<C, O, Local>) -> Self {
350 report.into_boxed_error()
351 }
352}
353
354impl IntoRootcause for Box<dyn Error + Send + Sync> {
355 type Output = Report;
356
357 #[inline(always)]
358 fn into_rootcause(self) -> Self::Output {
359 Report::new_custom::<BoxedErrorHandler>(self).into_dynamic()
360 }
361}
362
363impl IntoRootcause for Box<dyn Error> {
364 type Output = Report<Dynamic, markers::Mutable, Local>;
365
366 #[inline(always)]
367 fn into_rootcause(self) -> Self::Output {
368 Report::new_custom::<BoxedErrorHandler>(self).into_dynamic()
369 }
370}
371
372impl<T> IntoRootcause for Result<T, Box<dyn Error + Send + Sync>> {
373 type Output = Result<T, Report>;
374
375 #[inline(always)]
376 fn into_rootcause(self) -> Self::Output {
377 self.map_err(|e| e.into_rootcause())
378 }
379}
380
381impl<T> IntoRootcause for Result<T, Box<dyn Error>> {
382 type Output = Result<T, Report<Dynamic, markers::Mutable, Local>>;
383
384 #[inline(always)]
385 fn into_rootcause(self) -> Self::Output {
386 self.map_err(|e| e.into_rootcause())
387 }
388}