Skip to main content

rskit_server/
middleware.rs

1use std::sync::Arc;
2
3use axum::Router;
4
5/// Canonical interceptor ordering shared across rskit transports.
6///
7/// Request processing flows through these phases in order. Metrics wrap the
8/// handler completion so response observations happen after the handler returns.
9pub const HTTP_INTERCEPTOR_ORDER: [&str; 5] =
10    ["tracing", "logging", "auth", "validation", "metrics"];
11
12/// Baseline HTTP transport layers applied by [`HttpServerBuilder`](crate::HttpServerBuilder).
13///
14/// These layers wrap every HTTP server before application middleware executes.
15pub const HTTP_BASELINE_LAYER_ORDER: [&str; 5] = [
16    "request_id",
17    "cors",
18    "security_headers",
19    "body_limit",
20    "timeout",
21];
22
23/// Boxed router transform used to inject transport middleware without exposing
24/// axum's concrete layer stack in the public API.
25pub type RouterTransform = Arc<dyn Fn(Router) -> Router + Send + Sync + 'static>;
26
27/// Ordered HTTP middleware phases for service-facing servers.
28#[derive(Clone, Default)]
29pub struct HttpMiddlewareStack {
30    tracing: Vec<RouterTransform>,
31    logging: Vec<RouterTransform>,
32    auth: Vec<RouterTransform>,
33    validation: Vec<RouterTransform>,
34    metrics: Vec<RouterTransform>,
35}
36
37impl HttpMiddlewareStack {
38    /// Create an empty middleware stack.
39    #[must_use]
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Append a tracing-phase transform.
45    #[must_use]
46    pub fn with_tracing_transform<F>(mut self, transform: F) -> Self
47    where
48        F: Fn(Router) -> Router + Send + Sync + 'static,
49    {
50        self.tracing.push(Arc::new(transform));
51        self
52    }
53
54    /// Append a logging-phase transform.
55    #[must_use]
56    pub fn with_logging_transform<F>(mut self, transform: F) -> Self
57    where
58        F: Fn(Router) -> Router + Send + Sync + 'static,
59    {
60        self.logging.push(Arc::new(transform));
61        self
62    }
63
64    /// Append an auth-phase transform.
65    #[must_use]
66    pub fn with_auth_transform<F>(mut self, transform: F) -> Self
67    where
68        F: Fn(Router) -> Router + Send + Sync + 'static,
69    {
70        self.auth.push(Arc::new(transform));
71        self
72    }
73
74    /// Append a validation-phase transform.
75    #[must_use]
76    pub fn with_validation_transform<F>(mut self, transform: F) -> Self
77    where
78        F: Fn(Router) -> Router + Send + Sync + 'static,
79    {
80        self.validation.push(Arc::new(transform));
81        self
82    }
83
84    /// Append a metrics-phase transform.
85    #[must_use]
86    pub fn with_metrics_transform<F>(mut self, transform: F) -> Self
87    where
88        F: Fn(Router) -> Router + Send + Sync + 'static,
89    {
90        self.metrics.push(Arc::new(transform));
91        self
92    }
93
94    /// Apply the configured phases around a router.
95    pub fn apply(&self, router: Router) -> Router {
96        let router = apply_phase(router, &self.metrics);
97        let router = apply_phase(router, &self.validation);
98        let router = apply_phase(router, &self.auth);
99        let router = apply_phase(router, &self.logging);
100        apply_phase(router, &self.tracing)
101    }
102}
103
104fn apply_phase(router: Router, transforms: &[RouterTransform]) -> Router {
105    transforms
106        .iter()
107        .fold(router, |router, transform| transform(router))
108}
109
110#[cfg(test)]
111mod tests {
112    use std::convert::Infallible;
113    use std::sync::Arc;
114
115    use axum::{Router, body::Body, http::Request, response::Response, routing::get};
116    use parking_lot::Mutex;
117    use tower::{Layer, Service, ServiceExt};
118
119    use super::HttpMiddlewareStack;
120
121    #[derive(Clone)]
122    struct RecordLayer {
123        name: &'static str,
124        events: Arc<Mutex<Vec<&'static str>>>,
125    }
126
127    impl RecordLayer {
128        fn new(name: &'static str, events: Arc<Mutex<Vec<&'static str>>>) -> Self {
129            Self { name, events }
130        }
131    }
132
133    impl<S> Layer<S> for RecordLayer {
134        type Service = RecordService<S>;
135
136        fn layer(&self, inner: S) -> Self::Service {
137            RecordService {
138                inner,
139                name: self.name,
140                events: Arc::clone(&self.events),
141            }
142        }
143    }
144
145    #[derive(Clone)]
146    struct RecordService<S> {
147        inner: S,
148        name: &'static str,
149        events: Arc<Mutex<Vec<&'static str>>>,
150    }
151
152    impl<S> Service<Request<Body>> for RecordService<S>
153    where
154        S: Service<Request<Body>, Response = Response, Error = Infallible> + Clone + Send + 'static,
155        S::Future: Send + 'static,
156    {
157        type Response = Response;
158        type Error = Infallible;
159        type Future = futures::future::BoxFuture<'static, Result<Response, Infallible>>;
160
161        fn poll_ready(
162            &mut self,
163            cx: &mut std::task::Context<'_>,
164        ) -> std::task::Poll<Result<(), Self::Error>> {
165            self.inner.poll_ready(cx)
166        }
167
168        fn call(&mut self, req: Request<Body>) -> Self::Future {
169            let mut inner = self.inner.clone();
170            let events = Arc::clone(&self.events);
171            let name = self.name;
172            Box::pin(async move {
173                events.lock().push(name);
174                inner.call(req).await
175            })
176        }
177    }
178
179    #[tokio::test]
180    async fn stack_applies_request_phases_in_locked_order() {
181        let events = Arc::new(Mutex::new(Vec::new()));
182        let app = Router::new().route("/", get(|| async { "ok" }));
183        let app = HttpMiddlewareStack::new()
184            .with_tracing_transform({
185                let events = Arc::clone(&events);
186                move |router| router.layer(RecordLayer::new("tracing", Arc::clone(&events)))
187            })
188            .with_logging_transform({
189                let events = Arc::clone(&events);
190                move |router| router.layer(RecordLayer::new("logging", Arc::clone(&events)))
191            })
192            .with_auth_transform({
193                let events = Arc::clone(&events);
194                move |router| router.layer(RecordLayer::new("auth", Arc::clone(&events)))
195            })
196            .with_validation_transform({
197                let events = Arc::clone(&events);
198                move |router| router.layer(RecordLayer::new("validation", Arc::clone(&events)))
199            })
200            .with_metrics_transform({
201                let events = Arc::clone(&events);
202                move |router| router.layer(RecordLayer::new("metrics", Arc::clone(&events)))
203            })
204            .apply(app);
205
206        let response = app
207            .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
208            .await
209            .unwrap();
210
211        assert_eq!(response.status(), axum::http::StatusCode::OK);
212        assert_eq!(
213            *events.lock(),
214            vec!["tracing", "logging", "auth", "validation", "metrics"]
215        );
216    }
217}