Skip to main content

httpward_core/httpward_middleware/
pipe.rs

1// File: httpward-core/src/httpward_middleware/pipe.rs
2
3use std::os::raw::c_void;
4use std::sync::Arc;
5use crate::httpward_middleware::middleware_trait::HttpWardMiddleware;
6use crate::httpward_middleware::next::Next;
7use crate::httpward_middleware::types::BoxError;
8use rama::http::{Body, Request, Response};
9use rama::Context;
10use rama::service::Service;
11use std::fmt;
12
13/// Type alias for boxed middleware stored in the internal Vec.
14/// Each middleware must be Send + Sync because the Vec will be shared between threads.
15type BoxedMiddleware = Arc<dyn HttpWardMiddleware>;
16
17/// Public wrapper around shared pipeline storage.
18/// The internal Vec is wrapped in an Arc so cloning the pipe is cheap (one atomic increment).
19#[derive(Clone)]
20pub struct HttpWardMiddlewarePipe {
21    inner: Arc<Vec<BoxedMiddleware>>,
22}
23
24impl Default for HttpWardMiddlewarePipe {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl fmt::Debug for HttpWardMiddlewarePipe {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        f.debug_struct("HttpWardMiddlewarePipe")
33            .field("middleware_count", &self.inner.len())
34            .finish()
35    }
36}
37
38impl HttpWardMiddlewarePipe {
39    /// Create an empty, cheap-to-clone pipe.
40    pub fn new() -> Self {
41        Self {
42            inner: Arc::new(Vec::new()),
43        }
44    }
45
46    /// Number of middlewares in the pipe.
47    pub fn len(&self) -> usize {
48        self.inner.len()
49    }
50
51    pub fn is_empty(&self) -> bool {
52        self.inner.is_empty()
53    }
54
55    /// Add a middleware to the pipe (returns a new pipe for builder pattern compatibility)
56    pub fn add_layer<M>(&self, mw: M) -> Self
57    where
58        M: HttpWardMiddleware + Send + Sync + 'static,
59    {
60        let mut new_vec = (*self.inner).clone();
61        new_vec.push(Arc::new(mw));
62        Self {
63            inner: Arc::new(new_vec),
64        }
65    }
66
67    /// Add a boxed middleware (Arc<dyn HttpWardMiddleware>) into the pipe.
68    /// This is useful when the middleware is created dynamically (plugins).
69    pub fn add_boxed_layer(&self, mw: BoxedMiddleware) -> Self {
70        // Clone the inner Vec and append the boxed middleware.
71        let mut new_vec = (*self.inner).clone();
72        new_vec.push(mw);
73        Self {
74            inner: Arc::new(new_vec),
75        }
76    }
77
78    /// Find layer by name (middleware may return a name via `name()`).
79    /// Returns a reference to the boxed middleware if found.
80    pub fn get_layer_by_name(
81        &self,
82        name: &str,
83    ) -> Option<&BoxedMiddleware> {
84        self.inner.iter().find(|m| m.name().map_or(false, |n| n == name))
85    }
86
87    /// Execute the middleware chain for a concrete inner service `S`.
88    ///
89    /// This converts the concrete `inner` service to a boxed type (`BoxService`) once,
90    /// borrows a slice from the internal Arc<Vec<...>> and runs the optimized `Next`.
91    /// Hot path: no atomic ops per middleware.
92    pub async fn execute_middleware<S>(
93        &self,
94        inner: S,
95        ctx: Context<()>,
96        req: Request<Body>,
97    ) -> Result<Response<Body>, BoxError>
98    where
99        S: Service<(), Request<Body>, Response = Response<Body>> + Clone + Send + Sync + 'static,
100        S::Error: std::error::Error + Send + Sync + 'static,
101    {
102        let slice: &[BoxedMiddleware] = &*self.inner;
103
104        // Convert the concrete service to BoxService
105        let boxed_service = crate::httpward_middleware::adapter::box_service_from(inner);
106
107        let next = Next::new(slice, &boxed_service);
108
109        next.run(ctx, req).await
110    }
111}
112
113/// Builder used during configuration time to accumulate Box<dyn Middleware>.
114/// Use this builder to add layers; then call `build()` to obtain the cheap-cloneable pipe.
115pub struct HttpWardMiddlewarePipeBuilder {
116    v: Vec<BoxedMiddleware>,
117}
118
119impl HttpWardMiddlewarePipeBuilder {
120    /// Create a new builder.
121    pub fn new() -> Self {
122        Self { v: Vec::new() }
123    }
124
125    /// Add a middleware instance (consumes the middleware).
126    /// Chaining is supported.
127    pub fn add_layer<M>(mut self, mw: M) -> Self
128    where
129        M: HttpWardMiddleware + Send + Sync + 'static,
130    {
131        self.v.push(Arc::new(mw));
132        self
133    }
134
135    /// Add a pre-boxed middleware (useful for plugins that already produce BoxedMiddleware).
136    pub fn push_box(mut self, mw: BoxedMiddleware) -> Self {
137        self.v.push(mw);
138        self
139    }
140
141    /// Finalize builder into a cheap-cloneable `HttpWardMiddlewarePipe`.
142    pub fn build(self) -> HttpWardMiddlewarePipe {
143        HttpWardMiddlewarePipe {
144            inner: Arc::new(self.v),
145        }
146    }
147}
148
149/// C-compatible representation of a fat pointer to dyn HttpWardMiddleware.
150#[repr(C)]
151pub struct MiddlewareFatPtr {
152    pub data: *mut c_void,
153    pub vtable: *mut c_void,
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use async_trait::async_trait;
160    use rama::http::{Body, Request, Response};
161    use rama::Context;
162    use crate::httpward_middleware::types::BoxError;
163    use crate::httpward_middleware::next::Next;
164
165    // Minimal test middleware to verify plumbing.
166    struct DummyMw;
167    #[async_trait]
168    impl HttpWardMiddleware for DummyMw {
169        async fn handle(&self, ctx: Context<()>, req: Request<Body>, next: Next<'_>) -> Result<Response<Body>, BoxError> {
170            // call next without changes
171            next.run(ctx, req).await
172        }
173    }
174
175    #[tokio::test]
176    async fn build_and_execute_pipe() {
177        // This test ensures builder + pipe compile — not a full integration.
178        let builder = HttpWardMiddlewarePipeBuilder::new()
179            .add_layer(DummyMw);
180        let pipe = builder.build();
181
182        // Can't easily run a full Rama service here — just check metadata.
183        assert_eq!(pipe.len(), 1);
184        assert!(!pipe.is_empty());
185    }
186}