rok-core 0.6.0

Core primitives for the rok ecosystem — errors, crypto, i18n, config, DI, and more
Documentation
/// Trait for pipe implementations.
///
/// Any closure `\|item, next\| { ... }` automatically implements `Pipe<T>`.
/// Named structs can also implement it directly.
pub trait Pipe<T: 'static> {
    fn handle(&self, item: T, next: Box<dyn FnOnce(T) -> T>) -> T;
}

/// Blanket impl: any `Fn` closure works as a pipe.
impl<T: 'static, F> Pipe<T> for F
where
    F: Fn(T, Box<dyn FnOnce(T) -> T>) -> T + 'static,
{
    fn handle(&self, item: T, next: Box<dyn FnOnce(T) -> T>) -> T {
        (self)(item, next)
    }
}

/// Send a value through a series of pipes.
///
/// # Example
///
/// ```rust,ignore
/// use rok_core::pipeline::{Pipeline, Pipe};
///
/// fn add_exclamation(item: String, next: Box<dyn FnOnce(String) -> String>) -> String {
///     next(format!("{}!", item))
/// }
///
/// fn main() {
///     let result = Pipeline::new()
///         .send("hello".to_string())
///         .through(add_exclamation)
///         .through(|item, next| next(format!("{}?", item)))
///         .then(|item| item.to_uppercase());
///
///     assert_eq!(result, "HELLO!?");
/// }
/// ```
pub struct Pipeline<T: 'static> {
    item: Option<T>,
    pipes: Vec<Box<dyn FnOnce(T, Box<dyn FnOnce(T) -> T>) -> T>>,
}

impl<T: 'static> Pipeline<T> {
    pub fn new() -> Self {
        Self { item: None, pipes: Vec::new() }
    }

    /// Set the value to pipe through.
    pub fn send(mut self, item: T) -> Self {
        self.item = Some(item);
        self
    }

    /// Add a pipe. Accepts any [`Pipe<T>`] implementor (closures, named structs).
    pub fn through<P: Pipe<T> + 'static>(mut self, pipe: P) -> Self {
        self.pipes.push(Box::new(move |item, next| pipe.handle(item, next)));
        self
    }

    /// Run the pipeline with a final destination callback.
    pub fn then<F>(self, destination: F) -> T
    where
        F: FnOnce(T) -> T + 'static,
    {
        let item = self.item.expect("Pipeline::then called before send");
        let mut chain: Box<dyn FnOnce(T) -> T> = Box::new(destination);
        for pipe in self.pipes.into_iter().rev() {
            let prev = chain;
            chain = Box::new(move |item| pipe(item, prev));
        }
        chain(item)
    }
}

impl<T: 'static> Default for Pipeline<T> {
    fn default() -> Self {
        Self::new()
    }
}