Skip to main content

rootcause_preformat/
preformatted.rs

1//! Preformatted context and attachment storage types.
2//!
3//! See the [crate-level documentation](crate) for an overview of preformatting
4//! and the extension traits that produce these types.
5//!
6//! # Non-Send/Sync Example
7//!
8//! ```
9//! use core::cell::Cell;
10//!
11//! use rootcause::{
12//!     markers::{Local, SendSync},
13//!     prelude::*,
14//! };
15//! use rootcause_preformat::{PreformatReportExt, PreformattedContext};
16//!
17//! // Cell is !Send and !Sync
18//! #[derive(Debug)]
19//! struct LocalError {
20//!     counter: Cell<u32>,
21//! }
22//!
23//! impl core::fmt::Display for LocalError {
24//!     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
25//!         write!(f, "Local error: {}", self.counter.get())
26//!     }
27//! }
28//!
29//! let local_report: Report<LocalError, _, Local> = report!(LocalError {
30//!     counter: Cell::new(42)
31//! });
32//!
33//! // Preformat to make it Send + Sync
34//! let send_sync_report: Report<PreformattedContext, _, SendSync> = local_report.preformat();
35//!
36//! // Now it can be sent across threads
37//! // std::thread::spawn(move || { ... send_sync_report ... });
38//! ```
39
40use alloc::{format, string::String};
41use core::any::TypeId;
42
43use rootcause::{
44    ReportRef,
45    handlers::{
46        AttachmentFormattingStyle, AttachmentHandler, ContextFormattingStyle, ContextHandler,
47    },
48    report_attachment::ReportAttachmentRef,
49};
50
51/// A context that has been preformatted into `String`s for both
52/// [`Display`](core::fmt::Display) and [`Debug`](core::fmt::Debug).
53///
54/// This type stores the formatted output of a context along with metadata about
55/// the original type and preferred formatting styles. It's created
56/// automatically by [`PreformatReportExt::preformat`] and should not typically
57/// be constructed manually.
58///
59/// # Stored Information
60///
61/// - The original type's [`TypeId`] (accessible via [`original_type_id`])
62/// - Preformatted [`Display`](core::fmt::Display) output as a `String`
63/// - Preformatted [`Debug`](core::fmt::Debug) output as a `String`
64/// - Preferred formatting styles for both [`Display`](core::fmt::Display)
65///   and [`Debug`](core::fmt::Debug)
66///
67/// # Examples
68///
69/// ```
70/// use core::any::TypeId;
71///
72/// use rootcause::prelude::*;
73/// use rootcause_preformat::{PreformatReportExt, PreformattedContext};
74///
75/// #[derive(Debug)]
76/// struct MyError;
77/// impl core::fmt::Display for MyError {
78///     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
79///         write!(f, "my error")
80///     }
81/// }
82///
83/// let original: Report<MyError> = report!(MyError);
84/// let original_type_id = TypeId::of::<MyError>();
85///
86/// let preformatted: Report<PreformattedContext> = original.preformat();
87///
88/// // The preformatted context remembers its original type
89/// assert_eq!(
90///     preformatted.current_context().original_type_id(),
91///     original_type_id
92/// );
93/// ```
94///
95/// [`PreformatReportExt::preformat`]: crate::PreformatReportExt::preformat
96/// [`original_type_id`]: PreformattedContext::original_type_id
97/// [`TypeId`]: core::any::TypeId
98pub struct PreformattedContext {
99    original_type_id: TypeId,
100    display: String,
101    debug: String,
102    display_preferred_formatting_style: ContextFormattingStyle,
103    debug_preferred_formatting_style: ContextFormattingStyle,
104}
105
106impl PreformattedContext {
107    pub(crate) fn new_from_context<C: ?Sized, O, T>(report: ReportRef<'_, C, O, T>) -> Self {
108        Self {
109            original_type_id: report.current_context_type_id(),
110            display: format!("{}", report.format_current_context()),
111            debug: format!("{:?}", report.format_current_context()),
112            display_preferred_formatting_style: report.preferred_context_formatting_style(
113                rootcause::handlers::FormattingFunction::Display,
114            ),
115            debug_preferred_formatting_style: report
116                .preferred_context_formatting_style(rootcause::handlers::FormattingFunction::Debug),
117        }
118    }
119
120    /// Get the [`TypeId`] of the original context type before it was
121    /// preformatted.
122    ///
123    /// This can be useful for debugging or for implementing custom logic based
124    /// on the original type, even though the actual type has been erased.
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use core::any::TypeId;
130    ///
131    /// use rootcause::prelude::*;
132    /// use rootcause_preformat::{PreformatReportExt, PreformattedContext};
133    ///
134    /// #[derive(Debug)]
135    /// struct DatabaseError {
136    ///     code: i32,
137    /// }
138    ///
139    /// impl core::fmt::Display for DatabaseError {
140    ///     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141    ///         write!(f, "Database error: {}", self.code)
142    ///     }
143    /// }
144    ///
145    /// let report: Report<DatabaseError> = report!(DatabaseError { code: 404 });
146    /// let preformatted: Report<PreformattedContext> = report.preformat();
147    ///
148    /// // Even though the type is now PreformattedContext, we can still check
149    /// // what the original type was
150    /// assert_eq!(
151    ///     preformatted.current_context().original_type_id(),
152    ///     TypeId::of::<DatabaseError>()
153    /// );
154    /// ```
155    ///
156    /// [`TypeId`]: core::any::TypeId
157    pub fn original_type_id(&self) -> TypeId {
158        self.original_type_id
159    }
160}
161
162/// An attachment that has been preformatted into `String`s for both
163/// [`Display`](core::fmt::Display) and [`Debug`](core::fmt::Debug).
164///
165/// This type stores the formatted output of an attachment along with metadata
166/// about the original type and preferred formatting styles. It's created
167/// automatically by [`PreformatReportExt::preformat`] and should not typically
168/// be constructed manually.
169///
170/// # Stored Information
171///
172/// - The original type's [`TypeId`] (accessible via [`original_type_id`])
173/// - Preformatted [`Display`](core::fmt::Display) output as a `String`
174/// - Preformatted [`Debug`](core::fmt::Debug) output as a `String`
175/// - Preferred formatting styles for both [`Display`](core::fmt::Display)
176///   and [`Debug`](core::fmt::Debug)
177///
178/// # Examples
179///
180/// ```
181/// use rootcause::prelude::*;
182/// use rootcause_preformat::PreformatReportExt;
183///
184/// // When a report is preformatted, all attachments become PreformattedAttachment
185/// let report: Report = report!("error").attach("some data");
186/// let preformatted = report.preformat();
187///
188/// // All attachments in a preformatted report are PreformattedAttachment
189/// // They preserve information about their original types
190/// for attachment in preformatted.attachments().iter() {
191///     // Each attachment remembers its original type through original_type_id()
192///     let _original_type = attachment.inner_type_id();
193/// }
194/// ```
195///
196/// [`PreformatReportExt::preformat`]: crate::PreformatReportExt::preformat
197/// [`original_type_id`]: PreformattedAttachment::original_type_id
198/// [`TypeId`]: core::any::TypeId
199pub struct PreformattedAttachment {
200    original_type_id: TypeId,
201    display: String,
202    debug: String,
203    display_preferred_formatting_style: AttachmentFormattingStyle,
204    debug_preferred_formatting_style: AttachmentFormattingStyle,
205}
206
207impl PreformattedAttachment {
208    pub(crate) fn new_from_attachment<A>(attachment: ReportAttachmentRef<'_, A>) -> Self
209    where
210        A: ?Sized,
211    {
212        Self {
213            original_type_id: attachment.inner_type_id(),
214            display: format!("{}", attachment.format_inner()),
215            debug: format!("{:?}", attachment.format_inner()),
216            display_preferred_formatting_style: attachment
217                .preferred_formatting_style(rootcause::handlers::FormattingFunction::Display),
218            debug_preferred_formatting_style: attachment
219                .preferred_formatting_style(rootcause::handlers::FormattingFunction::Debug),
220        }
221    }
222
223    /// Get the [`TypeId`] of the original attachment type before it was
224    /// preformatted.
225    ///
226    /// This can be useful for debugging or for implementing custom logic based
227    /// on the original attachment type, even though the actual type has
228    /// been erased.
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// use rootcause::prelude::*;
234    /// use rootcause_preformat::{PreformatReportExt, PreformattedAttachment};
235    ///
236    /// let report: Report = report!("error").attach(42u32);
237    /// let preformatted = report.preformat();
238    ///
239    /// // After preformatting, all attachments are PreformattedAttachment
240    /// // They preserve the original type information
241    /// for attachment in preformatted.attachments().iter() {
242    ///     // Each PreformattedAttachment can tell you what type it originally was
243    ///     if let Some(preformatted) = attachment.downcast_inner::<PreformattedAttachment>() {
244    ///         let original_type = preformatted.original_type_id();
245    ///         // Can check if it was a specific type, e.g.:
246    ///         // if original_type == TypeId::of::<u32>() { ... }
247    ///     }
248    /// }
249    /// ```
250    ///
251    /// [`TypeId`]: core::any::TypeId
252    pub fn original_type_id(&self) -> TypeId {
253        self.original_type_id
254    }
255}
256
257/// Internal handler for preformatted contexts and attachments.
258///
259/// This handler is automatically registered for [`PreformattedContext`] and
260/// [`PreformattedAttachment`] types. It retrieves the pre-stored formatted
261/// strings rather than performing any formatting at display time.
262///
263/// [`Report::preformat`]: crate::Report::preformat
264pub(crate) struct PreformattedHandler;
265
266impl ContextHandler<PreformattedContext> for PreformattedHandler {
267    fn source(_value: &PreformattedContext) -> Option<&(dyn core::error::Error + 'static)> {
268        None
269    }
270
271    fn display(
272        value: &PreformattedContext,
273        formatter: &mut core::fmt::Formatter<'_>,
274    ) -> core::fmt::Result {
275        formatter.write_str(&value.display)
276    }
277
278    fn debug(
279        value: &PreformattedContext,
280        formatter: &mut core::fmt::Formatter<'_>,
281    ) -> core::fmt::Result {
282        formatter.write_str(&value.debug)
283    }
284
285    fn preferred_formatting_style(
286        value: &PreformattedContext,
287        report_formatting_function: rootcause::handlers::FormattingFunction,
288    ) -> ContextFormattingStyle {
289        match report_formatting_function {
290            rootcause::handlers::FormattingFunction::Display => {
291                value.display_preferred_formatting_style
292            }
293            rootcause::handlers::FormattingFunction::Debug => {
294                value.debug_preferred_formatting_style
295            }
296        }
297    }
298}
299
300impl AttachmentHandler<PreformattedAttachment> for PreformattedHandler {
301    fn display(
302        value: &PreformattedAttachment,
303        formatter: &mut core::fmt::Formatter<'_>,
304    ) -> core::fmt::Result {
305        formatter.write_str(&value.display)
306    }
307
308    fn debug(
309        value: &PreformattedAttachment,
310        formatter: &mut core::fmt::Formatter<'_>,
311    ) -> core::fmt::Result {
312        formatter.write_str(&value.debug)
313    }
314
315    fn preferred_formatting_style(
316        value: &PreformattedAttachment,
317        report_formatting_function: rootcause::handlers::FormattingFunction,
318    ) -> AttachmentFormattingStyle {
319        match report_formatting_function {
320            rootcause::handlers::FormattingFunction::Display => {
321                value.display_preferred_formatting_style
322            }
323            rootcause::handlers::FormattingFunction::Debug => {
324                value.debug_preferred_formatting_style
325            }
326        }
327    }
328}