retry-error: Provide fmt_error_with_sources in retry-error

This code came from tor-error.  So now tor-error depends on
retry-error.
This commit is contained in:
Ian Jackson 2023-07-18 11:09:54 +01:00
parent dd5ceed791
commit 882ce8c8ce
6 changed files with 69 additions and 17 deletions

2
Cargo.lock generated
View File

@ -3065,6 +3065,7 @@ name = "retry-error"
version = "0.4.2"
dependencies = [
"anyhow",
"thiserror",
]
[[package]]
@ -4421,6 +4422,7 @@ dependencies = [
"futures",
"once_cell",
"paste",
"retry-error",
"static_assertions",
"strum",
"thiserror",

View File

@ -14,6 +14,7 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
[dev-dependencies]
anyhow = "1.0.23"
thiserror = "1"
[dependencies]

View File

@ -0,0 +1 @@
ADDED: fn fmt_error_with_sources

View File

@ -41,7 +41,8 @@
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
use std::error::Error;
use std::fmt::{Debug, Display, Error as FmtError, Formatter};
use std::fmt::{self, Debug, Display, Error as FmtError, Formatter};
use std::iter;
/// An error type for use when we're going to do something a few times,
/// and they might all fail.
@ -227,6 +228,65 @@ impl<E: Display> Display for RetryError<E> {
}
}
/// Helper: formats a [`std::error::Error`] and its sources (as `"error: source"`)
///
/// Avoids duplication in messages by not printing messages which are
/// wholly-contained (textually) within already-printed messages.
///
/// Offered as a `fmt` function:
/// this is for use in more-convenient higher-level error handling functionality,
/// rather than directly in application/functional code.
///
/// This is used by `RetryError`'s impl of `Display`,
/// but will be useful for other error-handling situations.
///
/// # Example
///
/// ```
/// use std::fmt::{self, Display};
///
/// #[derive(Debug, thiserror::Error)]
/// #[error("some pernickety problem")]
/// struct Pernickety;
///
/// #[derive(Debug, thiserror::Error)]
/// enum ApplicationError {
/// #[error("everything is terrible")]
/// Terrible(#[source] Pernickety),
/// }
///
/// struct Wrapper(Box<dyn std::error::Error>);
/// impl Display for Wrapper {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// retry_error::fmt_error_with_sources(&*self.0, f)
/// }
/// }
///
/// let bad = Pernickety;
/// let err = ApplicationError::Terrible(bad);
///
/// let printed = Wrapper(err.into()).to_string();
/// assert_eq!(printed, "everything is terrible: some pernickety problem");
/// ```
pub fn fmt_error_with_sources(mut e: &dyn Error, f: &mut fmt::Formatter) -> fmt::Result {
let mut last = String::new();
let mut sep = iter::once("").chain(iter::repeat(": "));
loop {
let this = e.to_string();
if !last.contains(&this) {
write!(f, "{}{}", sep.next().expect("repeat ended"), &this)?;
}
last = this;
if let Some(ne) = e.source() {
e = ne;
} else {
break;
}
}
Ok(())
}
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@

View File

@ -30,6 +30,7 @@ derive_more = "0.99.3"
futures = "0.3"
once_cell = "1"
paste = "1"
retry-error = { path = "../retry-error", version = "0.4.2" } # WRONG should be 0.4.3
static_assertions = { version = "1", optional = true }
strum = { version = "0.25", features = ["derive"] }
thiserror = "1"

View File

@ -16,22 +16,9 @@ where
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// Non-generic inner function avoids code bloat
fn inner(mut e: &dyn StdError, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "error")?;
let mut last = String::new();
loop {
let this = e.to_string();
if !last.contains(&this) {
write!(f, ": {}", &this)?;
}
last = this;
if let Some(ne) = e.source() {
e = ne;
} else {
break;
}
}
fn inner(e: &dyn StdError, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "error: ")?;
retry_error::fmt_error_with_sources(e, f)?;
Ok(())
}