1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//! Facility for error-hinting

use super::ErrorHint;
use std::error::Error as StdError;

/// non-public module, to implement a "sealed" trait.
mod seal {
    /// Trait to seal the "HintableError" trait
    #[allow(unreachable_pub)]
    pub trait Sealed {}
    /// Trait to seal the "HintableErrorImpl" trait
    #[allow(unreachable_pub)]
    pub trait OnlyTheMacroShouldImplementThis__ {}
}

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

impl seal::Sealed for super::Error {}
impl HintableError for super::Error {
    fn hint(&self) -> Option<ErrorHint<'_>> {
        best_hint(self)
    }
}
#[cfg(feature = "anyhow")]
impl seal::Sealed for anyhow::Error {}
#[cfg(feature = "anyhow")]
impl HintableError for anyhow::Error {
    fn hint(&self) -> Option<ErrorHint<'_>> {
        best_hint(self.as_ref())
    }
}

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

/// Return the best hint possible from `err`, by looking for the first error in
/// the chain defined by `err` and its sources that provides a value for
/// HintableErrorImpl::hint.
fn best_hint<'a>(mut err: &'a (dyn StdError + 'static)) -> Option<ErrorHint<'a>> {
    loop {
        if let Some(hint) =
            downcast_to_hintable_impl(err).and_then(HintableErrorImpl::hint_specific)
        {
            return Some(hint);
        }
        err = err.source()?;
    }
}

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

impl HintableErrorImpl for fs_mistrust::Error {
    fn hint_specific(&self) -> Option<ErrorHint<'_>> {
        match self {
            fs_mistrust::Error::BadPermission(filename, bits, badbits) => Some(ErrorHint {
                inner: super::ErrorHintInner::BadPermission {
                    filename,
                    bits: *bits,
                    badbits: *badbits,
                },
            }),
            _ => None,
        }
    }
}

/// Declare one or more error types as having hints.
///
/// This macro implements Sealed for those types, and makes them participate
/// in `downcast_to_hintable_impl`.
macro_rules! hintable_impl {
    { $( $e:ty )+, $(,)? } =>
    {
        $(
            impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
        )+

        /// If possible, downcast this `StdError` to one of the implementations
        /// of `HintableErrorImpl`.
        fn downcast_to_hintable_impl<'a> (e: &'a (dyn StdError + 'static)) -> Option<&'a dyn HintableErrorImpl> {
            $(
                if let Some(hintable) =  e.downcast_ref::<$e>() {
                    return Some(hintable);
                }
            )+
            None
        }
    }
}

hintable_impl! {
    fs_mistrust::Error,
}

#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_duration_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->

    use super::*;

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

    #[test]
    fn find_hint_tor_error() {
        let underlying = mistrust_err();
        let want_hint_string = underlying.hint_specific().unwrap().to_string();

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

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

    #[test]
    #[cfg(feature = "anyhow")]
    fn find_hint_anyhow() {
        let underlying = mistrust_err();
        let want_hint_string = underlying.hint_specific().unwrap().to_string();

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