use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use serde_json::Value;
use tokio::sync::RwLock;
use crate::output::Format;
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
pub type MiddlewareFn =
Arc<dyn Fn(MiddlewareContext, MiddlewareNext) -> BoxFuture<()> + Send + Sync>;
pub type MiddlewareNext = Box<dyn FnOnce() -> BoxFuture<()> + Send>;
pub struct MiddlewareContext {
pub agent: bool,
pub command: String,
pub env: Value,
pub format: Format,
pub format_explicit: bool,
pub name: String,
pub vars: Arc<RwLock<serde_json::Map<String, Value>>>,
pub version: Option<String>,
}
impl Clone for MiddlewareContext {
fn clone(&self) -> Self {
MiddlewareContext {
agent: self.agent,
command: self.command.clone(),
env: self.env.clone(),
format: self.format,
format_explicit: self.format_explicit,
name: self.name.clone(),
vars: Arc::clone(&self.vars),
version: self.version.clone(),
}
}
}
pub fn compose(
middlewares: &[MiddlewareFn],
ctx: MiddlewareContext,
final_handler: impl FnOnce() -> BoxFuture<()> + Send + 'static,
) -> BoxFuture<()> {
let mut next: Box<dyn FnOnce() -> BoxFuture<()> + Send> = Box::new(final_handler);
for mw in middlewares.iter().rev() {
let mw = Arc::clone(mw);
let ctx = ctx.clone();
let current_next = next;
next = Box::new(move || -> BoxFuture<()> {
Box::pin(async move {
mw(ctx, current_next).await;
})
});
}
next()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
fn make_ctx() -> MiddlewareContext {
MiddlewareContext {
agent: false,
command: "test".to_string(),
env: Value::Null,
format: Format::Toon,
format_explicit: false,
name: "test-cli".to_string(),
vars: Arc::new(RwLock::new(serde_json::Map::new())),
version: None,
}
}
#[tokio::test]
async fn test_compose_empty_middleware() {
let called = Arc::new(AtomicUsize::new(0));
let called_clone = Arc::clone(&called);
let future = compose(&[], make_ctx(), move || {
Box::pin(async move {
called_clone.fetch_add(1, Ordering::SeqCst);
})
});
future.await;
assert_eq!(called.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn test_compose_onion_order() {
let order = Arc::new(tokio::sync::Mutex::new(Vec::<String>::new()));
let order_a = Arc::clone(&order);
let mw_a: MiddlewareFn = Arc::new(move |_ctx, next| {
let order = Arc::clone(&order_a);
Box::pin(async move {
order.lock().await.push("A-before".to_string());
next().await;
order.lock().await.push("A-after".to_string());
})
});
let order_b = Arc::clone(&order);
let mw_b: MiddlewareFn = Arc::new(move |_ctx, next| {
let order = Arc::clone(&order_b);
Box::pin(async move {
order.lock().await.push("B-before".to_string());
next().await;
order.lock().await.push("B-after".to_string());
})
});
let order_final = Arc::clone(&order);
let future = compose(&[mw_a, mw_b], make_ctx(), move || {
Box::pin(async move {
order_final.lock().await.push("handler".to_string());
})
});
future.await;
let result = order.lock().await;
assert_eq!(
*result,
vec!["A-before", "B-before", "handler", "B-after", "A-after"]
);
}
}