Skip to main content

arti_client/err/
hint.rs

1//! Facility for error-hinting
2
3use tor_basic_utils::error_sources::ErrorSources;
4
5use super::ErrorHint;
6use std::error::Error as StdError;
7
8/// non-public module, to implement a "sealed" trait.
9mod 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.
19pub 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
30impl seal::Sealed for super::Error {}
31impl HintableError for super::Error {
32    fn hint(&self) -> Option<ErrorHint<'_>> {
33        best_hint(self)
34    }
35}
36#[cfg(feature = "anyhow")]
37impl seal::Sealed for anyhow::Error {}
38#[cfg(feature = "anyhow")]
39impl HintableError for anyhow::Error {
40    fn hint(&self) -> Option<ErrorHint<'_>> {
41        best_hint(self.as_ref())
42    }
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.
50fn best_hint<'a>(err: &'a (dyn StdError + 'static)) -> Option<ErrorHint<'a>> {
51    ErrorSources::new(err)
52        .find_map(|e| downcast_to_hintable_impl(e).and_then(HintableErrorImpl::hint_specific))
53}
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.
62trait 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
72impl HintableErrorImpl for fs_mistrust::Error {
73    fn hint_specific(&self) -> Option<ErrorHint<'_>> {
74        match self {
75            fs_mistrust::Error::BadPermission(filename, bits, badbits) => Some(ErrorHint {
76                inner: super::ErrorHintInner::BadPermission {
77                    filename,
78                    bits: *bits,
79                    badbits: *badbits,
80                },
81            }),
82            _ => None,
83        }
84    }
85}
86
87impl 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`.
105macro_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        fn downcast_to_hintable_impl<'a> (e: &'a (dyn StdError + 'static)) -> Option<&'a dyn HintableErrorImpl> {
115            $(
116                if let Some(hintable) =  e.downcast_ref::<$e>() {
117                    return Some(hintable);
118                }
119            )+
120            None
121        }
122    }
123}
124
125hintable_impl! {
126    fs_mistrust::Error,
127    tor_netdoc::doc::netstatus::ProtocolSupportError,
128}
129
130#[cfg(test)]
131mod 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}