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///   `std::error::Error`
25/// - Special display formatting that differs from the type's `Display`
26///   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 always prefers display formatting.
42///
43/// # Examples
44///
45/// ```rust
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 `std::error::Error`, delegate to their `source`
94    /// method:
95    ///
96    /// ```rust
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    /// ```rust
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    /// ```rust
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 choose between display and debug
178    /// formatting based on how the report itself is being formatted. The
179    /// default implementation always returns
180    /// [`FormattingFunction::Display`], meaning the context will use
181    /// its [`display`](ContextHandler::display) method even when the report is
182    /// being debug-formatted.
183    ///
184    /// # Arguments
185    ///
186    /// - `value`: The context value
187    /// - `report_formatting_function`: How the report itself is being formatted
188    ///   ([`Display`](core::fmt::Display) or [`Debug`](core::fmt::Debug))
189    ///
190    /// # Default Behavior
191    ///
192    /// The default implementation ignores the report's formatting style and
193    /// always uses display formatting. This is the behavior of all built-in
194    /// handlers.
195    ///
196    /// # Examples
197    ///
198    /// Custom handler that mirrors the report's formatting:
199    ///
200    /// ```rust
201    /// use rootcause_internals::handlers::{
202    ///     ContextFormattingStyle, ContextHandler, FormattingFunction,
203    /// };
204    ///
205    /// struct MirrorHandler;
206    ///
207    /// impl<C: std::fmt::Display + std::fmt::Debug> ContextHandler<C> for MirrorHandler {
208    ///     fn source(_context: &C) -> Option<&(dyn std::error::Error + 'static)> {
209    ///         None
210    ///     }
211    ///
212    ///     fn display(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213    ///         std::fmt::Display::fmt(context, f)
214    ///     }
215    ///
216    ///     fn debug(context: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217    ///         std::fmt::Debug::fmt(context, f)
218    ///     }
219    ///
220    ///     fn preferred_formatting_style(
221    ///         _value: &C,
222    ///         report_formatting_function: FormattingFunction,
223    ///     ) -> ContextFormattingStyle {
224    ///         // Use the same formatting as the report
225    ///         ContextFormattingStyle {
226    ///             function: report_formatting_function,
227    ///         }
228    ///     }
229    /// }
230    /// ```
231    fn preferred_formatting_style(
232        value: &C,
233        report_formatting_function: FormattingFunction,
234    ) -> ContextFormattingStyle {
235        let _ = (value, report_formatting_function);
236        ContextFormattingStyle::default()
237    }
238}
239
240/// Trait for implementing custom formatting behavior for report attachments.
241///
242/// This trait defines how an attachment type should be formatted when displayed
243/// or debugged as part of an error report. Unlike [`ContextHandler`], this
244/// trait also allows specifying placement preferences (inline vs appendix).
245///
246/// # When to Implement
247///
248/// You typically don't need to implement this trait directly. The rootcause
249/// library provides built-in handlers that cover most use cases. Implement this
250/// trait when you need:
251/// - Custom formatting for attachment types
252/// - Special placement logic (e.g., large data in appendices)
253/// - Dynamic formatting based on attachment content
254///
255/// # Required Methods
256///
257/// - [`display`](AttachmentHandler::display): Formats the attachment for
258///   display output
259/// - [`debug`](AttachmentHandler::debug): Formats the attachment for debug
260///   output
261///
262/// # Optional Methods
263///
264/// - [`preferred_formatting_style`](AttachmentHandler::preferred_formatting_style):
265///   Specifies formatting preferences including placement (inline/appendix) and
266///   whether to use display or debug formatting. The default implementation prefers
267///   inline display formatting.
268///
269/// # Examples
270///
271/// ```rust
272/// use rootcause_internals::handlers::{
273///     AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler,
274///     FormattingFunction,
275/// };
276///
277/// // Attachment type with potentially large data
278/// struct LargeData {
279///     records: Vec<String>,
280/// }
281///
282/// // Handler that moves large attachments to appendix
283/// struct LargeDataHandler;
284///
285/// impl AttachmentHandler<LargeData> for LargeDataHandler {
286///     fn display(attachment: &LargeData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287///         write!(f, "{} records", attachment.records.len())
288///     }
289///
290///     fn debug(attachment: &LargeData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291///         write!(f, "LargeData {{ {} records }}", attachment.records.len())
292///     }
293///
294///     fn preferred_formatting_style(
295///         attachment: &LargeData,
296///         _report_formatting: FormattingFunction,
297///     ) -> AttachmentFormattingStyle {
298///         if attachment.records.len() > 10 {
299///             // Large data goes to appendix
300///             AttachmentFormattingStyle {
301///                 placement: AttachmentFormattingPlacement::Appendix {
302///                     appendix_name: "Large Data Records",
303///                 },
304///                 function: FormattingFunction::Display,
305///                 priority: 0,
306///             }
307///         } else {
308///             // Small data shows inline
309///             AttachmentFormattingStyle::default()
310///         }
311///     }
312/// }
313/// ```
314pub trait AttachmentHandler<A>: 'static {
315    /// Formats the attachment using display-style formatting.
316    ///
317    /// This method is called when the attachment needs to be displayed as part
318    /// of an error report. It should produce human-readable output suitable
319    /// for end users.
320    ///
321    /// # Examples
322    ///
323    /// ```rust
324    /// use rootcause_internals::handlers::AttachmentHandler;
325    ///
326    /// struct ConfigData {
327    ///     key: String,
328    ///     value: String,
329    /// }
330    ///
331    /// struct ConfigHandler;
332    ///
333    /// impl AttachmentHandler<ConfigData> for ConfigHandler {
334    ///     fn display(attachment: &ConfigData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335    ///         write!(f, "{} = {}", attachment.key, attachment.value)
336    ///     }
337    /// #   fn debug(attachment: &ConfigData, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338    /// #       write!(f, "ConfigData {{ key: {:?}, value: {:?} }}", attachment.key, attachment.value)
339    /// #   }
340    /// }
341    /// ```
342    fn display(value: &A, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
343
344    /// Formats the attachment using debug-style formatting.
345    ///
346    /// This method is called when the attachment needs to be debug-formatted.
347    /// It should produce detailed output suitable for developers.
348    fn debug(value: &A, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result;
349
350    /// Specifies the preferred formatting style and placement for this
351    /// attachment.
352    ///
353    /// This method allows the handler to control:
354    /// - **Placement**: Whether the attachment appears inline, in an appendix,
355    ///   or is hidden
356    /// - **Formatting**: Whether to use display or debug formatting
357    /// - **Priority**: The order in which attachments are displayed (higher =
358    ///   earlier)
359    ///
360    /// The default implementation returns inline display formatting with
361    /// priority 0.
362    ///
363    /// # Examples
364    ///
365    /// Attachment that hides sensitive data:
366    ///
367    /// ```rust
368    /// use rootcause_internals::handlers::{
369    ///     AttachmentFormattingPlacement, AttachmentFormattingStyle, AttachmentHandler,
370    ///     FormattingFunction,
371    /// };
372    ///
373    /// struct ApiKey(String);
374    ///
375    /// struct SecureHandler;
376    ///
377    /// impl AttachmentHandler<ApiKey> for SecureHandler {
378    ///     fn display(_attachment: &ApiKey, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379    ///         write!(f, "[REDACTED]")
380    ///     }
381    ///
382    ///     fn debug(_attachment: &ApiKey, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383    ///         write!(f, "ApiKey([REDACTED])")
384    ///     }
385    ///
386    ///     fn preferred_formatting_style(
387    ///         _attachment: &ApiKey,
388    ///         _report_formatting: FormattingFunction,
389    ///     ) -> AttachmentFormattingStyle {
390    ///         // Hide this attachment completely in production
391    ///         AttachmentFormattingStyle {
392    ///             placement: AttachmentFormattingPlacement::Hidden,
393    ///             function: FormattingFunction::Display,
394    ///             priority: 0,
395    ///         }
396    ///     }
397    /// }
398    /// ```
399    fn preferred_formatting_style(
400        value: &A,
401        report_formatting_function: FormattingFunction,
402    ) -> AttachmentFormattingStyle {
403        let _ = (value, report_formatting_function);
404        AttachmentFormattingStyle::default()
405    }
406}
407
408/// Formatting preferences for a context when displayed in a report.
409///
410/// This struct allows a [`ContextHandler`] to specify how it prefers to be
411/// formatted when its context is displayed as part of an error report. The
412/// formatting system may or may not respect these preferences depending on the
413/// formatter implementation.
414///
415/// # Fields
416///
417/// - `function`: Whether to use [`Display`](core::fmt::Display) or
418///   [`Debug`](core::fmt::Debug) formatting
419///
420/// # Default
421///
422/// The default is to use [`FormattingFunction::Display`].
423///
424/// # Examples
425///
426/// ```rust
427/// use rootcause_internals::handlers::{ContextFormattingStyle, FormattingFunction};
428///
429/// // Prefer display formatting (the default)
430/// let style = ContextFormattingStyle::default();
431/// assert_eq!(style.function, FormattingFunction::Display);
432///
433/// // Explicitly request debug formatting
434/// let debug_style = ContextFormattingStyle {
435///     function: FormattingFunction::Debug,
436/// };
437/// ```
438#[derive(Copy, Clone, Debug, Default)]
439pub struct ContextFormattingStyle {
440    /// The preferred formatting function to use
441    pub function: FormattingFunction,
442}
443
444/// Formatting preferences for an attachment when displayed in a report.
445///
446/// This struct allows an [`AttachmentHandler`] to specify how and where it
447/// prefers to be displayed when included in an error report. The formatting
448/// system may or may not respect these preferences depending on the formatter
449/// implementation.
450///
451/// # Fields
452///
453/// - `placement`: Where the attachment should appear (inline, appendix, hidden,
454///   etc.)
455/// - `function`: Whether to use [`Display`](core::fmt::Display) or
456///   [`Debug`](core::fmt::Debug) formatting
457/// - `priority`: Display order preference (higher values appear earlier)
458///
459/// # Default
460///
461/// The default is inline display formatting with priority 0.
462///
463/// # Examples
464///
465/// ```rust
466/// use rootcause_internals::handlers::{
467///     AttachmentFormattingPlacement, AttachmentFormattingStyle, FormattingFunction,
468/// };
469///
470/// // Default: inline display formatting
471/// let style = AttachmentFormattingStyle::default();
472/// assert_eq!(style.placement, AttachmentFormattingPlacement::Inline);
473/// assert_eq!(style.function, FormattingFunction::Display);
474/// assert_eq!(style.priority, 0);
475///
476/// // High-priority attachment in appendix
477/// let appendix_style = AttachmentFormattingStyle {
478///     placement: AttachmentFormattingPlacement::Appendix {
479///         appendix_name: "Stack Trace",
480///     },
481///     function: FormattingFunction::Debug,
482///     priority: 10,
483/// };
484/// ```
485#[derive(Copy, Clone, Debug, Default)]
486pub struct AttachmentFormattingStyle {
487    /// The preferred attachment placement
488    pub placement: AttachmentFormattingPlacement,
489    /// The preferred formatting function to use
490    pub function: FormattingFunction,
491    /// The preferred formatting priority. Higher priority means
492    /// a preference for being printed earlier in the report
493    pub priority: i32,
494}
495
496/// Specifies whether to use display or debug formatting for a context or
497/// attachment.
498///
499/// This enum is used by handlers to indicate their formatting preference when
500/// a context or attachment is displayed as part of an error report. The actual
501/// formatting system may or may not respect this preference.
502///
503/// # Variants
504///
505/// - **`Display`** (default): Use the `display` method
506/// - **`Debug`**: Use the `debug` method
507///
508/// # Examples
509///
510/// ```rust
511/// use rootcause_internals::handlers::FormattingFunction;
512///
513/// let display_formatting = FormattingFunction::Display;
514/// let debug_formatting = FormattingFunction::Debug;
515///
516/// // Display is the default
517/// assert_eq!(FormattingFunction::default(), FormattingFunction::Display);
518/// ```
519#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
520pub enum FormattingFunction {
521    /// Prefer display formatting via the `display` method.
522    #[default]
523    Display,
524    /// Prefer debug formatting via the `debug` method.
525    Debug,
526}
527
528/// Specifies where an attachment should be placed when displayed in a report.
529///
530/// This enum allows attachments to indicate their preferred placement in error
531/// reports. Different placements are suitable for different types of content:
532///
533/// - **Inline**: Short, contextual information that flows with the error
534///   message
535/// - **InlineWithHeader**: Multi-line content that needs a header for clarity
536/// - **Appendix**: Large or detailed content better suited to a separate
537///   section
538/// - **Opaque**: Content that shouldn't be shown but should be counted
539/// - **Hidden**: Content that shouldn't appear at all
540///
541/// The actual formatting system may or may not respect these preferences
542/// depending on the implementation.
543///
544/// # Examples
545///
546/// ```rust
547/// use rootcause_internals::handlers::AttachmentFormattingPlacement;
548///
549/// // Default is inline
550/// let inline = AttachmentFormattingPlacement::default();
551/// assert_eq!(inline, AttachmentFormattingPlacement::Inline);
552///
553/// // Attachment with header
554/// let with_header = AttachmentFormattingPlacement::InlineWithHeader {
555///     header: "Request Details",
556/// };
557///
558/// // Large content in appendix
559/// let appendix = AttachmentFormattingPlacement::Appendix {
560///     appendix_name: "Full Stack Trace",
561/// };
562///
563/// // Sensitive data that should be hidden
564/// let hidden = AttachmentFormattingPlacement::Hidden;
565/// ```
566#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
567pub enum AttachmentFormattingPlacement {
568    /// Display the attachment inline with the error message.
569    ///
570    /// Suitable for short, contextual information that naturally flows with the
571    /// error text. This is the default placement.
572    #[default]
573    Inline,
574
575    /// Display the attachment inline but preceded by a header.
576    ///
577    /// Useful for multi-line content that benefits from a descriptive header,
578    /// such as configuration snippets or multi-field data structures.
579    InlineWithHeader {
580        /// The header text to display above the attachment
581        header: &'static str,
582    },
583
584    /// Display the attachment in a separate appendix section.
585    ///
586    /// Suitable for large or detailed content that would disrupt the flow of
587    /// the main error message, such as full stack traces, large data dumps,
588    /// or detailed diagnostic information.
589    Appendix {
590        /// The name of the appendix section for this attachment
591        appendix_name: &'static str,
592    },
593
594    /// Don't display the attachment, but count it in a summary.
595    ///
596    /// The attachment won't be shown directly, but may appear in a message like
597    /// "3 additional opaque attachments". Useful for numerous low-priority
598    /// attachments that would clutter the output.
599    Opaque,
600
601    /// Don't display the attachment at all.
602    ///
603    /// The attachment is completely hidden and won't appear in any form. Useful
604    /// for sensitive data that should be excluded from error reports, or for
605    /// attachments meant only for programmatic access.
606    Hidden,
607}