Refactor FromRequest trait, use async fn (#279)

This commit is contained in:
Nikolay Kim 2024-01-08 15:46:47 +06:00 committed by GitHub
parent 174b5d86f0
commit 3f976ca71e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 118 additions and 179 deletions

View file

@ -2,6 +2,8 @@
## [1.0.0-b.1] - 2024-01-08 ## [1.0.0-b.1] - 2024-01-08
* web: Refactor FromRequest trait, use async fn
* Refactor io tls filters * Refactor io tls filters
## [1.0.0-b.0] - 2024-01-07 ## [1.0.0-b.0] - 2024-01-07

View file

@ -1,9 +1,9 @@
//! Request extractors //! Request extractors
use std::{future::Future, pin::Pin, task::Context, task::Poll}; use std::future::Future;
use super::error::ErrorRenderer; use super::error::ErrorRenderer;
use super::httprequest::HttpRequest; use super::httprequest::HttpRequest;
use crate::{http::Payload, util::BoxFuture, util::Ready}; use crate::http::Payload;
/// Trait implemented by types that can be extracted from request. /// Trait implemented by types that can be extracted from request.
/// ///
@ -12,11 +12,11 @@ pub trait FromRequest<Err>: Sized {
/// The associated error which can be returned. /// The associated error which can be returned.
type Error; type Error;
/// Future that resolves to a Self
type Future: Future<Output = Result<Self, Self::Error>>;
/// Convert request to a Self /// Convert request to a Self
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; fn from_request(
req: &HttpRequest,
payload: &mut Payload,
) -> impl Future<Output = Result<Self, Self::Error>>;
} }
/// Optionally extract a field from the request /// Optionally extract a field from the request
@ -26,7 +26,7 @@ pub trait FromRequest<Err>: Sized {
/// ## Example /// ## Example
/// ///
/// ```rust /// ```rust
/// use ntex::{http, util::Ready}; /// use ntex::http;
/// use ntex::web::{self, error, App, HttpRequest, FromRequest, DefaultError}; /// use ntex::web::{self, error, App, HttpRequest, FromRequest, DefaultError};
/// use rand; /// use rand;
/// ///
@ -37,13 +37,12 @@ pub trait FromRequest<Err>: Sized {
/// ///
/// impl<Err> FromRequest<Err> for Thing { /// impl<Err> FromRequest<Err> for Thing {
/// type Error = error::Error; /// type Error = error::Error;
/// type Future = Ready<Self, Self::Error>;
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut http::Payload) -> Self::Future { /// async fn from_request(req: &HttpRequest, payload: &mut http::Payload) -> Result<Self, Self::Error> {
/// if rand::random() { /// if rand::random() {
/// Ready::Ok(Thing { name: "thingy".into() }) /// Ok(Thing { name: "thingy".into() })
/// } else { /// } else {
/// Ready::Err(error::ErrorBadRequest("no luck").into()) /// Err(error::ErrorBadRequest("no luck").into())
/// } /// }
/// } /// }
/// } /// }
@ -66,25 +65,24 @@ pub trait FromRequest<Err>: Sized {
/// ``` /// ```
impl<T, Err> FromRequest<Err> for Option<T> impl<T, Err> FromRequest<Err> for Option<T>
where where
T: FromRequest<Err> + 'static, T: FromRequest<Err>,
Err: ErrorRenderer, Err: ErrorRenderer,
<T as FromRequest<Err>>::Error: Into<Err::Container>, <T as FromRequest<Err>>::Error: Into<Err::Container>,
{ {
type Error = Err::Container; type Error = Err::Container;
type Future = BoxFuture<'static, Result<Option<T>, Self::Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { async fn from_request(
let fut = T::from_request(req, payload); req: &HttpRequest,
Box::pin(async move { payload: &mut Payload,
match fut.await { ) -> Result<Option<T>, Self::Error> {
match T::from_request(req, payload).await {
Ok(v) => Ok(Some(v)), Ok(v) => Ok(Some(v)),
Err(e) => { Err(e) => {
log::debug!("Error for Option<T> extractor: {}", e.into()); log::debug!("Error for Option<T> extractor: {}", e.into());
Ok(None) Ok(None)
} }
} }
})
} }
} }
@ -95,7 +93,7 @@ where
/// ## Example /// ## Example
/// ///
/// ```rust /// ```rust
/// use ntex::{http, util::Ready}; /// use ntex::http;
/// use ntex::web::{self, error, App, HttpRequest, FromRequest}; /// use ntex::web::{self, error, App, HttpRequest, FromRequest};
/// use rand; /// use rand;
/// ///
@ -106,13 +104,12 @@ where
/// ///
/// impl<Err> FromRequest<Err> for Thing { /// impl<Err> FromRequest<Err> for Thing {
/// type Error = error::Error; /// type Error = error::Error;
/// type Future = Ready<Thing, Self::Error>;
/// ///
/// fn from_request(req: &HttpRequest, payload: &mut http::Payload) -> Self::Future { /// async fn from_request(req: &HttpRequest, payload: &mut http::Payload) -> Result<Thing, Self::Error> {
/// if rand::random() { /// if rand::random() {
/// Ready::Ok(Thing { name: "thingy".into() }) /// Ok(Thing { name: "thingy".into() })
/// } else { /// } else {
/// Ready::Err(error::ErrorBadRequest("no luck").into()) /// Err(error::ErrorBadRequest("no luck").into())
/// } /// }
/// } /// }
/// } /// }
@ -133,38 +130,34 @@ where
/// ``` /// ```
impl<T, E> FromRequest<E> for Result<T, T::Error> impl<T, E> FromRequest<E> for Result<T, T::Error>
where where
T: FromRequest<E> + 'static, T: FromRequest<E>,
T::Error: 'static,
E: ErrorRenderer, E: ErrorRenderer,
{ {
type Error = T::Error; type Error = T::Error;
type Future = BoxFuture<'static, Result<Result<T, T::Error>, Self::Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { async fn from_request(
let fut = T::from_request(req, payload); req: &HttpRequest,
Box::pin(async move { payload: &mut Payload,
match fut.await { ) -> Result<Self, Self::Error> {
match T::from_request(req, payload).await {
Ok(v) => Ok(Ok(v)), Ok(v) => Ok(Ok(v)),
Err(e) => Ok(Err(e)), Err(e) => Ok(Err(e)),
} }
})
} }
} }
#[doc(hidden)] #[doc(hidden)]
impl<E: ErrorRenderer> FromRequest<E> for () { impl<E: ErrorRenderer> FromRequest<E> for () {
type Error = E::Container; type Error = E::Container;
type Future = Ready<(), E::Container>;
#[inline] #[inline]
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { async fn from_request(_: &HttpRequest, _: &mut Payload) -> Result<(), E::Container> {
Ok(()).into() Ok(())
} }
} }
macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
/// FromRequest implementation for a tuple /// FromRequest implementation for a tuple
#[allow(unused_parens)] #[allow(unused_parens)]
impl<Err: ErrorRenderer, $($T: FromRequest<Err> + 'static),+> FromRequest<Err> for ($($T,)+) impl<Err: ErrorRenderer, $($T: FromRequest<Err> + 'static),+> FromRequest<Err> for ($($T,)+)
@ -172,54 +165,11 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
$(<$T as $crate::web::FromRequest<Err>>::Error: Into<Err::Container>),+ $(<$T as $crate::web::FromRequest<Err>>::Error: Into<Err::Container>),+
{ {
type Error = Err::Container; type Error = Err::Container;
type Future = $fut_type<Err, $($T),+>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { async fn from_request(req: &HttpRequest, payload: &mut Payload) -> Result<($($T,)+), Err::Container> {
$fut_type { Ok((
items: <($(Option<$T>,)+)>::default(), $($T::from_request(req, payload).await.map_err(|e| e.into())?,)+
$($T: $T::from_request(req, payload),)+
}
}
}
pin_project_lite::pin_project! {
#[doc(hidden)]
pub struct $fut_type<Err: ErrorRenderer, $($T: FromRequest<Err>),+>
{
items: ($(Option<$T>,)+),
$(#[pin] $T: $T::Future),+
}
}
impl<Err: ErrorRenderer, $($T: FromRequest<Err>),+> Future for $fut_type<Err, $($T),+>
where
$(<$T as $crate::web::FromRequest<Err>>::Error: Into<Err::Container>),+
{
type Output = Result<($($T,)+), Err::Container>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let mut ready = true;
$(
if this.items.$n.is_none() {
match this.$T.poll(cx) {
Poll::Ready(Ok(item)) => {
this.items.$n = Some(item);
}
Poll::Pending => ready = false,
Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())),
}
}
)+
if ready {
Poll::Ready(Ok(
($(this.items.$n.take().unwrap(),)+)
)) ))
} else {
Poll::Pending
}
} }
} }
}); });

