From a5b6b9453007d50169a8c7c56322f2409cebd325 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sun, 3 Jul 2022 15:56:48 +0200 Subject: [PATCH] checkpoint --- axum-core/src/extract/mod.rs | 55 +++++++++-------- axum-core/src/extract/request_parts.rs | 43 ++++++++------ axum-core/src/extract/tuple.rs | 12 ++-- axum/src/error_handling/mod.rs | 52 ++++++++-------- axum/src/extension.rs | 5 +- axum/src/extract/connect_info.rs | 7 ++- axum/src/extract/content_length_limit.rs | 7 ++- axum/src/extract/host.rs | 5 +- axum/src/extract/matched_path.rs | 5 +- axum/src/extract/mod.rs | 10 ++-- axum/src/extract/multipart.rs | 5 +- axum/src/extract/path/mod.rs | 5 +- axum/src/extract/query.rs | 13 ++-- axum/src/extract/raw_query.rs | 5 +- axum/src/extract/request_parts.rs | 15 +++-- axum/src/extract/state.rs | 20 +++++++ axum/src/extract/ws.rs | 9 +-- axum/src/form.rs | 70 +++++++++++----------- axum/src/handler/mod.rs | 5 +- axum/src/json.rs | 7 ++- axum/src/middleware/from_extractor.rs | 76 ++++++++++++++---------- axum/src/middleware/from_fn.rs | 5 ++ axum/src/typed_header.rs | 5 +- 23 files changed, 256 insertions(+), 185 deletions(-) create mode 100644 axum/src/extract/state.rs diff --git a/axum-core/src/extract/mod.rs b/axum-core/src/extract/mod.rs index f63bab5b..7344c3b0 100644 --- a/axum-core/src/extract/mod.rs +++ b/axum-core/src/extract/mod.rs @@ -60,29 +60,30 @@ mod tuple; /// [`http::Request`]: http::Request /// [`axum::extract`]: https://docs.rs/axum/latest/axum/extract/index.html #[async_trait] -pub trait FromRequest: Sized { +pub trait FromRequest: Sized { /// If the extractor fails it'll use this "rejection" type. A rejection is /// a kind of error that can be converted into a response. type Rejection: IntoResponse; /// Perform the extraction. - async fn from_request(req: &mut RequestParts) -> Result; + async fn from_request(req: &mut RequestParts) -> Result; } /// The type used with [`FromRequest`] to extract data from requests. /// /// Has several convenience methods for getting owned parts of the request. #[derive(Debug)] -pub struct RequestParts { +pub struct RequestParts { method: Method, uri: Uri, version: Version, headers: HeaderMap, extensions: Extensions, body: Option, + state: S, } -impl RequestParts { +impl RequestParts { /// Create a new `RequestParts`. /// /// You generally shouldn't need to construct this type yourself, unless @@ -90,7 +91,10 @@ impl RequestParts { /// [`tower::Service`]. /// /// [`tower::Service`]: https://docs.rs/tower/lastest/tower/trait.Service.html - pub fn new(req: Request) -> Self { + pub fn new(state: S, req: Request) -> Self + where + S: Send + Sync + 'static, + { let ( http::request::Parts { method, @@ -110,6 +114,7 @@ impl RequestParts { headers, extensions, body: Some(body), + state, } } @@ -141,7 +146,10 @@ impl RequestParts { /// } /// } /// ``` - pub async fn extract>(&mut self) -> Result { + pub async fn extract(&mut self) -> Result + where + E: FromRequest, + { E::from_request(self).await } @@ -159,6 +167,7 @@ impl RequestParts { headers, extensions, mut body, + state: _, } = self; let mut req = if let Some(body) = body.take() { @@ -245,46 +254,36 @@ impl RequestParts { pub fn take_body(&mut self) -> Option { self.body.take() } + + pub fn state(&self) -> &S { + &self.state + } } #[async_trait] -impl FromRequest for Option +impl FromRequest for Option where - T: FromRequest, + T: FromRequest, B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result, Self::Rejection> { + async fn from_request(req: &mut RequestParts) -> Result, Self::Rejection> { Ok(T::from_request(req).await.ok()) } } #[async_trait] -impl FromRequest for Result +impl FromRequest for Result where - T: FromRequest, + T: FromRequest, B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { Ok(T::from_request(req).await) } } - -/// TODO(david): docs -#[derive(Clone, Copy, Debug, Default)] -pub struct State(pub S); - -#[async_trait] -impl FromRequest for State -where - B: Send, -{ - type Rejection = Infallible; - - async fn from_request(req: &mut RequestParts) -> Result { - todo!() - } -} diff --git a/axum-core/src/extract/request_parts.rs b/axum-core/src/extract/request_parts.rs index 33383a7d..875c9a59 100644 --- a/axum-core/src/extract/request_parts.rs +++ b/axum-core/src/extract/request_parts.rs @@ -6,13 +6,14 @@ use http::{Extensions, HeaderMap, Method, Request, Uri, Version}; use std::convert::Infallible; #[async_trait] -impl FromRequest for Request +impl FromRequest for Request where B: Send, + S: Clone + Send, { type Rejection = BodyAlreadyExtracted; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let req = std::mem::replace( req, RequestParts { @@ -22,6 +23,7 @@ where headers: HeaderMap::new(), extensions: Extensions::default(), body: None, + state: req.state.clone(), }, ); @@ -30,37 +32,40 @@ where } #[async_trait] -impl FromRequest for Method +impl FromRequest for Method where B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { Ok(req.method().clone()) } } #[async_trait] -impl FromRequest for Uri +impl FromRequest for Uri where B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { Ok(req.uri().clone()) } } #[async_trait] -impl FromRequest for Version +impl FromRequest for Version where B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { Ok(req.version()) } } @@ -71,27 +76,29 @@ where /// /// [`TypedHeader`]: https://docs.rs/axum/latest/axum/extract/struct.TypedHeader.html #[async_trait] -impl FromRequest for HeaderMap +impl FromRequest for HeaderMap where B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { Ok(req.headers().clone()) } } #[async_trait] -impl FromRequest for Bytes +impl FromRequest for Bytes where B: http_body::Body + Send, B::Data: Send, B::Error: Into, + S: Send, { type Rejection = BytesRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let body = take_body(req)?; let bytes = crate::body::to_bytes(body) @@ -103,15 +110,16 @@ where } #[async_trait] -impl FromRequest for String +impl FromRequest for String where B: http_body::Body + Send, B::Data: Send, B::Error: Into, + S: Send, { type Rejection = StringRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let body = take_body(req)?; let bytes = crate::body::to_bytes(body) @@ -126,13 +134,14 @@ where } #[async_trait] -impl FromRequest for http::request::Parts +impl FromRequest for http::request::Parts where B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let method = unwrap_infallible(Method::from_request(req).await); let uri = unwrap_infallible(Uri::from_request(req).await); let version = unwrap_infallible(Version::from_request(req).await); @@ -159,6 +168,6 @@ fn unwrap_infallible(result: Result) -> T { } } -pub(crate) fn take_body(req: &mut RequestParts) -> Result { +pub(crate) fn take_body(req: &mut RequestParts) -> Result { req.take_body().ok_or(BodyAlreadyExtracted) } diff --git a/axum-core/src/extract/tuple.rs b/axum-core/src/extract/tuple.rs index 8c781a8d..d32ff23d 100644 --- a/axum-core/src/extract/tuple.rs +++ b/axum-core/src/extract/tuple.rs @@ -4,13 +4,14 @@ use async_trait::async_trait; use std::convert::Infallible; #[async_trait] -impl FromRequest for () +impl FromRequest for () where + S: Send, B: Send, { type Rejection = Infallible; - async fn from_request(_: &mut RequestParts) -> Result<(), Self::Rejection> { + async fn from_request(_: &mut RequestParts) -> Result<(), Self::Rejection> { Ok(()) } } @@ -21,14 +22,15 @@ macro_rules! impl_from_request { ( $($ty:ident),* $(,)? ) => { #[async_trait] #[allow(non_snake_case)] - impl FromRequest for ($($ty,)*) + impl FromRequest for ($($ty,)*) where - $( $ty: FromRequest + Send, )* + $( $ty: FromRequest + Send, )* + S: Send, B: Send, { type Rejection = Response; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { $( let $ty = $ty::from_request(req).await.map_err(|err| err.into_response())?; )* Ok(($($ty,)*)) } diff --git a/axum/src/error_handling/mod.rs b/axum/src/error_handling/mod.rs index e0027eed..b423f041 100644 --- a/axum/src/error_handling/mod.rs +++ b/axum/src/error_handling/mod.rs @@ -22,6 +22,8 @@ use tower_service::Service; /// that handles errors by converting them into responses. /// /// See [module docs](self) for more details on axum's error handling model. +// TODO(david): cannot access state, is that bad? It leads to inference issues and one has to +// specify the type manually and risk getting it wrong. So its basically an Extension at that point pub struct HandleErrorLayer { f: F, _extractor: PhantomData T>, @@ -49,7 +51,7 @@ where } } -impl fmt::Debug for HandleErrorLayer { +impl fmt::Debug for HandleErrorLayer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HandleErrorLayer") .field("f", &format_args!("{}", std::any::type_name::())) @@ -57,13 +59,13 @@ impl fmt::Debug for HandleErrorLayer { } } -impl Layer for HandleErrorLayer +impl Layer for HandleErrorLayer where F: Clone, { - type Service = HandleError; + type Service = HandleError; - fn layer(&self, inner: S) -> Self::Service { + fn layer(&self, inner: Svc) -> Self::Service { HandleError::new(inner, self.f.clone()) } } @@ -71,15 +73,15 @@ where /// A [`Service`] adapter that handles errors by converting them into responses. /// /// See [module docs](self) for more details on axum's error handling model. -pub struct HandleError { - inner: S, +pub struct HandleError { + inner: Svc, f: F, _extractor: PhantomData T>, } -impl HandleError { +impl HandleError { /// Create a new `HandleError`. - pub fn new(inner: S, f: F) -> Self { + pub fn new(inner: Svc, f: F) -> Self { Self { inner, f, @@ -88,9 +90,9 @@ impl HandleError { } } -impl Clone for HandleError +impl Clone for HandleError where - S: Clone, + Svc: Clone, F: Clone, { fn clone(&self) -> Self { @@ -102,9 +104,9 @@ where } } -impl fmt::Debug for HandleError +impl fmt::Debug for HandleError where - S: fmt::Debug, + Svc: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HandleError") @@ -114,12 +116,12 @@ where } } -impl Service> for HandleError +impl Service> for HandleError where - S: Service, Response = Response> + Clone + Send + 'static, - S::Error: Send, - S::Future: Send, - F: FnOnce(S::Error) -> Fut + Clone + Send + 'static, + Svc: Service, Response = Response> + Clone + Send + 'static, + Svc::Error: Send, + Svc::Future: Send, + F: FnOnce(Svc::Error) -> Fut + Clone + Send + 'static, Fut: Future + Send, Res: IntoResponse, ReqBody: Send + 'static, @@ -154,16 +156,16 @@ where #[allow(unused_macros)] macro_rules! impl_service { ( $($ty:ident),* $(,)? ) => { - impl Service> - for HandleError + impl Service> + for HandleError where - S: Service, Response = Response> + Clone + Send + 'static, - S::Error: Send, - S::Future: Send, - F: FnOnce($($ty),*, S::Error) -> Fut + Clone + Send + 'static, + Svc: Service, Response = Response> + Clone + Send + 'static, + Svc::Error: Send, + Svc::Future: Send, + F: FnOnce($($ty),*, Svc::Error) -> Fut + Clone + Send + 'static, Fut: Future + Send, Res: IntoResponse, - $( $ty: FromRequest + Send,)* + $( $ty: FromRequest<(), ReqBody> + Send,)* ReqBody: Send + 'static, ResBody: HttpBody + Send + 'static, ResBody::Error: Into, @@ -185,7 +187,7 @@ macro_rules! impl_service { let inner = std::mem::replace(&mut self.inner, clone); let future = Box::pin(async move { - let mut req = RequestParts::new(req); + let mut req = RequestParts::new((), req); $( let $ty = match $ty::from_request(&mut req).await { diff --git a/axum/src/extension.rs b/axum/src/extension.rs index 390f29e0..d040b9e4 100644 --- a/axum/src/extension.rs +++ b/axum/src/extension.rs @@ -73,14 +73,15 @@ use tower_service::Service; pub struct Extension(pub T); #[async_trait] -impl FromRequest for Extension +impl FromRequest for Extension where T: Clone + Send + Sync + 'static, B: Send, + S: Send, { type Rejection = ExtensionRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let value = req .extensions() .get::() diff --git a/axum/src/extract/connect_info.rs b/axum/src/extract/connect_info.rs index 100efe68..14cae7b4 100644 --- a/axum/src/extract/connect_info.rs +++ b/axum/src/extract/connect_info.rs @@ -128,14 +128,15 @@ opaque_future! { pub struct ConnectInfo(pub T); #[async_trait] -impl FromRequest for ConnectInfo +impl FromRequest for ConnectInfo where B: Send, T: Clone + Send + Sync + 'static, + S: Send, { - type Rejection = as FromRequest>::Rejection; + type Rejection = as FromRequest>::Rejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let Extension(connect_info) = Extension::::from_request(req).await?; Ok(connect_info) } diff --git a/axum/src/extract/content_length_limit.rs b/axum/src/extract/content_length_limit.rs index ffd55285..29fbd46c 100644 --- a/axum/src/extract/content_length_limit.rs +++ b/axum/src/extract/content_length_limit.rs @@ -36,15 +36,16 @@ use std::ops::Deref; pub struct ContentLengthLimit(pub T); #[async_trait] -impl FromRequest for ContentLengthLimit +impl FromRequest for ContentLengthLimit where - T: FromRequest, + T: FromRequest, T::Rejection: IntoResponse, B: Send, + S: Send, { type Rejection = ContentLengthLimitRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let content_length = req .headers() .get(http::header::CONTENT_LENGTH) diff --git a/axum/src/extract/host.rs b/axum/src/extract/host.rs index 9e126239..d7e49a5a 100644 --- a/axum/src/extract/host.rs +++ b/axum/src/extract/host.rs @@ -21,13 +21,14 @@ const X_FORWARDED_HOST_HEADER_KEY: &str = "X-Forwarded-Host"; pub struct Host(pub String); #[async_trait] -impl FromRequest for Host +impl FromRequest for Host where B: Send, + S: Send, { type Rejection = HostRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { if let Some(host) = parse_forwarded(req.headers()) { return Ok(Host(host.to_owned())); } diff --git a/axum/src/extract/matched_path.rs b/axum/src/extract/matched_path.rs index 1cd26a45..f8e67525 100644 --- a/axum/src/extract/matched_path.rs +++ b/axum/src/extract/matched_path.rs @@ -64,13 +64,14 @@ impl MatchedPath { } #[async_trait] -impl FromRequest for MatchedPath +impl FromRequest for MatchedPath where B: Send, + S: Send, { type Rejection = MatchedPathRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let matched_path = req .extensions() .get::() diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index 70289d00..dcc9e70d 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -14,9 +14,10 @@ mod content_length_limit; mod host; mod raw_query; mod request_parts; +mod state; #[doc(inline)] -pub use axum_core::extract::{FromRequest, RequestParts, State}; +pub use axum_core::extract::{FromRequest, RequestParts}; #[doc(inline)] #[allow(deprecated)] @@ -27,6 +28,7 @@ pub use self::{ path::Path, raw_query::RawQuery, request_parts::{BodyStream, RawBody}, + state::State, }; #[doc(no_inline)] @@ -73,13 +75,13 @@ pub use self::ws::WebSocketUpgrade; #[doc(no_inline)] pub use crate::TypedHeader; -pub(crate) fn take_body(req: &mut RequestParts) -> Result { +pub(crate) fn take_body(req: &mut RequestParts) -> Result { req.take_body().ok_or_else(BodyAlreadyExtracted::default) } // this is duplicated in `axum-extra/src/extract/form.rs` -pub(super) fn has_content_type( - req: &RequestParts, +pub(super) fn has_content_type( + req: &RequestParts, expected_content_type: &mime::Mime, ) -> bool { let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) { diff --git a/axum/src/extract/multipart.rs b/axum/src/extract/multipart.rs index 9391145a..168f3ef2 100644 --- a/axum/src/extract/multipart.rs +++ b/axum/src/extract/multipart.rs @@ -50,14 +50,15 @@ pub struct Multipart { } #[async_trait] -impl FromRequest for Multipart +impl FromRequest for Multipart where B: HttpBody + Default + Unpin + Send + 'static, B::Error: Into, + S: Send, { type Rejection = MultipartRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let stream = BodyStream::from_request(req).await?; let headers = req.headers(); let boundary = parse_boundary(headers).ok_or(InvalidBoundary)?; diff --git a/axum/src/extract/path/mod.rs b/axum/src/extract/path/mod.rs index c6ae6577..590f80ee 100644 --- a/axum/src/extract/path/mod.rs +++ b/axum/src/extract/path/mod.rs @@ -163,14 +163,15 @@ impl DerefMut for Path { } #[async_trait] -impl FromRequest for Path +impl FromRequest for Path where T: DeserializeOwned + Send, B: Send, + S: Send, { type Rejection = PathRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let params = match req.extensions_mut().get::() { Some(UrlParams::Params(params)) => params, Some(UrlParams::InvalidUtf8InPathParam { key }) => { diff --git a/axum/src/extract/query.rs b/axum/src/extract/query.rs index c267ce05..301a44b0 100644 --- a/axum/src/extract/query.rs +++ b/axum/src/extract/query.rs @@ -49,14 +49,15 @@ use std::ops::Deref; pub struct Query(pub T); #[async_trait] -impl FromRequest for Query +impl FromRequest for Query where T: DeserializeOwned, B: Send, + S: Send, { type Rejection = QueryRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let query = req.uri().query().unwrap_or_default(); let value = serde_urlencoded::from_str(query) .map_err(FailedToDeserializeQueryString::__private_new::)?; @@ -80,8 +81,12 @@ mod tests { use serde::Deserialize; use std::fmt::Debug; - async fn check(uri: impl AsRef, value: T) { - let mut req = RequestParts::new(Request::builder().uri(uri.as_ref()).body(()).unwrap()); + async fn check(uri: impl AsRef, value: T) + where + T: DeserializeOwned + PartialEq + Debug, + { + let req = Request::builder().uri(uri.as_ref()).body(()).unwrap(); + let mut req = RequestParts::new((), req); assert_eq!(Query::::from_request(&mut req).await.unwrap().0, value); } diff --git a/axum/src/extract/raw_query.rs b/axum/src/extract/raw_query.rs index 463c31d8..faf8df6e 100644 --- a/axum/src/extract/raw_query.rs +++ b/axum/src/extract/raw_query.rs @@ -27,13 +27,14 @@ use std::convert::Infallible; pub struct RawQuery(pub Option); #[async_trait] -impl FromRequest for RawQuery +impl FromRequest for RawQuery where B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let query = req.uri().query().map(|query| query.to_owned()); Ok(Self(query)) } diff --git a/axum/src/extract/request_parts.rs b/axum/src/extract/request_parts.rs index 4dd8a150..81976783 100644 --- a/axum/src/extract/request_parts.rs +++ b/axum/src/extract/request_parts.rs @@ -86,13 +86,14 @@ pub struct OriginalUri(pub Uri); #[cfg(feature = "original-uri")] #[async_trait] -impl FromRequest for OriginalUri +impl FromRequest for OriginalUri where B: Send, + S: Send, { type Rejection = Infallible; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let uri = Extension::::from_request(req) .await .unwrap_or_else(|_| Extension(OriginalUri(req.uri().clone()))) @@ -140,15 +141,16 @@ impl Stream for BodyStream { } #[async_trait] -impl FromRequest for BodyStream +impl FromRequest for BodyStream where B: HttpBody + Send + 'static, B::Data: Into, B::Error: Into, + S: Send, { type Rejection = BodyAlreadyExtracted; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let body = take_body(req)? .map_data(Into::into) .map_err(|err| Error::new(err.into())); @@ -196,13 +198,14 @@ fn body_stream_traits() { pub struct RawBody(pub B); #[async_trait] -impl FromRequest for RawBody +impl FromRequest for RawBody where B: Send, + S: Send, { type Rejection = BodyAlreadyExtracted; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { let body = take_body(req)?; Ok(Self(body)) } diff --git a/axum/src/extract/state.rs b/axum/src/extract/state.rs new file mode 100644 index 00000000..0801a113 --- /dev/null +++ b/axum/src/extract/state.rs @@ -0,0 +1,20 @@ +use super::{FromRequest, RequestParts}; +use async_trait::async_trait; +use std::convert::Infallible; + +/// TODO(david): docs +#[derive(Clone, Copy, Debug, Default)] +pub struct State(pub S); + +#[async_trait] +impl FromRequest for State +where + B: Send, + S: Clone + Send, +{ + type Rejection = Infallible; + + async fn from_request(req: &mut RequestParts) -> Result { + Ok(Self(req.state().clone())) + } +} diff --git a/axum/src/extract/ws.rs b/axum/src/extract/ws.rs index 7ac75ab6..2840ad63 100644 --- a/axum/src/extract/ws.rs +++ b/axum/src/extract/ws.rs @@ -244,13 +244,14 @@ impl WebSocketUpgrade { } #[async_trait] -impl FromRequest for WebSocketUpgrade +impl FromRequest for WebSocketUpgrade where B: Send, + S: Send, { type Rejection = WebSocketUpgradeRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { if req.method() != Method::GET { return Err(MethodNotGet.into()); } @@ -288,7 +289,7 @@ where } } -fn header_eq(req: &RequestParts, key: HeaderName, value: &'static str) -> bool { +fn header_eq(req: &RequestParts, key: HeaderName, value: &'static str) -> bool { if let Some(header) = req.headers().get(&key) { header.as_bytes().eq_ignore_ascii_case(value.as_bytes()) } else { @@ -296,7 +297,7 @@ fn header_eq(req: &RequestParts, key: HeaderName, value: &'static str) -> } } -fn header_contains(req: &RequestParts, key: HeaderName, value: &'static str) -> bool { +fn header_contains(req: &RequestParts, key: HeaderName, value: &'static str) -> bool { let header = if let Some(header) = req.headers().get(&key) { header } else { diff --git a/axum/src/form.rs b/axum/src/form.rs index 9974a460..48e17b2e 100644 --- a/axum/src/form.rs +++ b/axum/src/form.rs @@ -56,16 +56,17 @@ use std::ops::Deref; pub struct Form(pub T); #[async_trait] -impl FromRequest for Form +impl FromRequest for Form where T: DeserializeOwned, B: HttpBody + Send, B::Data: Send, B::Error: Into, + S: Send, { type Rejection = FormRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { if req.method() == Method::GET { let query = req.uri().query().unwrap_or_default(); let value = serde_urlencoded::from_str(query) @@ -125,29 +126,27 @@ mod tests { } async fn check_query(uri: impl AsRef, value: T) { - let mut req = RequestParts::new( - Request::builder() - .uri(uri.as_ref()) - .body(Empty::::new()) - .unwrap(), - ); + let req = Request::builder() + .uri(uri.as_ref()) + .body(Empty::::new()) + .unwrap(); + let mut req = RequestParts::new((), req); assert_eq!(Form::::from_request(&mut req).await.unwrap().0, value); } async fn check_body(value: T) { - let mut req = RequestParts::new( - Request::builder() - .uri("http://example.com/test") - .method(Method::POST) - .header( - http::header::CONTENT_TYPE, - mime::APPLICATION_WWW_FORM_URLENCODED.as_ref(), - ) - .body(Full::::new( - serde_urlencoded::to_string(&value).unwrap().into(), - )) - .unwrap(), - ); + let req = Request::builder() + .uri("http://example.com/test") + .method(Method::POST) + .header( + http::header::CONTENT_TYPE, + mime::APPLICATION_WWW_FORM_URLENCODED.as_ref(), + ) + .body(Full::::new( + serde_urlencoded::to_string(&value).unwrap().into(), + )) + .unwrap(); + let mut req = RequestParts::new((), req); assert_eq!(Form::::from_request(&mut req).await.unwrap().0, value); } @@ -204,21 +203,20 @@ mod tests { #[tokio::test] async fn test_incorrect_content_type() { - let mut req = RequestParts::new( - Request::builder() - .uri("http://example.com/test") - .method(Method::POST) - .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .body(Full::::new( - serde_urlencoded::to_string(&Pagination { - size: Some(10), - page: None, - }) - .unwrap() - .into(), - )) - .unwrap(), - ); + let req = Request::builder() + .uri("http://example.com/test") + .method(Method::POST) + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) + .body(Full::::new( + serde_urlencoded::to_string(&Pagination { + size: Some(10), + page: None, + }) + .unwrap() + .into(), + )) + .unwrap(); + let mut req = RequestParts::new((), req); assert!(matches!( Form::::from_request(&mut req) .await diff --git a/axum/src/handler/mod.rs b/axum/src/handler/mod.rs index 510fb4ae..b4e20a5b 100644 --- a/axum/src/handler/mod.rs +++ b/axum/src/handler/mod.rs @@ -244,13 +244,14 @@ macro_rules! impl_handler { Fut: Future + Send, B: Send + 'static, Res: IntoResponse, - $( $ty: FromRequest + Send,)* + $( $ty: FromRequest + Send,)* + S: Send + Sync + 'static { type Future = Pin + Send>>; fn call(self, state: S, req: Request) -> Self::Future { Box::pin(async move { - let mut req = RequestParts::new(req); + let mut req = RequestParts::new(state, req); $( let $ty = match $ty::from_request(&mut req).await { diff --git a/axum/src/json.rs b/axum/src/json.rs index 8c03c4c0..6fd2fd5a 100644 --- a/axum/src/json.rs +++ b/axum/src/json.rs @@ -93,16 +93,17 @@ use std::ops::{Deref, DerefMut}; pub struct Json(pub T); #[async_trait] -impl FromRequest for Json +impl FromRequest for Json where T: DeserializeOwned, B: HttpBody + Send, B::Data: Send, B::Error: Into, + S: Send, { type Rejection = JsonRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { if json_content_type(req) { let bytes = Bytes::from_request(req).await?; @@ -135,7 +136,7 @@ where } } -fn json_content_type(req: &RequestParts) -> bool { +fn json_content_type(req: &RequestParts) -> bool { let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) { content_type } else { diff --git a/axum/src/middleware/from_extractor.rs b/axum/src/middleware/from_extractor.rs index bef813f3..ab9b0205 100644 --- a/axum/src/middleware/from_extractor.rs +++ b/axum/src/middleware/from_extractor.rs @@ -116,10 +116,10 @@ impl fmt::Debug for FromExtractorLayer { } } -impl Layer for FromExtractorLayer { - type Service = FromExtractor; +impl Layer for FromExtractorLayer { + type Service = FromExtractor; - fn layer(&self, inner: S) -> Self::Service { + fn layer(&self, inner: T) -> Self::Service { FromExtractor { inner, _extractor: PhantomData, @@ -130,8 +130,8 @@ impl Layer for FromExtractorLayer { /// Middleware that runs an extractor and discards the value. /// /// See [`from_extractor`] for more details. -pub struct FromExtractor { - inner: S, +pub struct FromExtractor { + inner: T, _extractor: PhantomData E>, } @@ -142,9 +142,9 @@ fn traits() { assert_sync::>(); } -impl Clone for FromExtractor +impl Clone for FromExtractor where - S: Clone, + T: Clone, { fn clone(&self) -> Self { Self { @@ -154,9 +154,9 @@ where } } -impl fmt::Debug for FromExtractor +impl fmt::Debug for FromExtractor where - S: fmt::Debug, + T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FromExtractor") @@ -166,17 +166,17 @@ where } } -impl Service> for FromExtractor +impl Service> for FromExtractor where - E: FromRequest + 'static, + E: FromRequest<(), ReqBody> + 'static, ReqBody: Default + Send + 'static, - S: Service, Response = Response> + Clone, + T: Service, Response = Response> + Clone, ResBody: HttpBody + Send + 'static, ResBody::Error: Into, { type Response = Response; - type Error = S::Error; - type Future = ResponseFuture; + type Error = T::Error; + type Future = ResponseFuture; #[inline] fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { @@ -185,7 +185,7 @@ where fn call(&mut self, req: Request) -> Self::Future { let extract_future = Box::pin(async move { - let mut req = RequestParts::new(req); + let mut req = RequestParts::new((), req); let extracted = E::from_request(&mut req).await; (req, extracted) }); @@ -202,38 +202,41 @@ where pin_project! { /// Response future for [`FromExtractor`]. #[allow(missing_debug_implementations)] - pub struct ResponseFuture + pub struct ResponseFuture where - E: FromRequest, - S: Service>, + E: FromRequest<(), ReqBody>, + T: Service>, { #[pin] - state: State, - svc: Option, + state: State, + svc: Option, } } pin_project! { #[project = StateProj] - enum State + enum State where - E: FromRequest, - S: Service>, + E: FromRequest<(), ReqBody>, + T: Service>, { - Extracting { future: BoxFuture<'static, (RequestParts, Result)> }, - Call { #[pin] future: S::Future }, + Extracting { + future: BoxFuture<'static, (RequestParts<(), ReqBody>, Result)> + }, + Call { #[pin] future: T::Future }, + Error { response: Option } } } -impl Future for ResponseFuture +impl Future for ResponseFuture where - E: FromRequest, - S: Service, Response = Response>, + E: FromRequest<(), ReqBody>, + T: Service, Response = Response>, ReqBody: Default, ResBody: HttpBody + Send + 'static, ResBody::Error: Into, { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { @@ -261,6 +264,11 @@ where .poll(cx) .map(|result| result.map(|response| response.map(crate::body::boxed))); } + StateProj::Error { response } => { + return Poll::Ready(Ok(response + .take() + .expect("future polled after completion"))) + } }; this.state.set(new_state); @@ -279,13 +287,14 @@ mod tests { struct RequireAuth; #[async_trait::async_trait] - impl FromRequest for RequireAuth + impl FromRequest for RequireAuth where B: Send, + S: Send, { type Rejection = StatusCode; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { if let Some(auth) = req .headers() .get(header::AUTHORIZATION) @@ -317,4 +326,9 @@ mod tests { .await; assert_eq!(res.status(), StatusCode::OK); } + + #[test] + fn extracting_state() { + todo!() + } } diff --git a/axum/src/middleware/from_fn.rs b/axum/src/middleware/from_fn.rs index fc386c50..ddf91b45 100644 --- a/axum/src/middleware/from_fn.rs +++ b/axum/src/middleware/from_fn.rs @@ -313,4 +313,9 @@ mod tests { let body = hyper::body::to_bytes(res).await.unwrap(); assert_eq!(&body[..], b"ok"); } + + #[test] + fn extracting_state() { + todo!() + } } diff --git a/axum/src/typed_header.rs b/axum/src/typed_header.rs index d3df7b72..de61056a 100644 --- a/axum/src/typed_header.rs +++ b/axum/src/typed_header.rs @@ -52,14 +52,15 @@ use std::{convert::Infallible, ops::Deref}; pub struct TypedHeader(pub T); #[async_trait] -impl FromRequest for TypedHeader +impl FromRequest for TypedHeader where T: headers::Header, B: Send, + S: Send, { type Rejection = TypedHeaderRejection; - async fn from_request(req: &mut RequestParts) -> Result { + async fn from_request(req: &mut RequestParts) -> Result { match req.headers().typed_try_get::() { Ok(Some(value)) => Ok(Self(value)), Ok(None) => Err(TypedHeaderRejection {