use crate::http::response::Body;
use crate::routing::handler::BoxedHandler;
use hyper::{Request, Response};
use std::sync::Arc;
pub struct Next<S> {
pub(crate) handler: BoxedHandler<S>,
pub(crate) state: Arc<S>,
}
impl<S> std::fmt::Debug for Next<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Next").finish_non_exhaustive()
}
}
impl<S: Send + Sync + 'static> Next<S> {
#[inline]
pub async fn run(self, req: Request<Body>) -> Response<Body> {
let Self { handler, state } = self;
handler(req, state).await
}
#[inline]
#[must_use]
pub fn state(&self) -> &S {
&self.state
}
}
pub type BoxedMiddleware<S> =
Arc<dyn Fn(Request<Body>, Next<S>) -> crate::routing::handler::BoxedFuture + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MiddlewarePosition {
First,
Last,
}
#[derive(Clone)]
pub struct MethodHandler<S> {
pub(crate) raw: BoxedHandler<S>,
pub(crate) middlewares: Vec<BoxedMiddleware<S>>,
pub(crate) compiled: Option<BoxedHandler<S>>,
}
impl<S> std::fmt::Debug for MethodHandler<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MethodHandler")
.field("middlewares_count", &self.middlewares.len())
.field("compiled", &self.compiled.is_some())
.finish_non_exhaustive()
}
}
impl<S: Send + Sync + 'static> MethodHandler<S> {
pub fn new(raw: BoxedHandler<S>) -> Self {
Self {
raw,
middlewares: Vec::new(),
compiled: None,
}
}
pub fn compile_in_place(&mut self) {
if self.compiled.is_none() {
let mut handler = self.raw.clone();
for mw in self.middlewares.iter().rev() {
let h = handler.clone();
let mw_clone = mw.clone();
handler = Arc::new(move |req, state| {
let next = Next {
handler: h.clone(),
state,
};
mw_clone(req, next)
});
}
self.compiled = Some(handler);
}
}
pub async fn call(&self, req: Request<Body>, state: Arc<S>) -> Response<Body> {
if let Some(compiled) = &self.compiled {
compiled(req, state).await
} else {
let mut handler = self.raw.clone();
for mw in self.middlewares.iter().rev() {
let h = handler.clone();
let mw_clone = mw.clone();
handler = Arc::new(move |req, state| {
let next = Next {
handler: h.clone(),
state,
};
mw_clone(req, next)
});
}
handler(req, state).await
}
}
}
#[cfg(test)]
mod tests {
use super::{MethodHandler, MiddlewarePosition, Next};
use crate::http::response::{Body, IntoResponse};
use crate::routing::handler::{BoxedFuture, BoxedHandler, ResponseFuture};
use hyper::{Request, Response};
use std::sync::Arc;
fn handler_returning(body: &'static str) -> BoxedHandler<()> {
Arc::new(move |_req, _state| {
ResponseFuture::Boxed(Box::pin(async move { body.into_response() }))
})
}
fn tag_header_middleware(
name: &'static str,
) -> Arc<dyn Fn(Request<Body>, Next<()>) -> BoxedFuture + Send + Sync> {
Arc::new(move |req, next| {
ResponseFuture::Boxed(Box::pin(async move {
let mut resp = next.run(req).await;
resp.headers_mut()
.append("x-mw", name.parse().expect("valid header value"));
resp
}))
})
}
#[test]
fn middleware_position_variants_are_distinguishable() {
assert_ne!(MiddlewarePosition::First, MiddlewarePosition::Last);
}
#[test]
fn next_debug_does_not_panic() {
let next = Next {
handler: handler_returning("unused"),
state: Arc::new(()),
};
assert!(format!("{next:?}").contains("Next"));
}
#[test]
fn method_handler_debug_reports_middleware_count_and_compiled_state() {
let mut mh = MethodHandler::new(handler_returning("hi"));
assert!(format!("{mh:?}").contains("compiled: false"));
mh.middlewares.push(tag_header_middleware("a"));
mh.compile_in_place();
let debug = format!("{mh:?}");
assert!(debug.contains("middlewares_count: 1"));
assert!(debug.contains("compiled: true"));
}
#[tokio::test]
async fn call_runs_the_middleware_chain_even_when_not_yet_compiled() {
let mut mh = MethodHandler::new(handler_returning("body"));
mh.middlewares.push(tag_header_middleware("outer"));
mh.middlewares.push(tag_header_middleware("inner"));
assert!(mh.compiled.is_none());
let req = Request::builder().body(Body::empty()).unwrap();
let resp = mh.call(req, Arc::new(())).await;
let tags: Vec<&str> = resp
.headers()
.get_all("x-mw")
.iter()
.map(|v| v.to_str().unwrap())
.collect();
assert_eq!(tags, vec!["inner", "outer"]);
}
#[tokio::test]
async fn call_uses_the_cached_compiled_handler_once_compiled() {
let mut mh = MethodHandler::new(handler_returning("body"));
mh.middlewares.push(tag_header_middleware("only"));
mh.compile_in_place();
assert!(mh.compiled.is_some());
let req = Request::builder().body(Body::empty()).unwrap();
let resp: Response<Body> = mh.call(req, Arc::new(())).await;
assert_eq!(
resp.headers().get("x-mw").unwrap().to_str().unwrap(),
"only"
);
}
}