Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Error Accumulation — Collecting All Failures

effect! and flat_map are fail-fast. That is correct for dependent steps, but independent validation sometimes needs to collect multiple failures.

Fail-Fast Behavior

effect! {
    let name = bind* validate_name(&input.name);     // if this fails, stop
    let email = bind* validate_email(&input.email);  // not run after name failure
    let age = bind* validate_age(input.age);
    User { name, email, age }
}

Use fail-fast composition when each step depends on earlier successful values.

Manual Accumulation

The current root API does not include validate_all or partition helpers. For independent validations, run them as plain Result-returning checks or explicitly run each effect and collect errors.

let mut errors = Vec::new();

if let Err(error) = run_blocking(validate_name(&input.name), ()) {
    errors.push(error);
}
if let Err(error) = run_blocking(validate_email(&input.email), ()) {
    errors.push(error);
}
if let Err(error) = run_blocking(validate_age(input.age), ()) {
    errors.push(error);
}

if errors.is_empty() {
    Ok(())
} else {
    Err(errors)
}

Keep this pattern at validation boundaries. Inside business workflows, prefer typed fail-fast effects.

Combining Error Types

When composing effects with different error types, use map_error or flat_map_union with Or.

use effectful::Or;

type BothErrors = Or<DbError, NetworkError>;

let combined: Effect<Data, BothErrors, ()> = db_fetch()
    .union_error::<NetworkError>()
    .flat_map(|db| network_fetch().map_error(Or::Right).map(move |net| merge(db, net)));

Or<A, B> lets you defer flattening into an application error until a boundary.

ParseErrors

ParseErrors is an aggregate container for schema-style validation issues.

let errors = ParseErrors::new(vec![
    ParseError::new("name", "must not be empty"),
    ParseError::new("email", "invalid email"),
]);

for issue in errors.issues {
    eprintln!("{}: {}", issue.path, issue.message);
}

Current schema combinators usually return the first ParseError; build ParseErrors yourself when your boundary wants to report multiple issues.

When to Accumulate vs. Short-Circuit

SituationUse
Dependent stepseffect! / flat_map
Independent form validationManual accumulation
Batch import with partial successExplicit loop collecting successes/failures
Schema boundary with many field issuesParseErrors::new(collected)