rootcause_internals/
handlers.rs

1//! Handlers used to implement or override the behavior of
2//! [`core::error::Error`], [`core::fmt::Display`] and [`core::fmt::Debug`] when
3//! creating an attachment or report.
4
5//! Handlers that define formatting and error-chaining behavior for reports and
6//! attachments.
7//!
8//! This module provides the core traits and types for implementing custom
9//! handlers that control how context objects and attachments are formatted and
10//! displayed in error reports.
11
12/// Trait for implementing custom formatting and error-chaining behavior for
13/// report contexts.
14///
15/// This trait defines how a context type should be formatted when displayed or
16/// debugged as part of an error report, and how to navigate to its error source
17/// (if any).
18///
19/// # When to Implement
20///
21/// You typically don't need to implement this trait directly. The rootcause
22/// library provides built-in handlers (`Error`, `Display`, `Debug`, `Any`) that
23/// cover most use cases.
24///
25/// Implement this trait when you need custom formatting behavior that the
26/// built-in handlers don't provide, such as:
27/// - Custom source chain navigation for types that don't implement
28///   `std::error::Error`
29/// - Special display formatting that differs from the type's `Display`
30///   implementation
31/// - Dynamic formatting based on the context value
32///
33/// # Required Methods
34///
35/// - [`source`](ContextHandler::source): Returns the underlying error source,
36///   if any
37/// - [`display`](ContextHandler::display): Formats the context for display
38///   output
39/// - [`debug`](ContextHandler::debug): Formats the context for debug output
40///
41/// # Optional Methods
42///
43/// - [`preferred_formatting_style`](ContextHandler::preferred_formatting_style):
44///   Specifies whether to use display or debug formatting when embedded in a report.
45///   The default implementation always prefers display formatting.
46///
47/// # Examples
48///
49/// ```rust
50/// use std::error::Error;
51///
52/// use rootcause_internals::handlers::{
53///     ContextFormattingStyle, ContextHandler, FormattingFunction,
54/// };
55///
56/// // Custom context type
57/// struct CustomError {
58///     message: String,
59///     code: i32,
60/// }
61///
62/// // Custom handler with special formatting
63/// struct CustomHandler;
64///
65/// impl ContextHandler<CustomError> for CustomHandler {
66///     fn source(_context: &CustomError) -> Option<&(dyn Error + 'static)> {
67///         None
68///     }
69///
70///     fn display(context: &CustomError, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71///         write!(f, "Error {}: {}", context.code, context.message)
72///     }
73///
74///     fn debug(context: &CustomError, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75///         write!(
76///             f,
77///             "CustomError {{ code: {}, message: {:?} }}",
78///             context.code, context.message
79///         )
80///     }
81/// }
82/// ```
83pub trait ContextHandler<C>: 'static {
84    /// Returns the underlying error source for this context, if any.
85    ///
86    /// This method enables error chain traversal, allowing you to navigate from
87    /// a context to its underlying cause. It's used when displaying the full
88    /// error chain in a report.
89    ///
90    /// # Returns
91    ///
92    /// - `Some(&dyn Error)` if this context has an underlying error source
93    /// - `None` if this context is a leaf in the error chain
94    ///
95    /// # Examples
96    ///
97    /// For types implementing `std::error::Error`, delegate to their `source`
98    /// method:
99    ///
100    /// ```rust
101    /// use std::error::Error;
102    ///
103    /// use rootcause_internals::handlers::ContextHandler;
104    ///
105    /// struct ErrorHandler;
106    ///
107    /// impl<C: Error> ContextHandler<C> for ErrorHandler {
108    ///     fn source(context: &C) -> Option<&(dyn Error + 'static)> {
109    ///         context.source()
110    ///     }
111    /// #   fn display(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112    /// #       write!(f, "{}", context)
113    /// #   }
114    /// #   fn debug(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115    /// #       write!(f, "{:?}", context)
116    /// #   }
117    /// }
118    /// ```
119    fn source(value: &C) -> Option<&(dyn core::error::Error + 'static)>;
120
121    /// Formats the context using display-style formatting.
122    ///
123    /// This method is called when the context needs to be displayed as part of
124    /// an error report. It should produce human-readable output suitable for
125    /// end users.
126    ///
127    /// # Examples
128    ///
129    /// ```rust
130    /// use rootcause_internals::handlers::ContextHandler;
131    ///
132    /// struct DisplayHandler;
133    ///
134    /// impl<C: std::fmt::Display + std::fmt::Debug> ContextHandler<C> for DisplayHandler {
135    ///     fn source(_context: &C) -> Option<&(dyn std::error::Error + 'static)> {
136    ///         None
137    ///     }
138    ///
139    ///     fn display(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140    ///         std::fmt::Display::fmt(context, f)
141    ///     }
142    /// #   fn debug(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143    /// #       std::fmt::Debug::fmt(context, f)
144    /// #   }
145    /// }
146    /// ```
147    fn display(value: &C, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
148
149    /// Formats the context using debug-style formatting.
150    ///
151    /// This method is called when the context needs to be debug-formatted. It
152    /// should produce detailed output suitable for developers, potentially
153    /// including internal state and implementation details.
154    ///
155    /// # Examples
156    ///
157    /// ```rust
158    /// use rootcause_internals::handlers::ContextHandler;
159    ///
160    /// struct DebugHandler;
161    ///
162    /// impl<C: std::fmt::Debug> ContextHandler<C> for DebugHandler {
163    ///     fn source(_context: &C) -> Option<&(dyn std::error::Error + 'static)> {
164    ///         None
165    ///     }
166    ///
167    ///     fn display(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168    ///         write!(f, "Context of type `{}`", core::any::type_name::<C>())
169    ///     }
170    ///
171    ///     fn debug(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172    ///         std::fmt::Debug::fmt(context, f)
173    ///     }
174    /// }
175    /// ```
176    fn debug(value: &C, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
177
178    /// Specifies the preferred formatting style when this context is embedded
179    /// in a report.
180    ///
181    /// This method allows the handler to choose between display and debug
182    /// formatting based on how the report itself is being formatted. The
183    /// default implementation always returns
184    /// [`FormattingFunction::Display`], meaning the context will use
185    /// its [`display`](ContextHandler::display) method even when the report is
186    /// being debug-formatted.
187    ///
188    /// # Arguments
189    ///
190    /// - `value`: The context value
191    /// - `report_formatting_function`: How the report itself is being formatted
192    ///   ([`Display`](core::fmt::Display) or [`Debug`](core::fmt::Debug))
193    ///
194    /// # Default Behavior
195    ///
196    /// The default implementation ignores the report's formatting style and
197    /// always uses display formatting. This is the behavior of all built-in
198    /// handlers.
199    ///
200    /// # Examples
201    ///
202    /// Custom handler that mirrors the report's formatting:
203    ///
204    /// ```rust
205    /// use rootcause_internals::handlers::{
206    ///     ContextFormattingStyle, ContextHandler, FormattingFunction,
207    /// };
208    ///
209    /// struct MirrorHandler;
210    ///
211    /// impl<C: std::fmt::Display + std::fmt::Debug> ContextHandler<C> for MirrorHandler {
212    ///     fn source(_context: &C) -> Option<&(dyn std::error::Error + 'static)> {
213    ///         None
214    ///     }
215    ///
216    ///     fn display(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217    ///         std::fmt::Display::fmt(context, f)
218    ///     }
219    ///
220    ///     fn debug(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221    ///         std::fmt::Debug::fmt(context, f)
222    ///     }
223    ///
224    ///     fn preferred_formatting_style(
225    ///         _value: &C,
226    ///         report_formatting_function: FormattingFunction,
227    ///     ) -> ContextFormattingStyle {
228    ///         // Use the same formatting as the report
229    ///         ContextFormattingStyle {
230    ///             function: report_formatting_function,
231    ///         }
232    ///     }
233    /// }
234    /// ```
235    fn preferred_formatting_style(
236        value: &C,
237        report_formatting_function: FormattingFunction,
238    ) -> ContextFormattingStyle {
239        let _ = (value, report_formatting_function);
240        ContextFormattingStyle::default()
241    }
242}
243
244/// Trait for implementing custom formatting behavior for report attachments.
245///
246/// This trait defines how an attachment type should be formatted when displayed
247/// or debugged as part of an error report. Unlike [`ContextHandler`], this
248/// trait also allows specifying placement preferences (inline vs appendix).
249///
250/// # When to Implement
251///
252/// You typically don't need to implement this trait directly. The rootcause
253/// library provides built-in handlers that cover most use cases. Implement this
254/// trait when you need:
255/// - Custom formatting for attachment types
256/// - Special placement logic (e.g., large data in appendices)
257/// - Dynamic formatting based on attachment content
258///
259/// # Required Methods
260///
261/// - [`display`](AttachmentHandler::display): Formats the attachment for
262///   display output
263/// - [`debug`](AttachmentHandler::debug): Formats the attachment for debug
264///   output
265///
266/// # Optional Methods
267///
268/// - [`preferred_formatting_style`](AttachmentHandler::preferred_formatting_style):
269///   Specifies formatting preferences including placement (inline/appendix) and
270///   whether to use display or debug formatting. The default implementation prefers
271///   inline display formatting.
272///
273/// # Examples
274///
275/// ```rust
276/// use rootcause_internals::handlers::{
277///     AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler,
278///     FormattingFunction,
279/// };
280///
281/// // Attachment type with potentially large data
282/// struct LargeData {
283///     records: Vec<String>,
284/// }
285///
286/// // Handler that moves large attachments to appendix
287/// struct LargeDataHandler;
288///
289/// impl AttachmentHandler<LargeData> for LargeDataHandler {
290///     fn display(attachment: &LargeData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291///         write!(f, "{} records", attachment.records.len())
292///     }
293///
294///     fn debug(attachment: &LargeData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295///         write!(f, "LargeData {{ {} records }}", attachment.records.len())
296///     }
297///
298///     fn preferred_formatting_style(
299///         attachment: &LargeData,
300///         _report_formatting: FormattingFunction,
301///     ) -> AttachmentFormattingStyle {
302///         if attachment.records.len() > 10 {
303///             // Large data goes to appendix
304///             AttachmentFormattingStyle {
305///                 placement: AttachmentFormattingPlacement::Appendix {
306///                     appendix_name: "Large Data Records",
307///                 },
308///                 function: FormattingFunction::Display,
309///                 priority: 0,
310///             }
311///         } else {
312///             // Small data shows inline
313///             AttachmentFormattingStyle::default()
314///         }
315///     }
316/// }
317/// ```
318pub trait AttachmentHandler<A>: 'static {
319    /// Formats the attachment using display-style formatting.
320    ///
321    /// This method is called when the attachment needs to be displayed as part
322    /// of an error report. It should produce human-readable output suitable
323    /// for end users.
324    ///
325    /// # Examples
326    ///
327    /// ```rust
328    /// use rootcause_internals::handlers::AttachmentHandler;
329    ///
330    /// struct ConfigData {
331    ///     key: String,
332    ///     value: String,
333    /// }
334    ///
335    /// struct ConfigHandler;
336    ///
337    /// impl AttachmentHandler<ConfigData> for ConfigHandler {
338    ///     fn display(attachment: &ConfigData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339    ///         write!(f, "{} = {}", attachment.key, attachment.value)
340    ///     }
341    /// #   fn debug(attachment: &ConfigData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342    /// #       write!(f, "ConfigData {{ key: {:?}, value: {:?} }}", attachment.key, attachment.value)
343    /// #   }
344    /// }
345    /// ```
346    fn display(value: &A, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
347
348    /// Formats the attachment using debug-style formatting.
349    ///
350    /// This method is called when the attachment needs to be debug-formatted.
351    /// It should produce detailed output suitable for developers.
352    fn debug(value: &A, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
353
354    /// Specifies the preferred formatting style and placement for this
355    /// attachment.
356    ///
357    /// This method allows the handler to control:
358    /// - **Placement**: Whether the attachment appears inline, in an appendix,
359    ///   or is hidden
360    /// - **Formatting**: Whether to use display or debug formatting
361    /// - **Priority**: The order in which attachments are displayed (higher =
362    ///   earlier)
363    ///
364    /// The default implementation returns inline display formatting with
365    /// priority 0.
366    ///
367    /// # Examples
368    ///
369    /// Attachment that hides sensitive data:
370    ///
371    /// ```rust
372    /// use rootcause_internals::handlers::{
373    ///     AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler,
374    ///     FormattingFunction,
375    /// };
376    ///
377    /// struct ApiKey(String);
378    ///
379    /// struct SecureHandler;
380    ///
381    /// impl AttachmentHandler<ApiKey> for SecureHandler {
382    ///     fn display(_attachment: &ApiKey, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383    ///         write!(f, "[REDACTED]")
384    ///     }
385    ///
386    ///     fn debug(_attachment: &ApiKey, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387    ///         write!(f, "ApiKey([REDACTED])")
388    ///     }
389    ///
390    ///     fn preferred_formatting_style(
391    ///         _attachment: &ApiKey,
392    ///         _report_formatting: FormattingFunction,
393    ///     ) -> AttachmentFormattingStyle {
394    ///         // Hide this attachment completely in production
395    ///         AttachmentFormattingStyle {
396    ///             placement: AttachmentFormattingPlacement::Hidden,
397    ///             function: FormattingFunction::Display,
398    ///             priority: 0,
399    ///         }
400    ///     }
401    /// }
402    /// ```
403    fn preferred_formatting_style(
404        value: &A,
405        report_formatting_function: FormattingFunction,
406    ) -> AttachmentFormattingStyle {
407        let _ = (value, report_formatting_function);
408        AttachmentFormattingStyle::default()
409    }
410}
411
412/// Formatting preferences for a context when displayed in a report.
413///
414/// This struct allows a [`ContextHandler`] to specify how it prefers to be
415/// formatted when its context is displayed as part of an error report. The
416/// formatting system may or may not respect these preferences depending on the
417/// formatter implementation.
418///
419/// # Fields
420///
421/// - `function`: Whether to use [`Display`](core::fmt::Display) or
422///   [`Debug`](core::fmt::Debug) formatting
423///
424/// # Default
425///
426/// The default is to use [`FormattingFunction::Display`].
427///
428/// # Examples
429///
430/// ```rust
431/// use rootcause_internals::handlers::{ContextFormattingStyle, FormattingFunction};
432///
433/// // Prefer display formatting (the default)
434/// let style = ContextFormattingStyle::default();
435/// assert_eq!(style.function, FormattingFunction::Display);
436///
437/// // Explicitly request debug formatting
438/// let debug_style = ContextFormattingStyle {
439///     function: FormattingFunction::Debug,
440/// };
441/// ```
442#[derive(Copy, Clone, Debug, Default)]
443pub struct ContextFormattingStyle {
444    /// The preferred formatting function to use
445    pub function: FormattingFunction,
446}
447
448/// Formatting preferences for an attachment when displayed in a report.
449///
450/// This struct allows an [`AttachmentHandler`] to specify how and where it
451/// prefers to be displayed when included in an error report. The formatting
452/// system may or may not respect these preferences depending on the formatter
453/// implementation.
454///
455/// # Fields
456///
457/// - `placement`: Where the attachment should appear (inline, appendix, hidden,
458///   etc.)
459/// - `function`: Whether to use [`Display`](core::fmt::Display) or
460///   [`Debug`](core::fmt::Debug) formatting
461/// - `priority`: Display order preference (higher values appear earlier)
462///
463/// # Default
464///
465/// The default is inline display formatting with priority 0.
466///
467/// # Examples
468///
469/// ```rust
470/// use rootcause_internals::handlers::{
471///     AttachmentFormattingPlacement, AttachmentFormattingStyle, FormattingFunction,
472/// };
473///
474/// // Default: inline display formatting
475/// let style = AttachmentFormattingStyle::default();
476/// assert_eq!(style.placement, AttachmentFormattingPlacement::Inline);
477/// assert_eq!(style.function, FormattingFunction::Display);
478/// assert_eq!(style.priority, 0);
479///
480/// // High-priority attachment in appendix
481/// let appendix_style = AttachmentFormattingStyle {
482///     placement: AttachmentFormattingPlacement::Appendix {
483///         appendix_name: "Stack Trace",
484///     },
485///     function: FormattingFunction::Debug,
486///     priority: 10,
487/// };
488/// ```
489#[derive(Copy, Clone, Debug, Default)]
490pub struct AttachmentFormattingStyle {
491    /// The preferred attachment placement
492    pub placement: AttachmentFormattingPlacement,
493    /// The preferred formatting function to use
494    pub function: FormattingFunction,
495    /// The preferred formatting priority. Higher priority means
496    /// a preference for being printed earlier in the report
497    pub priority: i32,
498}
499
500/// Specifies whether to use display or debug formatting for a context or
501/// attachment.
502///
503/// This enum is used by handlers to indicate their formatting preference when
504/// a context or attachment is displayed as part of an error report. The actual
505/// formatting system may or may not respect this preference.
506///
507/// # Variants
508///
509/// - **`Display`** (default): Use the `display` method
510/// - **`Debug`**: Use the `debug` method
511///
512/// # Examples
513///
514/// ```rust
515/// use rootcause_internals::handlers::FormattingFunction;
516///
517/// let display_formatting = FormattingFunction::Display;
518/// let debug_formatting = FormattingFunction::Debug;
519///
520/// // Display is the default
521/// assert_eq!(FormattingFunction::default(), FormattingFunction::Display);
522/// ```
523#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
524pub enum FormattingFunction {
525    /// Prefer display formatting via the `display` method.
526    #[default]
527    Display,
528    /// Prefer debug formatting via the `debug` method.
529    Debug,
530}
531
532/// Specifies where an attachment should be placed when displayed in a report.
533///
534/// This enum allows attachments to indicate their preferred placement in error
535/// reports. Different placements are suitable for different types of content:
536///
537/// - **Inline**: Short, contextual information that flows with the error
538///   message
539/// - **InlineWithHeader**: Multi-line content that needs a header for clarity
540/// - **Appendix**: Large or detailed content better suited to a separate
541///   section
542/// - **Opaque**: Content that shouldn't be shown but should be counted
543/// - **Hidden**: Content that shouldn't appear at all
544///
545/// The actual formatting system may or may not respect these preferences
546/// depending on the implementation.
547///
548/// # Examples
549///
550/// ```rust
551/// use rootcause_internals::handlers::AttachmentFormattingPlacement;
552///
553/// // Default is inline
554/// let inline = AttachmentFormattingPlacement::default();
555/// assert_eq!(inline, AttachmentFormattingPlacement::Inline);
556///
557/// // Attachment with header
558/// let with_header = AttachmentFormattingPlacement::InlineWithHeader {
559///     header: "Request Details",
560/// };
561///
562/// // Large content in appendix
563/// let appendix = AttachmentFormattingPlacement::Appendix {
564///     appendix_name: "Full Stack Trace",
565/// };
566///
567/// // Sensitive data that should be hidden
568/// let hidden = AttachmentFormattingPlacement::Hidden;
569/// ```
570#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
571pub enum AttachmentFormattingPlacement {
572    /// Display the attachment inline with the error message.
573    ///
574    /// Suitable for short, contextual information that naturally flows with the
575    /// error text. This is the default placement.
576    #[default]
577    Inline,
578
579    /// Display the attachment inline but preceded by a header.
580    ///
581    /// Useful for multi-line content that benefits from a descriptive header,
582    /// such as configuration snippets or multi-field data structures.
583    InlineWithHeader {
584        /// The header text to display above the attachment
585        header: &'static str,
586    },
587
588    /// Display the attachment in a separate appendix section.
589    ///
590    /// Suitable for large or detailed content that would disrupt the flow of
591    /// the main error message, such as full stack traces, large data dumps,
592    /// or detailed diagnostic information.
593    Appendix {
594        /// The name of the appendix section for this attachment
595        appendix_name: &'static str,
596    },
597
598    /// Don't display the attachment, but count it in a summary.
599    ///
600    /// The attachment won't be shown directly, but may appear in a message like
601    /// "3 additional opaque attachments". Useful for numerous low-priority
602    /// attachments that would clutter the output.
603    Opaque,
604
605    /// Don't display the attachment at all.
606    ///
607    /// The attachment is completely hidden and won't appear in any form. Useful
608    /// for sensitive data that should be excluded from error reports, or for
609    /// attachments meant only for programmatic access.
610    Hidden,
611}