use super::ErrorHint;
use std::error::Error as StdError;
mod seal {
#[allow(unreachable_pub)]
pub trait Sealed {}
#[allow(unreachable_pub)]
pub trait OnlyTheMacroShouldImplementThis__ {}
}
pub trait HintableError: seal::Sealed {
fn hint(&self) -> Option<ErrorHint<'_>>;
}
impl seal::Sealed for super::Error {}
impl HintableError for super::Error {
fn hint(&self) -> Option<ErrorHint<'_>> {
best_hint(self)
}
}
#[cfg(feature = "anyhow")]
impl seal::Sealed for anyhow::Error {}
#[cfg(feature = "anyhow")]
impl HintableError for anyhow::Error {
fn hint(&self) -> Option<ErrorHint<'_>> {
best_hint(self.as_ref())
}
}
fn best_hint<'a>(mut err: &'a (dyn StdError + 'static)) -> Option<ErrorHint<'a>> {
loop {
if let Some(hint) =
downcast_to_hintable_impl(err).and_then(HintableErrorImpl::hint_specific)
{
return Some(hint);
}
err = err.source()?;
}
}
trait HintableErrorImpl: seal::OnlyTheMacroShouldImplementThis__ {
fn hint_specific(&self) -> Option<ErrorHint<'_>>;
}
impl HintableErrorImpl for fs_mistrust::Error {
fn hint_specific(&self) -> Option<ErrorHint<'_>> {
match self {
fs_mistrust::Error::BadPermission(filename, bits, badbits) => Some(ErrorHint {
inner: super::ErrorHintInner::BadPermission {
filename,
bits: *bits,
badbits: *badbits,
},
}),
_ => None,
}
}
}
macro_rules! hintable_impl {
{ $( $e:ty )+, $(,)? } =>
{
$(
impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
)+
fn downcast_to_hintable_impl<'a> (e: &'a (dyn StdError + 'static)) -> Option<&'a dyn HintableErrorImpl> {
$(
if let Some(hintable) = e.downcast_ref::<$e>() {
return Some(hintable);
}
)+
None
}
}
}
hintable_impl! {
fs_mistrust::Error,
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
fn mistrust_err() -> fs_mistrust::Error {
fs_mistrust::Error::BadPermission("/shocking-bad-directory".into(), 0o777, 0o022)
}
#[test]
fn find_hint_tor_error() {
let underlying = mistrust_err();
let want_hint_string = underlying.hint_specific().unwrap().to_string();
let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
let e = crate::Error {
detail: Box::new(crate::err::ErrorDetail::from(e)),
};
let hint: Option<ErrorHint<'_>> = e.hint();
assert_eq!(hint.unwrap().to_string(), want_hint_string);
dbg!(want_hint_string);
}
#[test]
fn find_no_hint_tor_error() {
let e = tor_error::internal!("let's suppose this error has no source");
let e = crate::Error {
detail: Box::new(crate::err::ErrorDetail::from(e)),
};
let hint: Option<ErrorHint<'_>> = e.hint();
assert!(hint.is_none());
}
#[test]
#[cfg(feature = "anyhow")]
fn find_hint_anyhow() {
let underlying = mistrust_err();
let want_hint_string = underlying.hint_specific().unwrap().to_string();
let e = tor_error::into_internal!("let's pretend an error happened")(underlying);
let e = anyhow::Error::from(e);
let hint: Option<ErrorHint<'_>> = e.hint();
assert_eq!(hint.unwrap().to_string(), want_hint_string);
}
}