1use tor_basic_utils::error_sources::ErrorSources;
4
5use super::ErrorHint;
6use std::error::Error as StdError;
7
8mod seal {
10 #[allow(unreachable_pub)]
12 pub trait Sealed {}
13 #[allow(unreachable_pub)]
15 pub trait OnlyTheMacroShouldImplementThis__ {}
16}
17
18pub trait HintableError: seal::Sealed {
20 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
45fn 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
55trait HintableErrorImpl: seal::OnlyTheMacroShouldImplementThis__ {
63 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
101macro_rules! hintable_impl {
106 { $( $e:ty ),+ $(,)? } =>
107 {
108 $(
109 impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
110 )+
111
112 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 #![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 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}