You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
videl/src/error/aggregate.rs

120 lines
4.6 KiB

//! Aggregate errors
use super::*;
use std::result::Result;
/// An aggregate of 2 operations that *might* have failed.
///
/// In order for all aggregate operations to propagate a value, they must all succeed.
/// However, will return the errors of failed ones and continue propagating an `Err()` variant, so you get any of the error(s) from the previous aggregate calls.
///
/// This produces a potential tree of errors, which the `Error` trait cannot represent, so the reporting for `Aggregate` is a bit shit so far, but its general rule is:
/// * Debug formatter prints all of the first and second errors including their sub-errors. So, the entire tree. But in an ugly and hard to read way
/// * The default formatter prints only the *first* error, but also only if the second error exists, because:
/// * The `source()` for the aggregate is first the *second* error, if exists, then the *first* if not.
///
/// This means, `Aggregate`'s message itself when it does form a tree will be the first branch of the tree, and its source will be the second branch. The second branch is signigicanly better formatted by `eyre`, so the results from the first branch might be a bit hard to decipher, but it's fit for purpose in smaller chains.
///
/// We prioratise the second branch as the source because that's the one least likely to be an `Aggregate` itself, and will have a more meaningful message trace when reported by `eyre`.
pub struct Aggregate<T,U>(Option<T>, Option<U>);
pub trait AggregateExt<T,E>: Sized
{
/// Aggregate this result with a function that can take the value if suceeded and return its own value, or a reference to the error and return either its own error or *both*
///
/// # How it works
/// `Operation 2` is always ran, and gets the value from `operation 1` if it suceeded.
/// There is never a time where the closure will not be called except when there is a panic
///
/// * If `operation 1` and `2` fail: The aggregate containing both errors returns
/// * If `operation 1` succeeds but operation 2 does not: the error from `operation 2` is returned
/// * If `operation 2` succeeds but `operation 1` does not: the error from `operation 1` is returned
/// * If both succeed: the value from `operation 2` is returned.
fn aggregate_with<F,U,V>(self, next: F) -> Result<V, Aggregate<E, U>>
where F: FnOnce(Result<T,&E>) -> Result<V, U>;
/// Same as `aggregate_with` except it takes a result instead of taking a closure, so it is evaluated before the aggregation is called. In practive this has little difference, because all closures are called regardless, however it can help in certain situation where we don't need to know the value of pervious aggregate calls, or if we want to calls to happen even if there is a panic in one of the aggregations.
fn aggregate<U,V>(self, next: Result<V,U>) -> Result<(T,V), Aggregate<E,U>>;
}
impl<T,E> AggregateExt<T,E> for Result<T,E>
{
fn aggregate<U,V>(self, next: Result<V,U>) -> Result<(T,V), Aggregate<E,U>>
{
match self {
Ok(v) => match next {
Ok(v1) => Ok((v,v1)),
Err(err) => Err(Aggregate(None, Some(err))),
},
Err(er) => match next {
Ok(_) => Err(Aggregate(Some(er), None)),
Err(err) => Err(Aggregate(Some(er), Some(err))),
},
}
}
fn aggregate_with<F,U,V>(self, next: F) -> Result<V, Aggregate<E, U>>
where F: FnOnce(Result<T,&E>) -> Result<V, U>
{
match self {
Ok(v) => {
match next(Ok(v)) {
Err(err) => Err(Aggregate(None, Some(err))),
Ok(ok) => Ok(ok),
}
}
Err(e) => {
match next(Err(&e)) {
Err(err) => Err(Aggregate(Some(e), Some(err))),
Ok(_) => Err(Aggregate(Some(e), None)),
}
}
}
}
}
impl<T,U> fmt::Debug for Aggregate<T,U>
where T: fmt::Debug,
U: fmt::Debug
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "Aggregate Error ({:?}, {:?})", self.0, self.1)
}
}
impl<T,U> fmt::Display for Aggregate<T,U>
where T: error::Error,
U: error::Error,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "Aggregate Error ({})", self.0.as_ref().map(|_| 1).unwrap_or(0) + self.1.as_ref().map(|_| 1).unwrap_or(0))?;
if self.1.is_some() {
if let Some(one) = &self.0 {
writeln!(f, "\nBranch: {}", one)?;
for next in std::iter::successors(one.source(), |e| e.source())
{
writeln!(f, " -> {}", next)?;
}
}
}
Ok(())
}
}
impl<T,U> error::Error for Aggregate<T,U>
where T: error::Error + 'static,
U: error::Error + 'static,
{
fn source(&self) -> Option<&(dyn error::Error + 'static)>
{
if let Some(z) = &self.1 {
return Some(z);
}
if let Some(o) = &self.0 {
return Some(o);
}
None
}
}