#![doc = include_str!("../README.md")]
use std::error::Error as StdError;
use std::fmt;
use derive_more::{Deref, DerefMut};
pub type ErrorCollection = Errors;
#[derive(Default, Deref, DerefMut)]
pub struct Errors(pub Vec<anyhow::Error>);
impl Errors {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, err: impl Into<anyhow::Error>) {
self.0.push(err.into());
}
pub fn append(&mut self, err: impl Into<Self>) {
self.0.append(&mut err.into().0);
}
pub fn collect<T, E>(&mut self, result: Result<T, E>) -> Option<T>
where
E: Into<anyhow::Error>,
{
match result {
Ok(value) => Some(value),
Err(err) => {
self.append(err.into());
None
}
}
}
pub fn into_vec(self) -> Vec<anyhow::Error> {
self.0
}
pub fn as_result(mut self) -> anyhow::Result<()> {
match self.len() {
0 => Ok(()),
1 => Err(self.pop().unwrap()),
_ => Err(self.into()),
}
}
}
const PADDING: usize = 3;
impl fmt::Debug for Errors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "Errors ")?;
let mut list = f.debug_list();
for error in self.iter() {
list.entry(error);
}
list.finish()
} else {
debug_collection(f, self, 0)
}
}
}
fn debug_collection(f: &mut fmt::Formatter<'_>, errors: &Errors, indent: usize) -> fmt::Result {
if errors.is_empty() {
writeln!(f, "none")
} else if errors.len() == 1 {
debug_error(f, &errors[0], indent)
} else {
writeln!(f, "{} errors:", errors.len())?;
for (idx, error) in errors.iter().enumerate() {
write!(f, "{}{}. ", spaces(indent + PADDING), idx + 1)?;
match error.downcast_ref::<Errors>() {
None => debug_error(f, error, indent + PADDING)?,
Some(errors) => debug_collection(f, errors, indent + PADDING)?,
}
}
Ok(())
}
}
fn debug_error(f: &mut fmt::Formatter<'_>, error: &anyhow::Error, indent: usize) -> fmt::Result {
let padding = spaces(indent + PADDING);
let error_string = format!("{error:?}");
for (idx, line) in error_string.split('\n').enumerate() {
let padding = if idx == 0 { "" } else { padding };
writeln!(f, "{padding}{line}")?;
}
Ok(())
}
impl fmt::Display for Errors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Errors: ")?;
if self.is_empty() {
return writeln!(f, "none");
}
let mut first = true;
display_collection(f, self, &mut first)?;
writeln!(f)
}
}
fn display_collection(
f: &mut fmt::Formatter<'_>,
errors: &Errors,
first: &mut bool,
) -> fmt::Result {
for error in errors.iter() {
match error.downcast_ref::<Errors>() {
Some(errors) => display_collection(f, errors, first)?,
None => display_error(f, error, first)?,
}
}
Ok(())
}
fn display_error(
f: &mut fmt::Formatter<'_>,
error: &anyhow::Error,
first: &mut bool,
) -> fmt::Result {
if *first {
*first = false;
} else {
write!(f, ", ")?;
}
if f.alternate() {
write!(f, "{error:#}")
} else {
write!(f, "{error}")
}
}
fn spaces(indent: usize) -> &'static str {
&" "[..indent.min(32)]
}
impl StdError for Errors {}
impl From<&str> for Errors {
fn from(value: &str) -> Self {
Self(vec![anyhow::anyhow!("{value}")])
}
}
impl From<String> for Errors {
fn from(value: String) -> Self {
Self(vec![anyhow::anyhow!(value)])
}
}
impl<T> From<Option<T>> for Errors
where
T: Into<anyhow::Error>,
{
fn from(result: Option<T>) -> Self {
match result {
Some(err) => err.into().into(),
None => Self::default(),
}
}
}
impl<T, E> From<Result<T, E>> for Errors
where
E: Into<anyhow::Error>,
{
fn from(result: Result<T, E>) -> Self {
match result {
Ok(_) => Self::default(),
Err(err) => err.into().into(),
}
}
}
impl From<Vec<anyhow::Error>> for Errors {
fn from(errors: Vec<anyhow::Error>) -> Self {
Self(errors)
}
}
impl From<anyhow::Error> for Errors {
fn from(error: anyhow::Error) -> Self {
match error.downcast::<Self>() {
Ok(errors) => errors,
Err(error) => Self(vec![error]),
}
}
}
impl<T> From<Errors> for anyhow::Result<T>
where
T: Default,
{
fn from(mut value: Errors) -> Self {
match value.len() {
0 => Ok(T::default()),
1 => Err(value.pop().unwrap()),
_ => Err(value.into()),
}
}
}
#[cfg(test)]
mod tests {
use std::io;
use anyhow::{Context, anyhow};
use super::*;
#[test]
fn push() {
let mut nested = Errors::new();
nested.push(anyhow!("Generic error 1"));
nested.push(anyhow!("Generic error 2"));
nested.push(anyhow!("Generic error 3"));
let mut errors = Errors::new();
errors.push(nested);
errors.push(anyhow!("Generic error 4"));
errors.push(io::Error::from_raw_os_error(22));
assert_eq!(errors.len(), 3);
}
#[test]
fn append() {
let mut nested = Errors::new();
nested.append(vec![anyhow!("Generic error 1"), anyhow!("Generic error 2")]);
let mut errors = Errors::new();
errors.append(nested);
errors.append(anyhow!("Generic error 3"));
assert_eq!(errors.len(), 3);
}
#[test]
fn collect() {
let mut errors = Errors::new();
let result: Result<(), anyhow::Error> = Ok(());
assert_eq!(errors.collect(result), Some(()));
let result: Result<(), anyhow::Error> = Err(anyhow!("Generic error 1"));
assert_eq!(errors.collect(result), None);
assert_eq!(errors.len(), 1);
}
#[test]
fn collect_nested() {
let mut nested = Errors::new();
nested.push(anyhow!("Generic error 1"));
nested.push(anyhow!("Generic error 2"));
nested.push(anyhow!("Generic error 3"));
let mut errors = Errors::new();
let result: Result<(), Errors> = Err(nested);
assert_eq!(errors.collect(result), None);
assert_eq!(errors.len(), 3);
}
fn deeply_nested() -> Errors {
let mut child = Errors::new();
child.push(anyhow!("Generic error 2"));
child.push(anyhow!("Generic error 3\nnew line"));
child.push(Errors(vec![anyhow!("Generic error 4")]));
let mut parent = Errors::new();
parent.push(child);
parent.push(io::Error::from_raw_os_error(1));
let mut errors = Errors::new();
errors.push(
anyhow::Result::<()>::Err(anyhow!("Original error"))
.context("Generic error 1")
.unwrap_err(),
);
errors.push(parent);
errors
}
#[test]
fn display() {
let errors = deeply_nested();
assert_eq!(
format!("{errors}"),
"Errors: Generic error 1, Generic error 2, Generic error 3\n\
new line, Generic error 4, Operation not permitted (os error 1)\n"
);
}
#[test]
fn display_alternate() {
let errors = deeply_nested();
assert_eq!(
format!("{errors:#}"),
"Errors: Generic error 1: Original error, Generic error 2, Generic error 3\n\
new line, Generic error 4, Operation not permitted (os error 1)\n"
);
}
#[test]
fn debug() {
let errors = deeply_nested();
assert_eq!(
format!("{errors:?}"),
"2 errors:
1. Generic error 1
\n \
Caused by:
Original error
2. 2 errors:
1. 3 errors:
1. Generic error 2
2. Generic error 3
new line
3. Generic error 4
2. Operation not permitted (os error 1)\n"
.replace("\n ", "\n")
);
}
#[test]
fn debug_alternate() {
let errors = deeply_nested();
println!("{errors:#?}");
assert_eq!(
format!("{errors:#?}"),
"Errors [
Error {
context: \"Generic error 1\",
source: \"Original error\",
},
Errors [
Errors [
\"Generic error 2\",
\"Generic error 3\\nnew line\",
Errors [
\"Generic error 4\",
],
],
Os {
code: 1,
kind: PermissionDenied,
message: \"Operation not permitted\",
},
],
]"
.replace("\n ", "\n")
);
}
}