Skip to main content

veil_engine/
error.rs

1//! Errors from redacting and scanning.
2
3use std::error::Error as StdError;
4use std::fmt;
5
6/// Why a redaction or scan failed. Callers should not send the text on: a
7/// failed redaction means sensitive values may still be in it.
8#[derive(Debug, thiserror::Error)]
9#[non_exhaustive]
10pub enum Error {
11    /// The rules turn name detection on, but no [`EntityRecognizer`](crate::EntityRecognizer)
12    /// was given. The engine fails rather than send names unredacted.
13    #[error("name detection is on, but no name-detection model was given")]
14    RecognizerMissing,
15    /// The name-detection model failed.
16    #[error("name detection failed: {0}")]
17    Recognizer(#[from] RecognizerError),
18    /// The [`Vault`](crate::Vault) failed.
19    #[error("vault: {0}")]
20    Vault(#[from] VaultError),
21}
22
23/// Defines an error type that wraps an implementation's own error, so the
24/// engine's API doesn't depend on any one implementation.
25macro_rules! wrapped_error {
26    ($(#[$doc:meta])* $name:ident) => {
27        $(#[$doc])*
28        #[derive(Debug)]
29        pub struct $name(Box<dyn StdError + Send + Sync>);
30
31        impl $name {
32            /// Wraps an error, or a message such as `"disk full"`.
33            pub fn new(error: impl Into<Box<dyn StdError + Send + Sync>>) -> Self {
34                $name(error.into())
35            }
36
37            /// The wrapped error, for example to downcast it.
38            pub fn get_ref(&self) -> &(dyn StdError + Send + Sync + 'static) {
39                &*self.0
40            }
41
42            /// Returns the wrapped error.
43            pub fn into_inner(self) -> Box<dyn StdError + Send + Sync> {
44                self.0
45            }
46        }
47
48        impl fmt::Display for $name {
49            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50                self.0.fmt(f)
51            }
52        }
53
54        impl StdError for $name {
55            fn source(&self) -> Option<&(dyn StdError + 'static)> {
56                self.0.source()
57            }
58        }
59    };
60}
61
62wrapped_error! {
63    /// An error from a [`Vault`](crate::Vault) implementation.
64    VaultError
65}
66
67wrapped_error! {
68    /// An error from an [`EntityRecognizer`](crate::EntityRecognizer) implementation.
69    RecognizerError
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn wrapped_errors_show_the_inner_message() {
78        let io = std::io::Error::other("disk full");
79        let e = Error::from(VaultError::new(io));
80        assert_eq!(e.to_string(), "vault: disk full");
81        let Error::Vault(v) = e else { unreachable!() };
82        assert!(v.get_ref().downcast_ref::<std::io::Error>().is_some());
83        assert_eq!(
84            Error::from(RecognizerError::new("model crashed")).to_string(),
85            "name detection failed: model crashed"
86        );
87    }
88}