View file

@ -5,7 +5,7 @@ use crate::http::{
}; };
use crate::io::{types, IoRef}; use crate::io::{types, IoRef};
use crate::router::Path; use crate::router::Path;
use crate::util::{Extensions, Ready}; use crate::util::Extensions;
use super::config::AppConfig; use super::config::AppConfig;
use super::error::ErrorRenderer; use super::error::ErrorRenderer;
@ -280,11 +280,10 @@ impl Drop for HttpRequest {
/// ``` /// ```
impl<Err: ErrorRenderer> FromRequest<Err> for HttpRequest { impl<Err: ErrorRenderer> FromRequest<Err> for HttpRequest {
type Error = Err::Container; type Error = Err::Container;
type Future = Ready<Self, Self::Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { async fn from_request(req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
Ok(req.clone()).into() Ok(req.clone())
} }
} }

View file

@ -101,22 +101,20 @@ where
Err: ErrorRenderer, Err: ErrorRenderer,
{ {
type Error = UrlencodedError; type Error = UrlencodedError;
type Future = BoxFuture<'static, Result<Self, Self::Error>>;
#[inline] async fn from_request(
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { req: &HttpRequest,
payload: &mut Payload,
) -> Result<Self, Self::Error> {
let limit = req let limit = req
.app_state::<FormConfig>() .app_state::<FormConfig>()
.map(|c| c.limit) .map(|c| c.limit)
.unwrap_or(16384); .unwrap_or(16384);
let fut = UrlEncoded::new(req, payload).limit(limit); match UrlEncoded::new(req, payload).limit(limit).await {
Box::pin(async move {
match fut.await {
Err(e) => Err(e), Err(e) => Err(e),
Ok(item) => Ok(Form(item)), Ok(item) => Ok(Form(item)),
} }
})
} }
} }

