#![deny(missing_docs, unsafe_code)]
use std::{any::Any, error::Error, sync::Arc};
use thiserror::Error;
pub mod prelude {
pub use crate::{Pipeline, PipelineBuilder, PipelineContext, PipelineError};
pub use libmw_macro::PipelineContext;
}
type PredicateThunk = fn(&mut dyn PipelineContext) -> bool;
type BranchThunk = fn(&mut PipelineBuilder);
type MiddlewareThunk = fn(&mut dyn PipelineContext, Pipeline) -> Result<(), Box<dyn Error>>;
type MiddlewareTraitThunk = Box<dyn Fn(&mut dyn PipelineContext, Pipeline) -> Result<(), Box<dyn Error>>>;
type Thunk = Arc<dyn Fn(&mut dyn PipelineContext) -> Result<(), Box<dyn Error>>>;
pub struct Pipeline {
next: Option<Thunk>,
}
impl Pipeline {
#[must_use]
pub fn invoke(&self, ctx: &mut dyn PipelineContext) -> Result<(), Box<dyn Error>> {
if self.next.is_none() {
return Ok(());
}
(*(self.next.as_ref().unwrap()))(ctx)?;
Ok(())
}
}
#[derive(Error, Debug)]
pub enum PipelineError {
#[error("{0}")]
Generic(String),
}
pub trait PipelineContext {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
pub struct PipelineBuilder {
middleware: Vec<MiddlewareTraitThunk>,
}
impl PipelineBuilder {
pub fn new() -> Self {
Self { middleware: Vec::new() }
}
pub fn with(&mut self, middleware: MiddlewareThunk) {
self.middleware.push(Box::new(middleware));
}
pub fn when(&mut self, predicate: PredicateThunk, builder: BranchThunk) {
let mut branch_builder = PipelineBuilder::new();
builder(&mut branch_builder);
let branch = branch_builder.assemble();
self.middleware.push(Box::new(move |ctx, next| {
if predicate(ctx) {
return branch.invoke(ctx);
}
next.invoke(ctx)
}));
}
pub fn assemble(self) -> Pipeline {
let Self { middleware } = self;
let mut chain: Option<Pipeline> = None;
let mut iter = middleware.into_iter().rev();
while let Some(mw) = iter.next() {
if chain.is_none() {
chain = Some(Pipeline {
next: Some(Arc::new(move |ctx| mw(ctx, Pipeline { next: None }))),
});
continue;
}
let n = chain.take().unwrap().next.take().unwrap();
chain = Some(Pipeline {
next: Some(Arc::new(move |ctx| mw(ctx, Pipeline { next: Some(n.clone()) }))),
});
}
chain.unwrap()
}
}
#[cfg(test)]
mod tests {
use libmw_macro::PipelineContext;
use super::*;
#[derive(PipelineContext)]
struct Context {
take_branch: bool,
}
#[test]
fn it_works() {
let mut builder = PipelineBuilder::new();
builder.with(|ctx, next| {
println!("before in closure");
next.invoke(ctx)?;
println!("after in closure");
Ok(())
});
builder.when(
|ctx| match ctx.as_any().downcast_ref::<Context>() {
Some(c) => c.take_branch,
None => false,
},
|builder| {
builder.with(|ctx, next| {
println!("branch handler 1 before");
next.invoke(ctx)?;
println!("branch handler 1 after");
Ok(())
});
builder.with(|ctx, next| {
println!("branch handler 2 before");
next.invoke(ctx)?;
println!("branch handler 2 after");
Ok(())
});
},
);
builder.with(|ctx, next| {
println!("before in last closure");
next.invoke(ctx)?;
println!("after in last closure");
Ok(())
});
let pipeline = builder.assemble();
let mut context = Context { take_branch: true };
let result = pipeline.invoke(&mut context);
match result {
Ok(_) => {
}
Err(e) => {
println!("{:#?}", e);
}
}
let mut context = Context { take_branch: false };
let result = pipeline.invoke(&mut context);
match result {
Ok(_) => {
}
Err(e) => {
println!("{:#?}", e);
}
}
println!("It did not crash!");
}
}