error_support/
handling.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! Helpers for components to "handle" errors.
6
7/// Describes what error reporting action should be taken.
8#[derive(Debug, Default)]
9pub struct ErrorReporting {
10    /// If Some(level), will write a log message at that level.
11    log_level: Option<crate::Level>,
12    /// If Some(report_class) will call the error reporter with details.
13    report_class: Option<String>,
14}
15
16/// Specifies how an "internal" error is converted to an "external" public error and
17/// any logging or reporting that should happen.
18pub struct ErrorHandling<E> {
19    /// The external error that should be returned.
20    pub err: E,
21    /// How the error should be reported.
22    pub reporting: ErrorReporting,
23}
24
25impl<E> ErrorHandling<E> {
26    /// Create an ErrorHandling instance with an error conversion.
27    ///
28    /// ErrorHandling instance are created using a builder-style API.  This is always the first
29    /// function in the chain, optionally followed by `log()`, `report()`, etc.
30    pub fn convert(err: E) -> Self {
31        Self {
32            err,
33            reporting: ErrorReporting::default(),
34        }
35    }
36
37    /// Add logging to an ErrorHandling instance
38    pub fn log(self, level: crate::Level) -> Self {
39        Self {
40            err: self.err,
41            reporting: ErrorReporting {
42                log_level: Some(level),
43                ..self.reporting
44            },
45        }
46    }
47
48    /// Add reporting to an ErrorHandling instance
49    pub fn report(self, report_class: impl Into<String>) -> Self {
50        Self {
51            err: self.err,
52            reporting: ErrorReporting {
53                report_class: Some(report_class.into()),
54                ..self.reporting
55            },
56        }
57    }
58
59    // Convenience functions for the most common error reports
60
61    /// log a warning
62    pub fn log_warning(self) -> Self {
63        self.log(crate::Level::Warn)
64    }
65
66    /// log an info
67    pub fn log_info(self) -> Self {
68        self.log(crate::Level::Info)
69    }
70
71    /// Add reporting to an ErrorHandling instance and also log an Error
72    pub fn report_error(self, report_class: impl Into<String>) -> Self {
73        Self {
74            err: self.err,
75            reporting: ErrorReporting {
76                log_level: Some(crate::Level::Error),
77                report_class: Some(report_class.into()),
78            },
79        }
80    }
81}
82
83/// A trait to define how errors are converted and reported.
84pub trait GetErrorHandling {
85    type ExternalError;
86
87    /// Return how to handle our internal errors
88    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>;
89}
90
91/// Handle the specified "internal" error, taking any logging or error
92/// reporting actions and converting the error to the public error.
93/// Called by our `handle_error` macro so needs to be public.
94pub fn convert_log_report_error<IE, EE>(e: IE) -> EE
95where
96    IE: GetErrorHandling<ExternalError = EE> + std::error::Error,
97    EE: std::error::Error,
98{
99    let handling = e.get_error_handling();
100    let reporting = handling.reporting;
101    if let Some(level) = reporting.log_level {
102        // tracing dosn't seem to have anything close enough to `log::log`, so we have to match levels explicitly.
103        let message = match &reporting.report_class {
104            Some(report_class) => format!("{report_class}: {}", e),
105            None => format!("{}", e),
106        };
107        match level {
108            crate::Level::Trace => crate::trace!("{}", message),
109            crate::Level::Debug => crate::debug!("{}", message),
110            crate::Level::Info => crate::info!("{}", message),
111            crate::Level::Warn => crate::warn!("{}", message),
112            crate::Level::Error => crate::error!("{}", message),
113        }
114    }
115    if let Some(report_class) = reporting.report_class {
116        // notify the error reporter if the feature is enabled.
117        // XXX - should we arrange for the `report_class` to have the
118        // original crate calling this as a prefix, or will we still be
119        // able to identify that?
120        crate::report_error_to_app(report_class, e.to_string());
121    }
122    handling.err
123}