View file

@ -163,19 +163,18 @@ where
T: DeserializeOwned + 'static, T: DeserializeOwned + 'static,
{ {
type Error = JsonPayloadError; type Error = JsonPayloadError;
type Future = BoxFuture<'static, Result<Self, Self::Error>>;
#[inline] async fn from_request(
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { req: &HttpRequest,
payload: &mut Payload,
) -> Result<Self, Self::Error> {
let req2 = req.clone(); let req2 = req.clone();
let (limit, ctype) = req let (limit, ctype) = req
.app_state::<JsonConfig>() .app_state::<JsonConfig>()
.map(|c| (c.limit, c.content_type.clone())) .map(|c| (c.limit, c.content_type.clone()))
.unwrap_or((32768, None)); .unwrap_or((32768, None));
let fut = JsonBody::new(req, payload, ctype).limit(limit); match JsonBody::new(req, payload, ctype).limit(limit).await {
Box::pin(async move {
match fut.await {
Err(e) => { Err(e) => {
log::debug!( log::debug!(
"Failed to deserialize Json from payload. \ "Failed to deserialize Json from payload. \
@ -186,7 +185,6 @@ where
} }
Ok(data) => Ok(Json(data)), Ok(data) => Ok(Json(data)),
} }
})
} }
} }

View file

@ -5,7 +5,7 @@ use serde::de;
use crate::web::error::{ErrorRenderer, PathError}; use crate::web::error::{ErrorRenderer, PathError};
use crate::web::{FromRequest, HttpRequest}; use crate::web::{FromRequest, HttpRequest};
use crate::{http::Payload, router::PathDeserializer, util::Ready}; use crate::{http::Payload, router::PathDeserializer};
#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's path. /// Extract typed information from the request's path.
@ -154,11 +154,8 @@ where
T: de::DeserializeOwned, T: de::DeserializeOwned,
{ {
type Error = PathError; type Error = PathError;
type Future = Ready<Self, Self::Error>;
#[inline] async fn from_request(req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
Ready::from(
de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
.map(|inner| Path { inner }) .map(|inner| Path { inner })
.map_err(move |e| { .map_err(move |e| {
@ -168,8 +165,7 @@ where
req.path() req.path()
); );
PathError::from(e) PathError::from(e)
}), })
)
} }
} }

