arti_client/err/hint.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
//! Facility for error-hinting
use super::ErrorHint;
use std::error::Error as StdError;
/// non-public module, to implement a "sealed" trait.
mod seal {
/// Trait to seal the "HintableError" trait
#[allow(unreachable_pub)]
pub trait Sealed {}
/// Trait to seal the "HintableErrorImpl" trait
#[allow(unreachable_pub)]
pub trait OnlyTheMacroShouldImplementThis__ {}
}
/// An error that can provide additional information about how to solve itself.
pub trait HintableError: seal::Sealed {
/// Return a hint object explaining how to solve this error, if we have one.
///
/// Most errors won't have obvious hints, but some do. For the ones that
/// do, we can return an [`ErrorHint`].
///
/// Right now, `ErrorHint` is completely opaque: the only supported option
/// is to format it for human consumption.
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())
}
}
// TODO: We could also define HintableError for &dyn StdError if we wanted.
/// Return the best hint possible from `err`, by looking for the first error in
/// the chain defined by `err` and its sources that provides a value for
/// HintableErrorImpl::hint.
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 for an error that can provide a hint _directly_.
///
/// Not defined for errors whose sources may provide a hint.
///
/// To implement this trait, you need to provide an impl in this crate, and
/// extend the macro invocation for `hintable_impl!`. Nothing else is currently
/// supported.
trait HintableErrorImpl: seal::OnlyTheMacroShouldImplementThis__ {
/// If possible, provide a hint for how to solve this error.
///
/// (This should not check the source of this error or any other error;
/// recursing is the job of [`best_hint`]. This is the method that
/// should be implemented for an error type that might have a hint about how
/// to solve that error in particular.)
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,
}
}
}
/// Declare one or more error types as having hints.
///
/// This macro implements Sealed for those types, and makes them participate
/// in `downcast_to_hintable_impl`.
macro_rules! hintable_impl {
{ $( $e:ty )+, $(,)? } =>
{
$(
impl seal::OnlyTheMacroShouldImplementThis__ for $e {}
)+
/// If possible, downcast this `StdError` to one of the implementations
/// of `HintableErrorImpl`.
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 {
// @@ begin test lint list maintained by maint/add_warning @@
#![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)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
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);
}
}