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