1
//! Facility for error-hinting
2

            
3
use tor_basic_utils::error_sources::ErrorSources;
4

            
5
use super::ErrorHint;
6
use std::error::Error as StdError;
7

            
8
/// non-public module, to implement a "sealed" trait.
9
mod seal {
10
    /// Trait to seal the "HintableError" trait
11
    #[allow(unreachable_pub)]
12
    pub trait Sealed {}
13
    /// Trait to seal the "HintableErrorImpl" trait
14
    #[allow(unreachable_pub)]
15
    pub trait OnlyTheMacroShouldImplementThis__ {}
16
}
17

            
18
/// An error that can provide additional information about how to solve itself.
19
pub trait HintableError: seal::Sealed {
20
    /// Return a hint object explaining how to solve this error, if we have one.
21
    ///
22
    /// Most errors won't have obvious hints, but some do.  For the ones that
23
    /// do, we can return an [`ErrorHint`].
24
    ///
25
    /// Right now, `ErrorHint` is completely opaque: the only supported option
26
    /// is to format it for human consumption.
27
    fn hint(&self) -> Option<ErrorHint<'_>>;
28
}
29

            
30
impl seal::Sealed for super::Error {}
31
impl HintableError for super::Error {
32
4
    fn hint(&self) -> Option<ErrorHint<'_>> {
33
4
        best_hint(self)
34
4
    }
35
}
36
#[cfg(feature = "anyhow")]
37
impl seal::Sealed for anyhow::Error {}
38
#[cfg(feature = "anyhow")]
39
impl HintableError for anyhow::Error {
40
818
    fn hint(&self) -> Option<ErrorHint<'_>> {
41
818
        best_hint(self.as_ref())
42
818
    }
43
}
44

            
45
// TODO: We could also define HintableError for &dyn StdError if we wanted.
46

            
47
/// Return the best hint possible from `err`, by looking for the first error in
48
/// the chain defined by `err` and its sources that provides a value for
49
/// HintableErrorImpl::hint.
50
822
fn best_hint<'a>(err: &'a (dyn StdError + 'static)) -> Option<ErrorHint<'a>> {
51
822
    ErrorSources::new(err)
52
861
        .find_map(|e| downcast_to_hintable_impl(e).and_then(HintableErrorImpl::hint_specific))
53
822
}
54

            
55
/// Trait for an error that can provide a hint _directly_.
56
///
57
/// Not defined for errors whose sources may provide a hint.
58
///
59
/// To implement this trait, you need to provide an impl in this crate, and
60
/// extend the macro invocation for `hintable_impl!`.  Nothing else is currently
61
/// supported.
62
trait HintableErrorImpl: seal::OnlyTheMacroShouldImplementThis__ {
63
    /// If possible, provide a hint for how to solve this error.
64
    ///
65
    /// (This should not check the source of this error or any other error;
66
    /// recursing is the job of [`best_hint`].  This is the method that
67
    /// should be implemented for an error type that might have a hint about how
68
    /// to solve that error in particular.)
69
    fn hint_specific(&self) -> Option<ErrorHint<'_>>;
70
}
71

            
72
impl HintableErrorImpl for fs_mistrust::Error {
73
8
    fn hint_specific(&self) -> Option<ErrorHint<'_>> {
74
8
        match self {
75
8
            fs_mistrust::Error::BadPermission(filename, bits, badbits) => Some(ErrorHint {
76
8
                inner: super::ErrorHintInner::BadPermission {
77
8
                    filename,
78
8
                    bits: *bits,
79
8
                    badbits: *badbits,
80
8
                },
81
8
            }),
82
            _ => None,
83
        }
84
8
    }
85
}
86

            
87
impl HintableErrorImpl for tor_netdoc::doc::netstatus::ProtocolSupportError {
88
    fn hint_specific(&self) -> Option<ErrorHint<'_>> {
89
        use tor_netdoc::doc::netstatus::ProtocolSupportError as E;
90
        match self {
91
            E::MissingRequired(protocols) => Some(ErrorHint {
92
                inner: super::ErrorHintInner::MissingProtocols {
93
                    required: protocols,
94
                },
95
            }),
96
            _ => None,
97
        }
98
    }
99
}
100

            
101
/// Declare one or more error types as having hints.
102
///
103
/// This macro implements Sealed for those types, and makes them participate
104
/// in `downcast_to_hintable_impl`.
105
macro_rules! hintable_impl {
106
    { $( $e:ty ),+ $(,)? } =>
107
    {
108
        $(
109
            impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
110
        )+
111

            
112
        /// If possible, downcast this `StdError` to one of the implementations
113
        /// of `HintableErrorImpl`.
114
834
        fn downcast_to_hintable_impl<'a> (e: &'a (dyn StdError + 'static)) -> Option<&'a dyn HintableErrorImpl> {
115
            $(
116
830
                if let Some(hintable) =  e.downcast_ref::<$e>() {
117
4
                    return Some(hintable);
118
830
                }
119
            )+
120
830
            None
121
834
        }
122
    }
123
}
124

            
125
hintable_impl! {
126
    fs_mistrust::Error,
127
    tor_netdoc::doc::netstatus::ProtocolSupportError,
128
}
129

            
130
#[cfg(test)]
131
mod test {
132
    // @@ begin test lint list maintained by maint/add_warning @@
133
    #![allow(clippy::bool_assert_comparison)]
134
    #![allow(clippy::clone_on_copy)]
135
    #![allow(clippy::dbg_macro)]
136
    #![allow(clippy::mixed_attributes_style)]
137
    #![allow(clippy::print_stderr)]
138
    #![allow(clippy::print_stdout)]
139
    #![allow(clippy::single_char_pattern)]
140
    #![allow(clippy::unwrap_used)]
141
    #![allow(clippy::unchecked_time_subtraction)]
142
    #![allow(clippy::useless_vec)]
143
    #![allow(clippy::needless_pass_by_value)]
144
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
145

            
146
    use super::*;
147

            
148
    fn mistrust_err() -> fs_mistrust::Error {
149
        fs_mistrust::Error::BadPermission("/shocking-bad-directory".into(), 0o777, 0o022)
150
    }
151

            
152
    #[test]
153
    fn find_hint_tor_error() {
154
        let underlying = mistrust_err();
155
        let want_hint_string = underlying.hint_specific().unwrap().to_string();
156

            
157
        let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
158
        let e = crate::Error {
159
            detail: Box::new(crate::err::ErrorDetail::from(e)),
160
        };
161
        let hint: Option<ErrorHint<'_>> = e.hint();
162
        assert_eq!(hint.unwrap().to_string(), want_hint_string);
163
        dbg!(want_hint_string);
164
    }
165

            
166
    #[test]
167
    fn find_no_hint_tor_error() {
168
        let e = tor_error::internal!("let's suppose this error has no source");
169
        let e = crate::Error {
170
            detail: Box::new(crate::err::ErrorDetail::from(e)),
171
        };
172
        let hint: Option<ErrorHint<'_>> = e.hint();
173
        assert!(hint.is_none());
174
    }
175

            
176
    #[test]
177
    #[cfg(feature = "anyhow")]
178
    fn find_hint_anyhow() {
179
        let underlying = mistrust_err();
180
        let want_hint_string = underlying.hint_specific().unwrap().to_string();
181

            
182
        let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
183
        let e = anyhow::Error::from(e);
184
        let hint: Option<ErrorHint<'_>> = e.hint();
185
        assert_eq!(hint.unwrap().to_string(), want_hint_string);
186
    }
187
}