a3s_boot/pipeline/
middleware.rs1use 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
6pub 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
22pub 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#[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#[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}