Skip to main content

rootcause_internals/
handlers.rs

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