modulink_rs/chains/
mod.rs

1//! Chain struct for composing and running links with context, middleware, and branching.
2//!
3//! # Shadowing Policy (Ergonomic APIs)
4//! For ergonomic usage (`Chain`, `Link`, `Context`), always use variable shadowing (e.g., `let ctx = ...`) instead of `mut`.
5//! This prevents accidental mutation and is safer for async/concurrent code. See migration plan for details.
6//!
7//! Example (ergonomic, shadowing):
8//! ```rust
9//! use modulink_rs::context::Context;
10//! let ctx = Context::new();
11//! let ctx = ctx.insert("a", 1);
12//! let ctx = ctx.insert("b", 2);
13//! ```
14//!
15//! Advanced/generic APIs may use `mut` for performance, but must document the tradeoff.
16
17use std::sync::Arc;
18use std::future::Future;
19use std::pin::Pin;
20
21// Generic Chain: works with any context type (Context, MutableContext, or user-defined)
22pub struct ChainGeneric<T> {
23    links: Vec<Arc<dyn Fn(T) -> Pin<Box<dyn Future<Output = T> + Send>> + Send + Sync>>,
24    middleware: Vec<Arc<dyn crate::middleware::Middleware<T>>>,
25    pub branches: Vec<Branch<T>>,
26}
27
28pub struct Branch<T> {
29    pub source: usize,
30    pub target: usize,
31    pub condition: Arc<dyn Fn(&T) -> bool + Send + Sync>,
32}
33
34impl<T: 'static + Send> ChainGeneric<T> {
35    pub fn new() -> Self {
36        ChainGeneric { links: Vec::new(), middleware: Vec::new(), branches: Vec::new() }
37    }
38    pub fn add_link(&mut self, link: Arc<dyn Fn(T) -> Pin<Box<dyn Future<Output = T> + Send>> + Send + Sync>) {
39        self.links.push(link);
40    }
41    pub fn use_middleware(&mut self, mw: Arc<dyn crate::middleware::Middleware<T>>) {
42        self.middleware.push(mw);
43    }
44    pub fn link_count(&self) -> usize {
45        self.links.len()
46    }
47    pub fn connect<F>(&mut self, source: usize, target: usize, condition: F)
48    where
49        F: Fn(&T) -> bool + Send + Sync + 'static,
50    {
51        self.branches.push(Branch {
52            source,
53            target,
54            condition: Arc::new(condition),
55        });
56    }
57    pub async fn run(&self, ctx: T) -> T {
58        let mut idx = 0;
59        let mut ctx = ctx;
60        while idx < self.links.len() {
61            for mw in &self.middleware {
62                mw.before(&ctx).await;
63            }
64            ctx = (self.links[idx].clone())(ctx).await;
65            for mw in &self.middleware {
66                mw.after(&ctx).await;
67            }
68            // Check for branch
69            if let Some(branch) = self.branches.iter().find(|b| b.source == idx && (b.condition)(&ctx)) {
70                idx = branch.target;
71            } else {
72                idx += 1;
73            }
74        }
75        ctx
76    }
77}
78
79// Ergonomic defaults
80pub type Chain = ChainGeneric<crate::context::Context>;
81pub type LinkGeneric<C> = crate::links::LinkGeneric<C>;
82pub type Link = crate::links::Link;
83
84// Optionally, re-export as Chain/Link for crate root (see lib.rs)
85// pub use Chain as Chain;
86// pub use Link as Link;