arti_client/err/
hint.rs

1//! Facility for error-hinting
2
3use super::ErrorHint;
4use std::error::Error as StdError;
5
6/// non-public module, to implement a "sealed" trait.
7mod 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.
17pub 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
28impl seal::Sealed for super::Error {}
29impl HintableError for super::Error {
30    fn hint(&self) -> Option<ErrorHint<'_>> {
31        best_hint(self)
32    }
33}
34#[cfg(feature = "anyhow")]
35impl seal::Sealed for anyhow::Error {}
36#[cfg(feature = "anyhow")]
37impl HintableError for anyhow::Error {
38    fn hint(&self) -> Option<ErrorHint<'_>> {
39        best_hint(self.as_ref())
40    }
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.
48fn best_hint<'a>(mut err: &'a (dyn StdError + 'static)) -> Option<ErrorHint<'a>> {
49    loop {
50        if let Some(hint) =
51            downcast_to_hintable_impl(err).and_then(HintableErrorImpl::hint_specific)
52        {
53            return Some(hint);
54        }
55        err = err.source()?;
56    }
57}
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.
66trait 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
76impl HintableErrorImpl for fs_mistrust::Error {
77    fn hint_specific(&self) -> Option<ErrorHint<'_>> {
78        match self {
79            fs_mistrust::Error::BadPermission(filename, bits, badbits) => Some(ErrorHint {
80                inner: super::ErrorHintInner::BadPermission {
81                    filename,
82                    bits: *bits,
83                    badbits: *badbits,
84                },
85            }),
86            _ => None,
87        }
88    }
89}
90
91/// Declare one or more error types as having hints.
92///
93/// This macro implements Sealed for those types, and makes them participate
94/// in `downcast_to_hintable_impl`.
95macro_rules! hintable_impl {
96    { $( $e:ty )+, $(,)? } =>
97    {
98        $(
99            impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
100        )+
101
102        /// If possible, downcast this `StdError` to one of the implementations
103        /// of `HintableErrorImpl`.
104        fn downcast_to_hintable_impl<'a> (e: &'a (dyn StdError + 'static)) -> Option<&'a dyn HintableErrorImpl> {
105            $(
106                if let Some(hintable) =  e.downcast_ref::<$e>() {
107                    return Some(hintable);
108                }
109            )+
110            None
111        }
112    }
113}
114
115hintable_impl! {
116    fs_mistrust::Error,
117}
118
119#[cfg(test)]
120mod test {
121    // @@ begin test lint list maintained by maint/add_warning @@
122    #![allow(clippy::bool_assert_comparison)]
123    #![allow(clippy::clone_on_copy)]
124    #![allow(clippy::dbg_macro)]
125    #![allow(clippy::mixed_attributes_style)]
126    #![allow(clippy::print_stderr)]
127    #![allow(clippy::print_stdout)]
128    #![allow(clippy::single_char_pattern)]
129    #![allow(clippy::unwrap_used)]
130    #![allow(clippy::unchecked_duration_subtraction)]
131    #![allow(clippy::useless_vec)]
132    #![allow(clippy::needless_pass_by_value)]
133    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
134
135    use super::*;
136
137    fn mistrust_err() -> fs_mistrust::Error {
138        fs_mistrust::Error::BadPermission("/shocking-bad-directory".into(), 0o777, 0o022)
139    }
140
141    #[test]
142    fn find_hint_tor_error() {
143        let underlying = mistrust_err();
144        let want_hint_string = underlying.hint_specific().unwrap().to_string();
145
146        let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
147        let e = crate::Error {
148            detail: Box::new(crate::err::ErrorDetail::from(e)),
149        };
150        let hint: Option<ErrorHint<'_>> = e.hint();
151        assert_eq!(hint.unwrap().to_string(), want_hint_string);
152        dbg!(want_hint_string);
153    }
154
155    #[test]
156    fn find_no_hint_tor_error() {
157        let e = tor_error::internal!("let's suppose this error has no source");
158        let e = crate::Error {
159            detail: Box::new(crate::err::ErrorDetail::from(e)),
160        };
161        let hint: Option<ErrorHint<'_>> = e.hint();
162        assert!(hint.is_none());
163    }
164
165    #[test]
166    #[cfg(feature = "anyhow")]
167    fn find_hint_anyhow() {
168        let underlying = mistrust_err();
169        let want_hint_string = underlying.hint_specific().unwrap().to_string();
170
171        let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
172        let e = anyhow::Error::from(e);
173        let hint: Option<ErrorHint<'_>> = e.hint();
174        assert_eq!(hint.unwrap().to_string(), want_hint_string);
175    }
176}