pipx 0.1.0

Composable pipelines for Rust with synchronous and asynchronous execution, middleware-style processing, and predictable data flow.
Documentation
use std::sync::Arc;

use pipx::{Next, Pipe, Pipeline, PipelineError, PipelineResult, StepFailure};

#[derive(Debug)]
struct Signup {
    email: String,
    password: String,
    normalized: bool,
}

struct NormalizeEmail;

impl Pipe<Signup> for NormalizeEmail {
    fn handle(&self, mut passable: Signup, next: Next<'_, Signup>) -> PipelineResult<Signup> {
        passable.email = passable.email.trim().to_lowercase();
        passable.normalized = true;

        next.handle(passable)
    }
}

struct ValidateEmail;

impl Pipe<Signup> for ValidateEmail {
    fn handle(&self, passable: Signup, next: Next<'_, Signup>) -> PipelineResult<Signup> {
        if passable.email.contains('@') {
            next.handle(passable)
        } else {
            Err(PipelineError::StepFailure(StepFailure {
                step: "ValidateEmail",
                message: "email must contain @".to_string(),
            }))
        }
    }
}

struct ValidatePassword;

impl Pipe<Signup> for ValidatePassword {
    fn handle(&self, passable: Signup, next: Next<'_, Signup>) -> PipelineResult<Signup> {
        if passable.password.len() >= 12 {
            next.handle(passable)
        } else {
            Err(PipelineError::StepFailure(StepFailure {
                step: "ValidatePassword",
                message: "password must be at least 12 characters".to_string(),
            }))
        }
    }
}

fn main() -> pipx::PipelineResult<()> {
    let signup = Signup {
        email: " USER@example.COM ".to_string(),
        password: "correct horse".to_string(),
        normalized: false,
    };

    let signup = Pipeline::new()
        .send(signup)
        .through(vec![
            Arc::new(NormalizeEmail),
            Arc::new(ValidateEmail),
            Arc::new(ValidatePassword),
        ])
        .then_return()?;

    println!("{signup:?}");
    Ok(())
}