1//! Facility for error-hinting
23use super::ErrorHint;
4use std::error::Error as StdError;
56/// non-public module, to implement a "sealed" trait.
7mod seal {
8/// Trait to seal the "HintableError" trait
9#[allow(unreachable_pub)]
10pub trait Sealed {}
11/// Trait to seal the "HintableErrorImpl" trait
12#[allow(unreachable_pub)]
13pub trait OnlyTheMacroShouldImplementThis__ {}
14}
1516/// 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.
25fn hint(&self) -> Option<ErrorHint<'_>>;
26}
2728impl seal::Sealed for super::Error {}
29impl HintableError for super::Error {
30fn 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 {
38fn hint(&self) -> Option<ErrorHint<'_>> {
39 best_hint(self.as_ref())
40 }
41}
4243// TODO: We could also define HintableError for &dyn StdError if we wanted.
4445/// 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>> {
49loop {
50if let Some(hint) =
51 downcast_to_hintable_impl(err).and_then(HintableErrorImpl::hint_specific)
52 {
53return Some(hint);
54 }
55 err = err.source()?;
56 }
57}
5859/// 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.)
73fn hint_specific(&self) -> Option<ErrorHint<'_>>;
74}
7576impl HintableErrorImpl for fs_mistrust::Error {
77fn hint_specific(&self) -> Option<ErrorHint<'_>> {
78match 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}
9091impl HintableErrorImpl for tor_netdoc::doc::netstatus::ProtocolSupportError {
92fn hint_specific(&self) -> Option<ErrorHint<'_>> {
93use tor_netdoc::doc::netstatus::ProtocolSupportError as E;
94match self {
95 E::MissingRequired(protocols) => Some(ErrorHint {
96 inner: super::ErrorHintInner::MissingProtocols {
97 required: protocols,
98 },
99 }),
100_ => None,
101 }
102 }
103}
104105/// 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`.
109macro_rules! hintable_impl {
110 { $( $e:ty ),+ $(,)? } =>
111 {
112 $(
113impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
114 )+
115116/// If possible, downcast this `StdError` to one of the implementations
117 /// of `HintableErrorImpl`.
118fn downcast_to_hintable_impl<'a> (e: &'a (dyn StdError + 'static)) -> Option<&'a dyn HintableErrorImpl> {
119 $(
120if let Some(hintable) = e.downcast_ref::<$e>() {
121return Some(hintable);
122 }
123 )+
124None
125}
126 }
127}
128129hintable_impl! {
130 fs_mistrust::Error,
131 tor_netdoc::doc::netstatus::ProtocolSupportError,
132}
133134#[cfg(test)]
135mod 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 @@ -->
149150use super::*;
151152fn mistrust_err() -> fs_mistrust::Error {
153 fs_mistrust::Error::BadPermission("/shocking-bad-directory".into(), 0o777, 0o022)
154 }
155156#[test]
157fn find_hint_tor_error() {
158let underlying = mistrust_err();
159let want_hint_string = underlying.hint_specific().unwrap().to_string();
160161let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
162let e = crate::Error {
163 detail: Box::new(crate::err::ErrorDetail::from(e)),
164 };
165let hint: Option<ErrorHint<'_>> = e.hint();
166assert_eq!(hint.unwrap().to_string(), want_hint_string);
167dbg!(want_hint_string);
168 }
169170#[test]
171fn find_no_hint_tor_error() {
172let e = tor_error::internal!("let's suppose this error has no source");
173let e = crate::Error {
174 detail: Box::new(crate::err::ErrorDetail::from(e)),
175 };
176let hint: Option<ErrorHint<'_>> = e.hint();
177assert!(hint.is_none());
178 }
179180#[test]
181 #[cfg(feature = "anyhow")]
182fn find_hint_anyhow() {
183let underlying = mistrust_err();
184let want_hint_string = underlying.hint_specific().unwrap().to_string();
185186let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
187let e = anyhow::Error::from(e);
188let hint: Option<ErrorHint<'_>> = e.hint();
189assert_eq!(hint.unwrap().to_string(), want_hint_string);
190 }
191}