rainier_middleware/
pipeline.rs1use std::any::TypeId;
28use std::sync::Arc;
29
30use rainier_http::{Request, Response};
31use rainier_support::BoxedFuture;
32
33pub trait ConcreteMiddleware: Send + Sync + 'static {
52 fn concrete_type_id(&self) -> TypeId;
54
55 fn concrete_type_name(&self) -> &'static str;
57}
58
59impl<T: Middleware> ConcreteMiddleware for T {
60 fn concrete_type_id(&self) -> TypeId {
61 TypeId::of::<T>()
62 }
63
64 fn concrete_type_name(&self) -> &'static str {
65 std::any::type_name::<T>()
66 }
67}
68
69#[async_trait::async_trait]
71pub trait Middleware: ConcreteMiddleware {
72 async fn handle(&self, request: Request, next: Next) -> Response;
78
79 fn name(&self) -> &'static str {
85 short_type_name(self.concrete_type_name())
86 }
87}
88
89pub(crate) fn short_type_name(full: &'static str) -> &'static str {
94 let without_generics = full.split_once('<').map_or(full, |(head, _)| head);
97 without_generics.rsplit("::").next().unwrap_or(without_generics)
98}
99
100pub trait Destination: Send + Sync + 'static {
105 fn call(&self, request: Request) -> BoxedFuture<Response>;
107}
108
109impl<F, Fut> Destination for F
112where
113 F: Fn(Request) -> Fut + Send + Sync + 'static,
114 Fut: std::future::Future<Output = Response> + Send + 'static,
115{
116 fn call(&self, request: Request) -> BoxedFuture<Response> {
117 Box::pin(self(request))
118 }
119}
120
121pub struct Next {
123 stages: Arc<[Arc<dyn Middleware>]>,
124 index: usize,
125 destination: Arc<dyn Destination>,
126}
127
128impl Next {
129 pub fn run(self, request: Request) -> BoxedFuture<Response> {
132 let Some(stage) = self.stages.get(self.index).cloned() else {
133 return self.destination.call(request);
134 };
135
136 let next =
137 Next { stages: self.stages, index: self.index + 1, destination: self.destination };
138
139 Box::pin(async move { stage.handle(request, next).await })
142 }
143
144 pub fn remaining(&self) -> usize {
146 self.stages.len().saturating_sub(self.index)
147 }
148}
149
150impl std::fmt::Debug for Next {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.debug_struct("Next").field("remaining", &self.remaining()).finish()
153 }
154}
155
156#[derive(Default)]
172pub struct Pipeline {
173 stages: Vec<Arc<dyn Middleware>>,
174}
175
176impl Pipeline {
177 pub fn new() -> Self {
179 Self::default()
180 }
181
182 pub fn through(mut self, middleware: impl Middleware) -> Self {
184 self.stages.push(Arc::new(middleware));
185 self
186 }
187
188 pub fn through_arc(mut self, middleware: Arc<dyn Middleware>) -> Self {
190 self.stages.push(middleware);
191 self
192 }
193
194 pub fn through_all(
196 mut self,
197 middleware: impl IntoIterator<Item = Arc<dyn Middleware>>,
198 ) -> Self {
199 self.stages.extend(middleware);
200 self
201 }
202
203 pub fn then(self, destination: impl Destination) -> ReadyPipeline {
205 self.then_arc(Arc::new(destination))
206 }
207
208 pub fn then_arc(self, destination: Arc<dyn Destination>) -> ReadyPipeline {
210 ReadyPipeline { stages: self.stages.into(), destination }
211 }
212
213 pub fn len(&self) -> usize {
215 self.stages.len()
216 }
217
218 pub fn is_empty(&self) -> bool {
220 self.stages.is_empty()
221 }
222}
223
224#[derive(Clone)]
228pub struct ReadyPipeline {
229 stages: Arc<[Arc<dyn Middleware>]>,
230 destination: Arc<dyn Destination>,
231}
232
233impl ReadyPipeline {
234 pub async fn run(&self, request: Request) -> Response {
236 Next {
237 stages: Arc::clone(&self.stages),
238 index: 0,
239 destination: Arc::clone(&self.destination),
240 }
241 .run(request)
242 .await
243 }
244
245 pub fn stage_names(&self) -> Vec<&'static str> {
247 self.stages.iter().map(|s| s.name()).collect()
248 }
249}
250
251impl std::fmt::Debug for ReadyPipeline {
252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253 f.debug_struct("ReadyPipeline").field("stages", &self.stage_names()).finish()
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use rainier_http::StatusCode;
261 use std::sync::Mutex;
262
263 struct Trace {
265 tag: &'static str,
266 log: Arc<Mutex<Vec<String>>>,
267 }
268
269 #[async_trait::async_trait]
270 impl Middleware for Trace {
271 async fn handle(&self, request: Request, next: Next) -> Response {
272 self.log.lock().unwrap().push(format!("{}:before", self.tag));
273 let response = next.run(request).await;
274 self.log.lock().unwrap().push(format!("{}:after", self.tag));
275 response
276 }
277
278 fn name(&self) -> &'static str {
279 "Trace"
280 }
281 }
282
283 struct ShortCircuit;
284
285 #[async_trait::async_trait]
286 impl Middleware for ShortCircuit {
287 async fn handle(&self, _request: Request, _next: Next) -> Response {
288 Response::new(StatusCode::FORBIDDEN)
289 }
290 }
291
292 fn log() -> Arc<Mutex<Vec<String>>> {
293 Arc::new(Mutex::new(Vec::new()))
294 }
295
296 #[tokio::test]
297 async fn an_empty_pipeline_reaches_the_destination() {
298 let response = Pipeline::new()
299 .then(|_: Request| async { Response::text("hi") })
300 .run(Request::builder().build())
301 .await;
302 assert_eq!(response.status(), StatusCode::OK);
303 }
304
305 #[tokio::test]
306 async fn middleware_wraps_the_destination_in_order() {
307 let log = log();
308 let recorded = Arc::clone(&log);
309
310 let destination_log = Arc::clone(&log);
311 let response = Pipeline::new()
312 .through(Trace { tag: "outer", log: Arc::clone(&log) })
313 .through(Trace { tag: "inner", log: Arc::clone(&log) })
314 .then(move |_: Request| {
315 let log = Arc::clone(&destination_log);
316 async move {
317 log.lock().unwrap().push("handler".into());
318 Response::text("ok")
319 }
320 })
321 .run(Request::builder().build())
322 .await;
323
324 assert_eq!(response.status(), StatusCode::OK);
325 assert_eq!(
326 *recorded.lock().unwrap(),
327 vec!["outer:before", "inner:before", "handler", "inner:after", "outer:after"]
328 );
329 }
330
331 #[tokio::test]
332 async fn short_circuiting_skips_the_rest_and_the_handler() {
333 let log = log();
334 let recorded = Arc::clone(&log);
335
336 let response = Pipeline::new()
337 .through(Trace { tag: "outer", log: Arc::clone(&log) })
338 .through(ShortCircuit)
339 .through(Trace { tag: "never", log: Arc::clone(&log) })
340 .then(|_: Request| async { Response::text("unreachable") })
341 .run(Request::builder().build())
342 .await;
343
344 assert_eq!(response.status(), StatusCode::FORBIDDEN);
345 assert_eq!(*recorded.lock().unwrap(), vec!["outer:before", "outer:after"]);
347 }
348
349 #[tokio::test]
350 async fn middleware_can_rewrite_the_request_and_the_response() {
351 struct Tag;
352
353 #[async_trait::async_trait]
354 impl Middleware for Tag {
355 async fn handle(&self, mut request: Request, next: Next) -> Response {
356 request.merge_input(serde_json::json!({ "injected": "yes" }));
357 next.run(request).await.with_header("x-tagged", "1")
358 }
359 }
360
361 let response =
362 Pipeline::new()
363 .through(Tag)
364 .then(|request: Request| async move {
365 Response::text(request.input_or("injected", "no"))
366 })
367 .run(Request::builder().build())
368 .await;
369
370 assert_eq!(response.header("x-tagged"), Some("1"));
371 let body = response.into_http().into_body().collect().await.unwrap();
372 assert_eq!(body, "yes");
373 }
374
375 #[tokio::test]
376 async fn a_ready_pipeline_can_be_run_repeatedly() {
377 let pipeline = Pipeline::new()
378 .through(Trace { tag: "a", log: log() })
379 .then(|_: Request| async { Response::text("ok") });
380
381 for _ in 0..3 {
382 assert_eq!(pipeline.run(Request::builder().build()).await.status(), StatusCode::OK);
383 }
384 assert_eq!(pipeline.stage_names(), vec!["Trace"]);
385 }
386
387 #[tokio::test]
388 async fn next_reports_how_much_is_left() {
389 struct CountRemaining(Arc<Mutex<Vec<usize>>>);
390
391 #[async_trait::async_trait]
392 impl Middleware for CountRemaining {
393 async fn handle(&self, request: Request, next: Next) -> Response {
394 self.0.lock().unwrap().push(next.remaining());
395 next.run(request).await
396 }
397 }
398
399 let seen = Arc::new(Mutex::new(Vec::new()));
400 Pipeline::new()
401 .through(CountRemaining(Arc::clone(&seen)))
402 .through(CountRemaining(Arc::clone(&seen)))
403 .then(|_: Request| async { Response::no_content() })
404 .run(Request::builder().build())
405 .await;
406
407 assert_eq!(*seen.lock().unwrap(), vec![1, 0]);
408 }
409}