Skip to main content

a3s_boot/pipeline/
middleware.rs

1use crate::routing::path::{match_path_shape, validate_route_path};
2use crate::{BootError, BootRequest, BootResponse, BoxFuture, HttpMethod, Result, RouteDefinition};
3use std::future::Future;
4use std::sync::Arc;
5
6/// Result of running a middleware.
7pub enum MiddlewareOutcome {
8    Continue(BootRequest),
9    Respond(BootResponse),
10}
11
12impl MiddlewareOutcome {
13    pub fn next(request: BootRequest) -> Self {
14        Self::Continue(request)
15    }
16
17    pub fn response(response: BootResponse) -> Self {
18        Self::Respond(response)
19    }
20}
21
22/// Request middleware that runs before guards, interceptors, pipes, and handlers.
23pub trait Middleware: Send + Sync + 'static {
24    fn handle(&self, request: BootRequest) -> BoxFuture<'static, Result<MiddlewareOutcome>>;
25}
26
27impl<F, Fut> Middleware for F
28where
29    F: Fn(BootRequest) -> Fut + Send + Sync + 'static,
30    Fut: Future<Output = Result<MiddlewareOutcome>> + Send + 'static,
31{
32    fn handle(&self, request: BootRequest) -> BoxFuture<'static, Result<MiddlewareOutcome>> {
33        Box::pin(self(request))
34    }
35}
36
37/// Route selector used by [`MiddlewareConsumer`].
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct MiddlewareRoute {
40    method: Option<HttpMethod>,
41    path: String,
42}
43
44impl MiddlewareRoute {
45    pub fn any(path: impl Into<String>) -> Result<Self> {
46        Self::new(None, path)
47    }
48
49    pub fn all(path: impl Into<String>) -> Result<Self> {
50        Self::any(path)
51    }
52
53    pub fn method(method: HttpMethod, path: impl Into<String>) -> Result<Self> {
54        Self::new(Some(method), path)
55    }
56
57    pub fn get(path: impl Into<String>) -> Result<Self> {
58        Self::method(HttpMethod::Get, path)
59    }
60
61    pub fn post(path: impl Into<String>) -> Result<Self> {
62        Self::method(HttpMethod::Post, path)
63    }
64
65    pub fn put(path: impl Into<String>) -> Result<Self> {
66        Self::method(HttpMethod::Put, path)
67    }
68
69    pub fn patch(path: impl Into<String>) -> Result<Self> {
70        Self::method(HttpMethod::Patch, path)
71    }
72
73    pub fn delete(path: impl Into<String>) -> Result<Self> {
74        Self::method(HttpMethod::Delete, path)
75    }
76
77    pub fn options(path: impl Into<String>) -> Result<Self> {
78        Self::method(HttpMethod::Options, path)
79    }
80
81    pub fn head(path: impl Into<String>) -> Result<Self> {
82        Self::method(HttpMethod::Head, path)
83    }
84
85    pub fn path(&self) -> &str {
86        &self.path
87    }
88
89    pub fn http_method(&self) -> Option<HttpMethod> {
90        self.method
91    }
92
93    fn new(method: Option<HttpMethod>, path: impl Into<String>) -> Result<Self> {
94        let path = path.into();
95        validate_route_path(&path)?;
96        Ok(Self {
97            method: method.filter(|method| !method.is_wildcard()),
98            path,
99        })
100    }
101
102    pub(crate) fn matches(&self, route_method: HttpMethod, path_candidates: &[String]) -> bool {
103        self.matches_method(route_method)
104            && path_candidates
105                .iter()
106                .any(|path| match_path_shape(&self.path, path))
107    }
108
109    fn matches_method(&self, route_method: HttpMethod) -> bool {
110        match self.method {
111            Some(method) => method == route_method || route_method.is_wildcard(),
112            None => true,
113        }
114    }
115}
116
117/// Nest-style route-scoped middleware configuration.
118#[derive(Clone, Default)]
119pub struct MiddlewareConsumer {
120    entries: Vec<MiddlewareConsumerEntry>,
121}
122
123impl MiddlewareConsumer {
124    pub fn new() -> Self {
125        Self::default()
126    }
127
128    pub fn apply<M>(&mut self, middleware: M) -> MiddlewareConsumerBuilder<'_>
129    where
130        M: Middleware,
131    {
132        self.apply_arc(Arc::new(middleware))
133    }
134
135    pub fn apply_arc(&mut self, middleware: Arc<dyn Middleware>) -> MiddlewareConsumerBuilder<'_> {
136        MiddlewareConsumerBuilder {
137            consumer: self,
138            middleware: vec![middleware],
139            excluded: Vec::new(),
140        }
141    }
142
143    pub fn extend(&mut self, other: MiddlewareConsumer) {
144        self.entries.extend(other.entries);
145    }
146
147    pub(crate) fn apply_to_route(
148        &self,
149        route: RouteDefinition,
150        path_candidates: &[String],
151    ) -> RouteDefinition {
152        let mut middleware = Vec::new();
153        for entry in &self.entries {
154            if entry.matches(route.method(), path_candidates) {
155                middleware.extend(entry.middleware.iter().cloned());
156            }
157        }
158
159        if middleware.is_empty() {
160            route
161        } else {
162            route.with_middleware_prefix_arc(&middleware)
163        }
164    }
165}
166
167pub struct MiddlewareConsumerBuilder<'a> {
168    consumer: &'a mut MiddlewareConsumer,
169    middleware: Vec<Arc<dyn Middleware>>,
170    excluded: Vec<MiddlewareRoute>,
171}
172
173impl MiddlewareConsumerBuilder<'_> {
174    pub fn and<M>(mut self, middleware: M) -> Self
175    where
176        M: Middleware,
177    {
178        self.middleware.push(Arc::new(middleware));
179        self
180    }
181
182    pub fn and_arc(mut self, middleware: Arc<dyn Middleware>) -> Self {
183        self.middleware.push(middleware);
184        self
185    }
186
187    pub fn exclude<I>(mut self, routes: I) -> Self
188    where
189        I: IntoIterator<Item = MiddlewareRoute>,
190    {
191        self.excluded.extend(routes);
192        self
193    }
194
195    pub fn exclude_route(mut self, route: MiddlewareRoute) -> Self {
196        self.excluded.push(route);
197        self
198    }
199
200    pub fn for_route(self, route: MiddlewareRoute) -> Result<()> {
201        self.for_routes([route])
202    }
203
204    pub fn for_routes<I>(self, routes: I) -> Result<()>
205    where
206        I: IntoIterator<Item = MiddlewareRoute>,
207    {
208        let routes = routes.into_iter().collect::<Vec<_>>();
209        if routes.is_empty() {
210            return Err(BootError::BadRequest(
211                "middleware consumer requires at least one route".to_string(),
212            ));
213        }
214        self.register(routes);
215        Ok(())
216    }
217
218    pub fn for_all_routes(self) -> Result<()> {
219        self.register(Vec::new());
220        Ok(())
221    }
222
223    fn register(self, routes: Vec<MiddlewareRoute>) {
224        self.consumer.entries.push(MiddlewareConsumerEntry {
225            middleware: self.middleware,
226            routes,
227            excluded: self.excluded,
228        });
229    }
230}
231
232#[derive(Clone)]
233struct MiddlewareConsumerEntry {
234    middleware: Vec<Arc<dyn Middleware>>,
235    routes: Vec<MiddlewareRoute>,
236    excluded: Vec<MiddlewareRoute>,
237}
238
239impl MiddlewareConsumerEntry {
240    fn matches(&self, route_method: HttpMethod, path_candidates: &[String]) -> bool {
241        let included = self.routes.is_empty()
242            || self
243                .routes
244                .iter()
245                .any(|route| route.matches(route_method, path_candidates));
246        let excluded = self
247            .excluded
248            .iter()
249            .any(|route| route.matches(route_method, path_candidates));
250
251        included && !excluded
252    }
253}