View file

@ -5,7 +5,7 @@ use encoding_rs::UTF_8;
use mime::Mime; use mime::Mime;
use crate::http::{error, header, HttpMessage}; use crate::http::{error, header, HttpMessage};
use crate::util::{stream_recv, BoxFuture, Bytes, BytesMut, Either, Ready, Stream}; use crate::util::{stream_recv, BoxFuture, Bytes, BytesMut, Stream};
use crate::web::error::{ErrorRenderer, PayloadError}; use crate::web::error::{ErrorRenderer, PayloadError};
use crate::web::{FromRequest, HttpRequest}; use crate::web::{FromRequest, HttpRequest};
@ -107,11 +107,13 @@ impl Stream for Payload {
/// ``` /// ```
impl<Err: ErrorRenderer> FromRequest<Err> for Payload { impl<Err: ErrorRenderer> FromRequest<Err> for Payload {
type Error = Err::Container; type Error = Err::Container;
type Future = Ready<Payload, Self::Error>;
#[inline] #[inline]
fn from_request(_: &HttpRequest, payload: &mut crate::http::Payload) -> Self::Future { async fn from_request(
Ready::Ok(Payload(payload.take())) _: &HttpRequest,
payload: &mut crate::http::Payload,
) -> Result<Payload, Self::Error> {
Ok(Payload(payload.take()))
} }
} }
@ -141,11 +143,11 @@ impl<Err: ErrorRenderer> FromRequest<Err> for Payload {
/// ``` /// ```
impl<Err: ErrorRenderer> FromRequest<Err> for Bytes { impl<Err: ErrorRenderer> FromRequest<Err> for Bytes {
type Error = PayloadError; type Error = PayloadError;
type Future =
Either<BoxFuture<'static, Result<Bytes, Self::Error>>, Ready<Bytes, Self::Error>>;
#[inline] async fn from_request(
fn from_request(req: &HttpRequest, payload: &mut crate::http::Payload) -> Self::Future { req: &HttpRequest,
payload: &mut crate::http::Payload,
) -> Result<Bytes, Self::Error> {
let tmp; let tmp;
let cfg = if let Some(cfg) = req.app_state::<PayloadConfig>() { let cfg = if let Some(cfg) = req.app_state::<PayloadConfig>() {
cfg cfg
@ -155,11 +157,11 @@ impl<Err: ErrorRenderer> FromRequest<Err> for Bytes {
}; };
if let Err(e) = cfg.check_mimetype(req) { if let Err(e) = cfg.check_mimetype(req) {
return Either::Right(Ready::Err(e)); Err(e)
} } else {
let limit = cfg.limit; let limit = cfg.limit;
Either::Left(Box::pin(HttpMessageBody::new(req, payload).limit(limit))) HttpMessageBody::new(req, payload).limit(limit).await
}
} }
} }
@ -192,11 +194,11 @@ impl<Err: ErrorRenderer> FromRequest<Err> for Bytes {
/// ``` /// ```
impl<Err: ErrorRenderer> FromRequest<Err> for String { impl<Err: ErrorRenderer> FromRequest<Err> for String {
type Error = PayloadError; type Error = PayloadError;
type Future =
Either<BoxFuture<'static, Result<String, Self::Error>>, Ready<String, Self::Error>>;
#[inline] async fn from_request(
fn from_request(req: &HttpRequest, payload: &mut crate::http::Payload) -> Self::Future { req: &HttpRequest,
payload: &mut crate::http::Payload,
) -> Result<String, Self::Error> {
let tmp; let tmp;
let cfg = if let Some(cfg) = req.app_state::<PayloadConfig>() { let cfg = if let Some(cfg) = req.app_state::<PayloadConfig>() {
cfg cfg
@ -207,19 +209,16 @@ impl<Err: ErrorRenderer> FromRequest<Err> for String {
// check content-type // check content-type
if let Err(e) = cfg.check_mimetype(req) { if let Err(e) = cfg.check_mimetype(req) {
return Either::Right(Ready::Err(e)); return Err(e);
} }
// check charset // check charset
let encoding = match req.encoding() { let encoding = match req.encoding() {
Ok(enc) => enc, Ok(enc) => enc,
Err(e) => return Either::Right(Ready::Err(PayloadError::from(e))), Err(e) => return Err(PayloadError::from(e)),
}; };
let limit = cfg.limit; let limit = cfg.limit;
let fut = HttpMessageBody::new(req, payload).limit(limit); let body = HttpMessageBody::new(req, payload).limit(limit).await?;
Either::Left(Box::pin(async move {
let body = fut.await?;
if encoding == UTF_8 { if encoding == UTF_8 {
Ok(str::from_utf8(body.as_ref()) Ok(str::from_utf8(body.as_ref())
@ -231,7 +230,6 @@ impl<Err: ErrorRenderer> FromRequest<Err> for String {
.map(|s| s.into_owned()) .map(|s| s.into_owned())
.ok_or(PayloadError::Decoding)?) .ok_or(PayloadError::Decoding)?)
} }
}))
} }
} }
/// Payload configuration for request's payload. /// Payload configuration for request's payload.

View file

@ -3,9 +3,9 @@ use std::{fmt, ops};
use serde::de; use serde::de;
use crate::http::Payload;
use crate::web::error::{ErrorRenderer, QueryPayloadError}; use crate::web::error::{ErrorRenderer, QueryPayloadError};
use crate::web::{FromRequest, HttpRequest}; use crate::web::{FromRequest, HttpRequest};
use crate::{http::Payload, util::Ready};
/// Extract typed information from the request's query. /// Extract typed information from the request's query.
/// ///
@ -128,12 +128,11 @@ where
Err: ErrorRenderer, Err: ErrorRenderer,
{ {
type Error = QueryPayloadError; type Error = QueryPayloadError;
type Future = Ready<Self, Self::Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { async fn from_request(req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
serde_urlencoded::from_str::<T>(req.query_string()) serde_urlencoded::from_str::<T>(req.query_string())
.map(|val| Ready::Ok(Query(val))) .map(|val| Ok(Query(val)))
.unwrap_or_else(move |e| { .unwrap_or_else(move |e| {
let e = QueryPayloadError::Deserialize(e); let e = QueryPayloadError::Deserialize(e);
@ -142,7 +141,7 @@ where
Request path: {:?}", Request path: {:?}",
req.path() req.path()
); );
Ready::Err(e) Err(e)
}) })
} }
} }

View file

@ -1,10 +1,10 @@
use std::{marker::PhantomData, ops::Deref}; use std::{marker::PhantomData, ops::Deref};
use crate::http::Payload;
use crate::web::error::{ErrorRenderer, StateExtractorError}; use crate::web::error::{ErrorRenderer, StateExtractorError};
use crate::web::extract::FromRequest; use crate::web::extract::FromRequest;
use crate::web::httprequest::HttpRequest; use crate::web::httprequest::HttpRequest;
use crate::web::service::AppState; use crate::web::service::AppState;
use crate::{http::Payload, util::Ready};
/// Application state. /// Application state.
/// ///
@ -78,19 +78,18 @@ impl<T> Clone for State<T> {
impl<T: 'static, E: ErrorRenderer> FromRequest<E> for State<T> { impl<T: 'static, E: ErrorRenderer> FromRequest<E> for State<T> {
type Error = StateExtractorError; type Error = StateExtractorError;
type Future = Ready<Self, Self::Error>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { async fn from_request(req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
if req.0.app_state.contains::<T>() { if req.0.app_state.contains::<T>() {
Ready::Ok(Self(req.0.app_state.clone(), PhantomData)) Ok(Self(req.0.app_state.clone(), PhantomData))
} else { } else {
log::debug!( log::debug!(
"Failed to construct App-level State extractor. \ "Failed to construct App-level State extractor. \
Request path: {:?}", Request path: {:?}",
req.path() req.path()
); );
Ready::Err(StateExtractorError::NotConfigured) Err(StateExtractorError::NotConfigured)
} }
} }
} }