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}