use crate::map::HashSet;
use std::{fmt, hash::Hash};
#[derive(thiserror::Error, Debug, Clone, Default, PartialEq, Eq)]
pub struct ErrorStream<E>(smallvec::SmallVec<[E; 1]>);
impl<E> ErrorStream<E> {
pub fn expect_nonempty<I: IntoIterator<Item = E>>(errors: I) -> Self {
let mut errors = errors.into_iter();
let first = errors.next().expect("expected at least one error");
let mut stream = Self::from(first);
stream.extend(errors);
stream
}
pub fn add_extra_errors<T>(
result: Result<T, ErrorStream<E>>,
extra_errors: impl IntoIterator<Item = E>,
) -> Result<T, ErrorStream<E>> {
match result {
Ok(value) => {
let errors: SmallVec<[E; 1]> = extra_errors.into_iter().collect();
if errors.is_empty() {
Ok(value)
} else {
Err(ErrorStream(errors))
}
}
Err(mut errors) => {
errors.extend(extra_errors);
Err(errors)
}
}
}
pub fn iter(&self) -> impl Iterator<Item = &E> {
self.0.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut E> {
self.0.iter_mut()
}
pub fn drain(&mut self) -> impl Iterator<Item = E> + '_ {
self.0.drain(..)
}
pub fn push(&mut self, error: E) {
self.0.push(error);
}
pub fn extend(&mut self, other: impl IntoIterator<Item = E>) {
self.0.extend(other);
}
#[inline(never)] #[cold]
fn unpack<T, ES: Into<ErrorStream<E>>>(&mut self, result: Result<T, ES>) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(error) => {
self.0.extend(error.into().0);
None
}
}
}
}
impl<E: fmt::Display> fmt::Display for ErrorStream<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Errors occurred:")?;
for error in self.iter() {
writeln!(f, "{error}\n")?;
}
Ok(())
}
}
impl<E: Ord + Eq> ErrorStream<E> {
pub fn sort_deduplicate(mut self) -> Self {
self.0.sort_unstable();
self.0.dedup();
self
}
}
impl<E: Eq + Hash> ErrorStream<E> {
pub fn hash_deduplicate(mut self) -> Self {
let set = self.0.drain(..).collect::<HashSet<_>>();
self.0.extend(set);
self
}
}
impl<E> IntoIterator for ErrorStream<E> {
type Item = <smallvec::SmallVec<[E; 1]> as IntoIterator>::Item;
type IntoIter = <smallvec::SmallVec<[E; 1]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<E> From<E> for ErrorStream<E> {
fn from(error: E) -> Self {
Self(smallvec::smallvec![error])
}
}
pub trait CombineErrors {
type Ok;
type Error;
fn combine_errors(self) -> Result<Self::Ok, ErrorStream<Self::Error>>;
}
macro_rules! tuple_combine_errors {
($($T:ident),*) => {
impl<$($T,)* E> CombineErrors for ($(Result<$T, ErrorStream<E>>,)*) {
type Ok = ($($T,)*);
type Error = E;
#[allow(non_snake_case)]
fn combine_errors(self) -> Result<Self::Ok, ErrorStream<Self::Error>> {
let mut errors = ErrorStream(Default::default());
let ($($T,)* ) = self;
$(
let $T = errors.unpack($T);
)*
if errors.0.is_empty() {
Ok(($($T.unwrap(),)*))
} else {
Err(errors)
}
}
}
};
}
tuple_combine_errors!(T1, T2);
tuple_combine_errors!(T1, T2, T3);
tuple_combine_errors!(T1, T2, T3, T4);
tuple_combine_errors!(T1, T2, T3, T4, T5);
tuple_combine_errors!(T1, T2, T3, T4, T5, T6);
tuple_combine_errors!(T1, T2, T3, T4, T5, T6, T7);
tuple_combine_errors!(T1, T2, T3, T4, T5, T6, T7, T8);
tuple_combine_errors!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
tuple_combine_errors!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
tuple_combine_errors!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
tuple_combine_errors!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
pub trait CollectAllErrors {
type Item;
type Error;
fn collect_all_errors<C: FromIterator<Self::Item>>(self) -> Result<C, ErrorStream<Self::Error>>;
}
impl<T, E, I: Iterator<Item = Result<T, ErrorStream<E>>>> CollectAllErrors for I {
type Item = T;
type Error = E;
fn collect_all_errors<Collection: FromIterator<Self::Item>>(self) -> Result<Collection, ErrorStream<Self::Error>> {
let mut all_errors = ErrorStream(Default::default());
let collection = self
.filter_map(|result| match result {
Ok(value) => Some(value),
Err(errors) => {
all_errors.extend(errors);
None
}
})
.collect::<Collection>();
if all_errors.0.is_empty() {
Ok(collection)
} else {
Err(all_errors)
}
}
}
#[macro_export]
macro_rules! expect_error_matching (
($result:expr, $expected:pat => $cond:expr) => {
let result: &::std::result::Result<
_,
$crate::error_stream::ErrorStream<_>
> = &$result;
match result {
Ok(_) => panic!("expected error, but got Ok"),
Err(errors) => {
let err = errors.iter().find(|error|
if let $expected = error {
$cond
} else {
false
}
);
if let None = err {
panic!("expected error matching `{}` satisfying `{}`,\n but got {:#?}", stringify!($expected), stringify!($cond), errors);
}
}
}
};
($result:expr, $expected:pat) => {
let result: &::std::result::Result<
_,
$crate::error_stream::ErrorStream<_>
> = &$result;
match result {
Ok(_) => panic!("expected error, but got Ok"),
Err(errors) => {
let err = errors.iter().find(|error| matches!(error, $expected));
if let None = err {
panic!("expected error matching `{}`,\n but got {:#?}", stringify!($expected), errors);
}
}
}
};
);
pub use expect_error_matching;
use smallvec::SmallVec;
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
enum MyError {
A(u32),
B,
}
type Result<T> = std::result::Result<T, ErrorStream<MyError>>;
#[test]
fn combine_errors() {
type ResultTuple = (Result<i32>, Result<String>, Result<u8>);
let tuple_1: ResultTuple = (Ok(1), Ok("hi".into()), Ok(3));
assert_eq!(tuple_1.combine_errors(), Ok((1, "hi".into(), 3)));
let tuple_2: ResultTuple = (Err(MyError::A(1).into()), Ok("hi".into()), Ok(3));
assert_eq!(tuple_2.combine_errors(), Err(MyError::A(1).into()));
let tuple_3: ResultTuple = (Err(MyError::A(1).into()), Err(MyError::A(2).into()), Ok(3));
assert_eq!(
tuple_3.combine_errors(),
Err(ErrorStream(smallvec::smallvec![MyError::A(1), MyError::A(2)]))
);
let tuple_4: ResultTuple = (
Err(MyError::A(1).into()),
Err(MyError::A(2).into()),
Err(MyError::A(3).into()),
);
assert_eq!(
tuple_4.combine_errors(),
Err(ErrorStream(smallvec::smallvec![
MyError::A(1),
MyError::A(2),
MyError::A(3)
]))
);
}
#[test]
fn collect_all_errors() {
let data: Vec<Result<i32>> = vec![Ok(1), Ok(2), Ok(3)];
assert_eq!(data.into_iter().collect_all_errors::<Vec<_>>(), Ok(vec![1, 2, 3]));
let data = vec![Ok(1), Err(MyError::A(0).into()), Ok(3)];
assert_eq!(
data.into_iter().collect_all_errors::<Vec<_>>(),
Err(ErrorStream([MyError::A(0)].into()))
);
let data: Vec<Result<i32>> = vec![
Err(MyError::A(1).into()),
Err(MyError::A(2).into()),
Err(MyError::A(3).into()),
];
assert_eq!(
data.into_iter().collect_all_errors::<Vec<_>>(),
Err(ErrorStream(smallvec::smallvec![
MyError::A(1),
MyError::A(2),
MyError::A(3)
]))
);
}
#[test]
#[should_panic]
fn expect_error_matching_without_cond_panics() {
let data: Result<()> = Err(ErrorStream(vec![MyError::B].into()));
expect_error_matching!(data, MyError::A(_));
}
#[test]
#[should_panic]
fn expect_error_matching_with_cond_panics() {
let data: Result<()> = Err(ErrorStream(vec![MyError::A(5), MyError::A(10)].into()));
expect_error_matching!(data, MyError::A(n) => n == &12);
}
}