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
91macro_rules! hintable_impl {
96 { $( $e:ty )+, $(,)? } =>
97 {
98 $(
99 impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
100 )+
101
102 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 #![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 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}