1use super::ErrorHint;
4use std::error::Error as StdError;
5
6mod seal {
8 #[allow(unreachable_pub)]
10 pub trait Sealed {}
11 #[allow(unreachable_pub)]
13 pub trait OnlyTheMacroShouldImplementThis__ {}
14}
15
16pub trait HintableError: seal::Sealed {
18 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
43fn 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
59trait HintableErrorImpl: seal::OnlyTheMacroShouldImplementThis__ {
67 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
91impl 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
105macro_rules! hintable_impl {
110 { $( $e:ty ),+ $(,)? } =>
111 {
112 $(
113 impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
114 )+
115
116 fn downcast_to_hintable_impl<'a> (e: &'a (dyn StdError + 'static)) -> Option<&'a dyn HintableErrorImpl> {
119 $(
120 if let Some(hintable) = e.downcast_ref::<$e>() {
121 return Some(hintable);
122 }
123 )+
124 None
125 }
126 }
127}
128
129hintable_impl! {
130 fs_mistrust::Error,
131 tor_netdoc::doc::netstatus::ProtocolSupportError,
132}
133
134#[cfg(test)]
135mod test {
136 #![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 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}