1
//! Facility for error-hinting
2

            
3
use super::ErrorHint;
4
use std::error::Error as StdError;
5

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

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

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

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

            
45
/// Return the best hint possible from `err`, by looking for the first error in
46
/// the chain defined by `err` and its sources that provides a value for
47
/// HintableErrorImpl::hint.
48
210
fn best_hint<'a>(mut err: &'a (dyn StdError + 'static)) -> Option<ErrorHint<'a>> {
49
    loop {
50
4
        if let Some(hint) =
51
222
            downcast_to_hintable_impl(err).and_then(HintableErrorImpl::hint_specific)
52
        {
53
4
            return Some(hint);
54
218
        }
55
218
        err = err.source()?;
56
    }
57
210
}
58

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

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

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

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

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

            
129
hintable_impl! {
130
    fs_mistrust::Error,
131
    tor_netdoc::doc::netstatus::ProtocolSupportError,
132
}
133

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

            
150
    use super::*;
151

            
152
    fn mistrust_err() -> fs_mistrust::Error {
153
        fs_mistrust::Error::BadPermission("/shocking-bad-directory".into(), 0o777, 0o022)
154
    }
155

            
156
    #[test]
157
    fn find_hint_tor_error() {
158
        let underlying = mistrust_err();
159
        let want_hint_string = underlying.hint_specific().unwrap().to_string();
160

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

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

            
180
    #[test]
181
    #[cfg(feature = "anyhow")]
182
    fn find_hint_anyhow() {
183
        let underlying = mistrust_err();
184
        let want_hint_string = underlying.hint_specific().unwrap().to_string();
185

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