Skip to main content

webx_host/
pipeline.rs

1//! Middleware pipeline —Sequential execution model.
2//!
3//! Middlewares are called in registration order. Each middleware can
4//! inspect or modify the request. The final handler (router) is called
5//! after all middlewares have passed.
6
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10use webx_core::error::{Error, Result};
11use webx_core::http::IHttpContext;
12use webx_core::middleware::IMiddleware;
13
14/// Boxed final handler function type.
15pub type HandlerFn = Arc<
16    dyn for<'a> Fn(
17            &'a mut dyn IHttpContext,
18        ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
19        + Send
20        + Sync,
21>;
22
23pub struct MiddlewarePipeline {
24    middlewares: Vec<Arc<dyn IMiddleware>>,
25}
26
27impl MiddlewarePipeline {
28    pub fn new() -> Self {
29        Self {
30            middlewares: Vec::new(),
31        }
32    }
33
34    pub fn add_middleware(&mut self, middleware: Arc<dyn IMiddleware>) {
35        self.middlewares.push(middleware);
36    }
37
38    /// Execute middleware onion: invoke forward, final handler, after hooks reverse.
39    ///
40    /// Supports short-circuit: if a middleware's `invoke` returns
41    /// `ControlFlow::Break(())`, remaining middleware and the final handler are
42    /// skipped.
43    ///
44    /// `after` hooks always run for the middleware whose `invoke` completed,
45    /// including when the request fails: an error raised by a middleware or by
46    /// the final handler must still let metrics, tracing, gzip and cleanup
47    /// observe the response. The first error is returned after the unwind; a
48    /// failure inside an `after` hook is logged and never masks the original
49    /// error.
50    pub async fn execute(
51        &self,
52        ctx: &mut dyn IHttpContext,
53        final_handler: HandlerFn,
54    ) -> Result<()> {
55        use std::ops::ControlFlow;
56
57        let mut executed: Vec<Arc<dyn IMiddleware>> = Vec::new();
58        let mut short_circuited = false;
59        let mut failure: Option<Error> = None;
60
61        // Forward pass: invoke each middleware, track executed for after hooks
62        for middleware in &self.middlewares {
63            match middleware.invoke(ctx).await {
64                Ok(ControlFlow::Continue(())) => executed.push(Arc::clone(middleware)),
65                Ok(ControlFlow::Break(())) => {
66                    short_circuited = true;
67                    break;
68                }
69                Err(err) => {
70                    failure = Some(err);
71                    break;
72                }
73            }
74        }
75
76        // Run the final handler (router) only if not short-circuited and the
77        // forward pass did not already fail.
78        if !short_circuited && failure.is_none() {
79            if let Err(err) = final_handler(ctx).await {
80                failure = Some(err);
81            }
82        }
83
84        // Reverse pass: after hooks on executed middleware only, error path
85        // included.
86        for middleware in executed.into_iter().rev() {
87            if let Err(err) = middleware.after(ctx).await {
88                if failure.is_none() {
89                    failure = Some(err);
90                } else {
91                    tracing::error!(error = %err, "middleware after hook failed");
92                }
93            }
94        }
95
96        match failure {
97            Some(err) => Err(err),
98            None => Ok(()),
99        }
100    }
101}
102
103impl Default for MiddlewarePipeline {
104    fn default() -> Self {
105        Self::new()
106    }
107}