diff --git a/src/routing/future.rs b/src/routing/future.rs index df0c4349..f6393ddb 100644 --- a/src/routing/future.rs +++ b/src/routing/future.rs @@ -1,19 +1,15 @@ //! Future types. -use crate::{body::BoxBody, clone_box_service::CloneBoxService}; +use crate::body::BoxBody; use futures_util::future::Either; use http::{Request, Response}; -use pin_project_lite::pin_project; -use std::{ - convert::Infallible, - future::{ready, Future}, - pin::Pin, - task::{Context, Poll}, -}; +use std::{convert::Infallible, future::ready}; use tower::util::Oneshot; -use tower_service::Service; -pub use super::method_not_allowed::MethodNotAllowedFuture; +pub use super::{ + into_make_service::IntoMakeService, method_not_allowed::MethodNotAllowedFuture, + route::RouteFuture, +}; opaque_future! { /// Response future for [`Router`](super::Router). @@ -33,62 +29,3 @@ impl RouterFuture { RouterFuture::new(Either::Right(ready(Ok(response)))) } } - -pin_project! { - /// Response future for [`Route`](super::Route). - pub struct RouteFuture { - #[pin] - future: Oneshot< - CloneBoxService, Response, Infallible>, - Request, - > - } -} - -impl RouteFuture { - pub(crate) fn new( - future: Oneshot, Response, Infallible>, Request>, - ) -> Self { - RouteFuture { future } - } -} - -impl Future for RouteFuture { - type Output = Result, Infallible>; - - #[inline] - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().future.poll(cx) - } -} - -pin_project! { - /// The response future for [`Nested`](super::Nested). - #[derive(Debug)] - pub(crate) struct NestedFuture - where - S: Service>, - { - #[pin] - pub(super) inner: Oneshot> - } -} - -impl Future for NestedFuture -where - S: Service, Response = Response, Error = Infallible>, - B: Send + Sync + 'static, -{ - type Output = Result, Infallible>; - - #[inline] - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().inner.poll(cx) - } -} - -opaque_future! { - /// Response future from [`MakeRouteService`] services. - pub type MakeRouteServiceFuture = - std::future::Ready>; -} diff --git a/src/routing/into_make_service.rs b/src/routing/into_make_service.rs new file mode 100644 index 00000000..046f485a --- /dev/null +++ b/src/routing/into_make_service.rs @@ -0,0 +1,57 @@ +use std::{ + convert::Infallible, + future::ready, + task::{Context, Poll}, +}; +use tower_service::Service; + +/// A [`MakeService`] that produces axum router services. +/// +/// [`MakeService`]: tower::make::MakeService +#[derive(Debug, Clone)] +pub struct IntoMakeService { + service: S, +} + +impl IntoMakeService { + pub(super) fn new(service: S) -> Self { + Self { service } + } +} + +impl Service for IntoMakeService +where + S: Clone, +{ + type Response = S; + type Error = Infallible; + type Future = MakeRouteServiceFuture; + + #[inline] + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _target: T) -> Self::Future { + MakeRouteServiceFuture::new(ready(Ok(self.service.clone()))) + } +} + +opaque_future! { + /// Response future from [`MakeRouteService`] services. + pub type MakeRouteServiceFuture = + std::future::Ready>; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn traits() { + use crate::tests::*; + + assert_send::>(); + assert_sync::>(); + } +} diff --git a/src/routing/method_not_allowed.rs b/src/routing/method_not_allowed.rs index 6e29490e..5dbc400f 100644 --- a/src/routing/method_not_allowed.rs +++ b/src/routing/method_not_allowed.rs @@ -67,3 +67,16 @@ opaque_future! { pub type MethodNotAllowedFuture = std::future::Ready, E>>; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn traits() { + use crate::tests::*; + + assert_send::>(); + assert_sync::>(); + } +} diff --git a/src/routing/mod.rs b/src/routing/mod.rs index 41de488b..8bb09d6c 100644 --- a/src/routing/mod.rs +++ b/src/routing/mod.rs @@ -1,10 +1,9 @@ //! Routing between [`Service`]s and handlers. -use self::future::{NestedFuture, RouteFuture, RouterFuture}; +use self::future::RouterFuture; use self::not_found::NotFound; use crate::{ body::{box_body, Body, BoxBody}, - clone_box_service::CloneBoxService, extract::{ connect_info::{Connected, IntoMakeServiceWithConnectInfo}, OriginalUri, @@ -19,7 +18,6 @@ use std::{ collections::HashMap, convert::Infallible, fmt, - future::ready, sync::Arc, task::{Context, Poll}, }; @@ -32,12 +30,15 @@ pub mod future; pub mod handler_method_router; pub mod service_method_router; +mod into_make_service; mod method_filter; mod method_not_allowed; +mod nested; mod not_found; +mod route; -pub use self::method_filter::MethodFilter; pub(crate) use self::method_not_allowed::MethodNotAllowed; +pub use self::{into_make_service::IntoMakeService, method_filter::MethodFilter, route::Route}; #[doc(no_inline)] pub use self::handler_method_router::{ @@ -353,7 +354,7 @@ where panic!("Invalid route: {}", err); } - self.routes.insert(id, Route::new(Nested { svc })); + self.routes.insert(id, Route::new(nested::Nested { svc })); self } @@ -754,7 +755,8 @@ where .collect::>(); if let Some(tail) = match_.params.get(NEST_TAIL_PARAM) { - req.extensions_mut().insert(NestMatchTail(tail.to_string())); + req.extensions_mut() + .insert(nested::NestMatchTail(tail.to_string())); } insert_url_params(&mut req, params); @@ -769,9 +771,6 @@ where } } -#[derive(Clone)] -struct NestMatchTail(String); - impl Service> for Router where B: Send + Sync + 'static, @@ -824,18 +823,33 @@ where } } -pub(crate) struct UriStack(Vec); - -impl UriStack { - fn push(req: &mut Request) { - let uri = req.uri().clone(); - - if let Some(stack) = req.extensions_mut().get_mut::() { - stack.0.push(uri); +fn with_path(uri: &Uri, new_path: &str) -> Uri { + let path_and_query = if let Some(path_and_query) = uri.path_and_query() { + let new_path = if new_path.starts_with('/') { + Cow::Borrowed(new_path) } else { - req.extensions_mut().insert(Self(vec![uri])); + Cow::Owned(format!("/{}", new_path)) + }; + + if let Some(query) = path_and_query.query() { + Some( + format!("{}?{}", new_path, query) + .parse::() + .unwrap(), + ) + } else { + Some(new_path.parse().unwrap()) } - } + } else { + None + }; + + let mut parts = http::uri::Parts::default(); + parts.scheme = uri.scheme().cloned(); + parts.authority = uri.authority().cloned(); + parts.path_and_query = path_and_query; + + Uri::from_parts(parts).unwrap() } // we store the potential error here such that users can handle invalid path @@ -881,150 +895,6 @@ pub(crate) struct InvalidUtf8InPathParam { pub(crate) key: ByteStr, } -/// A [`Service`] that has been nested inside a router at some path. -/// -/// Created with [`Router::nest`]. -#[derive(Debug, Clone)] -struct Nested { - svc: S, -} - -impl Service> for Nested -where - S: Service, Response = Response, Error = Infallible> + Clone, - B: Send + Sync + 'static, -{ - type Response = Response; - type Error = Infallible; - type Future = NestedFuture; - - #[inline] - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, mut req: Request) -> Self::Future { - // strip the prefix from the URI just before calling the inner service - // such that any surrounding middleware still see the full path - if let Some(tail) = req.extensions_mut().remove::() { - UriStack::push(&mut req); - let new_uri = with_path(req.uri(), &tail.0); - *req.uri_mut() = new_uri; - } - - NestedFuture { - inner: self.svc.clone().oneshot(req), - } - } -} - -fn with_path(uri: &Uri, new_path: &str) -> Uri { - let path_and_query = if let Some(path_and_query) = uri.path_and_query() { - let new_path = if new_path.starts_with('/') { - Cow::Borrowed(new_path) - } else { - Cow::Owned(format!("/{}", new_path)) - }; - - if let Some(query) = path_and_query.query() { - Some( - format!("{}?{}", new_path, query) - .parse::() - .unwrap(), - ) - } else { - Some(new_path.parse().unwrap()) - } - } else { - None - }; - - let mut parts = http::uri::Parts::default(); - parts.scheme = uri.scheme().cloned(); - parts.authority = uri.authority().cloned(); - parts.path_and_query = path_and_query; - - Uri::from_parts(parts).unwrap() -} - -/// A [`MakeService`] that produces axum router services. -/// -/// [`MakeService`]: tower::make::MakeService -#[derive(Debug, Clone)] -pub struct IntoMakeService { - service: S, -} - -impl IntoMakeService { - fn new(service: S) -> Self { - Self { service } - } -} - -impl Service for IntoMakeService -where - S: Clone, -{ - type Response = S; - type Error = Infallible; - type Future = future::MakeRouteServiceFuture; - - #[inline] - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, _target: T) -> Self::Future { - future::MakeRouteServiceFuture::new(ready(Ok(self.service.clone()))) - } -} - -/// How routes are stored inside a [`Router`]. -/// -/// You normally shouldn't need to care about this type. -pub struct Route(CloneBoxService, Response, Infallible>); - -impl Route { - fn new(svc: T) -> Self - where - T: Service, Response = Response, Error = Infallible> - + Clone - + Send - + 'static, - T::Future: Send + 'static, - { - Self(CloneBoxService::new(svc)) - } -} - -impl Clone for Route { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl fmt::Debug for Route { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Route").finish() - } -} - -impl Service> for Route { - type Response = Response; - type Error = Infallible; - type Future = RouteFuture; - - #[inline] - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - #[inline] - fn call(&mut self, req: Request) -> Self::Future { - RouteFuture::new(self.0.clone().oneshot(req)) - } -} - /// Wrapper around `matchit::Node` that supports merging two `Node`s. #[derive(Clone, Default)] struct Node { @@ -1109,16 +979,5 @@ mod tests { use crate::tests::*; assert_send::>(); - - assert_send::>(); - - assert_send::>(); - assert_sync::>(); - - assert_send::>(); - assert_sync::>(); - - assert_send::>(); - assert_sync::>(); } } diff --git a/src/routing/nested.rs b/src/routing/nested.rs new file mode 100644 index 00000000..9ef7a1e9 --- /dev/null +++ b/src/routing/nested.rs @@ -0,0 +1,69 @@ +use crate::body::BoxBody; +use http::{Request, Response, Uri}; +use std::{ + convert::Infallible, + task::{Context, Poll}, +}; +use tower::util::Oneshot; +use tower::ServiceExt; +use tower_service::Service; + +/// A [`Service`] that has been nested inside a router at some path. +/// +/// Created with [`Router::nest`]. +#[derive(Debug, Clone)] +pub(super) struct Nested { + pub(super) svc: S, +} + +impl Service> for Nested +where + S: Service, Response = Response, Error = Infallible> + Clone, + B: Send + Sync + 'static, +{ + type Response = Response; + type Error = Infallible; + type Future = Oneshot>; + + #[inline] + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, mut req: Request) -> Self::Future { + // strip the prefix from the URI just before calling the inner service + // such that any surrounding middleware still see the full path + if let Some(tail) = req.extensions_mut().remove::() { + UriStack::push(&mut req); + let new_uri = super::with_path(req.uri(), &tail.0); + *req.uri_mut() = new_uri; + } + + self.svc.clone().oneshot(req) + } +} + +pub(crate) struct UriStack(Vec); + +impl UriStack { + fn push(req: &mut Request) { + let uri = req.uri().clone(); + + if let Some(stack) = req.extensions_mut().get_mut::() { + stack.0.push(uri); + } else { + req.extensions_mut().insert(Self(vec![uri])); + } + } +} + +#[derive(Clone)] +pub(super) struct NestMatchTail(pub(super) String); + +#[test] +fn traits() { + use crate::tests::*; + + assert_send::>(); + assert_sync::>(); +} diff --git a/src/routing/route.rs b/src/routing/route.rs new file mode 100644 index 00000000..eafd4572 --- /dev/null +++ b/src/routing/route.rs @@ -0,0 +1,100 @@ +use crate::{ + body::{Body, BoxBody}, + clone_box_service::CloneBoxService, +}; +use http::{Request, Response}; +use pin_project_lite::pin_project; +use std::{ + convert::Infallible, + fmt, + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use tower::{util::Oneshot, ServiceExt}; +use tower_service::Service; + +/// How routes are stored inside a [`Router`](super::Router). +/// +/// You normally shouldn't need to care about this type. +pub struct Route(CloneBoxService, Response, Infallible>); + +impl Route { + pub(super) fn new(svc: T) -> Self + where + T: Service, Response = Response, Error = Infallible> + + Clone + + Send + + 'static, + T::Future: Send + 'static, + { + Self(CloneBoxService::new(svc)) + } +} + +impl Clone for Route { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl fmt::Debug for Route { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Route").finish() + } +} + +impl Service> for Route { + type Response = Response; + type Error = Infallible; + type Future = RouteFuture; + + #[inline] + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: Request) -> Self::Future { + RouteFuture::new(self.0.clone().oneshot(req)) + } +} + +pin_project! { + /// Response future for [`Route`]. + pub struct RouteFuture { + #[pin] + future: Oneshot< + CloneBoxService, Response, Infallible>, + Request, + > + } +} + +impl RouteFuture { + pub(crate) fn new( + future: Oneshot, Response, Infallible>, Request>, + ) -> Self { + RouteFuture { future } + } +} + +impl Future for RouteFuture { + type Output = Result, Infallible>; + + #[inline] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project().future.poll(cx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn traits() { + use crate::tests::*; + assert_send::>(); + } +}