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}