This commit is contained in:
Ali MJ Al-Nasrawy 2021-11-24 23:31:20 +01:00 committed by GitHub
commit 08fc79a4d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 233 additions and 77 deletions

View File

@ -6,8 +6,8 @@ use futures_core::future::LocalBoxFuture;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest, dev::Payload, error::ErrorInternalServerError, request::HttpRequest, Error, FromRequest,
Error, FromRequestX,
}; };
/// Data factory. /// Data factory.
@ -143,6 +143,29 @@ impl<T: ?Sized + 'static> FromRequest for Data<T> {
} }
} }
impl<'a, T: ?Sized + 'static> FromRequestX<'a> for &Data<T> {
type Output = &'a Data<T>;
type Error = Error;
type Future = Ready<Result<Self::Output, Error>>;
#[inline]
fn from_request(req: &'a HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.app_data::<Data<T>>() {
ok(st)
} else {
log::debug!(
"Failed to construct App-level Data extractor. \
Request path: {:?} (type: {})",
req.path(),
type_name::<T>(),
);
err(ErrorInternalServerError(
"App data is not configured, to configure construct it with web::Data::new() and pass it to App::app_data()",
))
}
}
}
impl<T: ?Sized + 'static> DataFactory for Data<T> { impl<T: ?Sized + 'static> DataFactory for Data<T> {
fn create(&self, extensions: &mut Extensions) -> bool { fn create(&self, extensions: &mut Extensions) -> bool {
extensions.insert(Data(self.0.clone())); extensions.insert(Data(self.0.clone()));
@ -160,6 +183,9 @@ mod tests {
web, App, HttpResponse, web, App, HttpResponse,
}; };
// shadow
trait FromRequest {}
// allow deprecated App::data // allow deprecated App::data
#[allow(deprecated)] #[allow(deprecated)]
#[actix_rt::test] #[actix_rt::test]

View File

@ -76,6 +76,38 @@ pub trait FromRequest: Sized {
} }
} }
pub trait FromRequestX<'a>: Sized {
/// Must be Self unless the extractor borrows request.
type Output;
/// The associated error which can be returned.
// TODO Consider adding 'static bound
type Error: Into<Error>;
/// Future that resolves to a Self.
type Future: Future<Output = Result<Self::Output, Self::Error>>;
/// Create a Self from request parts asynchronously.
fn from_request(req: &'a HttpRequest, payload: &mut Payload) -> Self::Future;
/// Create a Self from request head asynchronously.
///
/// This method is short for `T::from_request(req, &mut Payload::None)`.
fn extract(req: &'a HttpRequest) -> Self::Future {
Self::from_request(req, &mut Payload::None)
}
}
impl<'a, T: FromRequest> FromRequestX<'a> for T {
type Output = Self;
type Error = <Self as FromRequest>::Error;
type Future = <Self as FromRequest>::Future;
fn from_request(req: &'a HttpRequest, payload: &mut Payload) -> Self::Future {
Self::from_request(req, payload)
}
}
/// Optionally extract a field from the request /// Optionally extract a field from the request
/// ///
/// If the FromRequest for T fails, return None rather than returning an error response /// If the FromRequest for T fails, return None rather than returning an error response
@ -123,16 +155,16 @@ pub trait FromRequest: Sized {
/// ); /// );
/// } /// }
/// ``` /// ```
impl<T: 'static> FromRequest for Option<T> impl<'a, T> FromRequestX<'a> for Option<T>
where where
T: FromRequest, T: FromRequestX<'a>,
T::Future: 'static,
{ {
type Output = Option<T::Output>;
type Error = Error; type Error = Error;
type Future = FromRequestOptFuture<T::Future>; type Future = FromRequestOptFuture<T::Future>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &'a HttpRequest, payload: &mut Payload) -> Self::Future {
FromRequestOptFuture { FromRequestOptFuture {
fut: T::from_request(req, payload), fut: T::from_request(req, payload),
} }
@ -209,6 +241,7 @@ where
/// ); /// );
/// } /// }
/// ``` /// ```
// TODO
impl<T> FromRequest for Result<T, T::Error> impl<T> FromRequest for Result<T, T::Error>
where where
T: FromRequest + 'static, T: FromRequest + 'static,
@ -266,6 +299,26 @@ impl FromRequest for Uri {
} }
} }
impl<'a> FromRequestX<'a> for &Uri {
type Output = &'a Uri;
type Error = Infallible;
type Future = Ready<Result<Self::Output, Self::Error>>;
fn from_request(req: &'a HttpRequest, _: &mut Payload) -> Self::Future {
ok(req.uri())
}
}
impl<'a> FromRequestX<'a> for &HttpRequest {
type Output = &'a HttpRequest;
type Error = Infallible;
type Future = Ready<Result<Self::Output, Self::Error>>;
fn from_request(req: &'a HttpRequest, _: &mut Payload) -> Self::Future {
ok(req)
}
}
/// Extract the request's method. /// Extract the request's method.
/// ///
/// # Examples /// # Examples
@ -318,38 +371,41 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
// redundant imports // redundant imports
use super::*; use super::*;
use std::marker::PhantomData;
/// A helper struct to allow us to pin-project through /// A helper struct to allow us to pin-project through
/// to individual fields /// to individual fields
#[pin_project::pin_project] #[pin_project::pin_project]
struct FutWrapper<$($T: FromRequest),+>($(#[pin] $T::Future),+); struct FutWrapper<'a, $($T: FromRequestX<'a>),+>($(#[pin] $T::Future,)+ PhantomData<&'a ()>);
/// FromRequest implementation for tuple /// FromRequest implementation for tuple
#[doc(hidden)] #[doc(hidden)]
#[allow(unused_parens)] #[allow(unused_parens)]
impl<$($T: FromRequest + 'static),+> FromRequest for ($($T,)+) impl<'a, $($T: FromRequestX<'a>),+> FromRequestX<'a> for ($($T,)+)
{ {
type Output = ($($T::Output,)+);
type Error = Error; type Error = Error;
type Future = $fut_type<$($T),+>; type Future = $fut_type<'a, $($T),+>;
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &'a HttpRequest, payload: &mut Payload) -> Self::Future {
$fut_type { $fut_type {
items: <($(Option<$T>,)+)>::default(), items: <($(Option<$T::Output>,)+)>::default(),
futs: FutWrapper($($T::from_request(req, payload),)+), futs: FutWrapper($($T::from_request(req, payload),)+ PhantomData),
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
#[pin_project::pin_project] #[pin_project::pin_project]
pub struct $fut_type<$($T: FromRequest),+> { pub struct $fut_type<'a, $($T: FromRequestX<'a>),+> {
items: ($(Option<$T>,)+), items: ($(Option<$T::Output>,)+),
#[pin] #[pin]
futs: FutWrapper<$($T,)+>, futs: FutWrapper<'a, $($T,)+>,
} }
impl<$($T: FromRequest),+> Future for $fut_type<$($T),+> impl<'a, $($T: FromRequestX<'a>),+> Future for $fut_type<'a, $($T),+>
{ {
type Output = Result<($($T,)+), Error>; type Output = Result<($($T::Output,)+), Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project(); let mut this = self.project();
@ -405,6 +461,9 @@ mod tests {
use crate::test::TestRequest; use crate::test::TestRequest;
use crate::types::{Form, FormConfig}; use crate::types::{Form, FormConfig};
// shadow
trait FromRequest {}
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
struct Info { struct Info {
hello: String, hello: String,

View File

@ -7,31 +7,47 @@ use actix_service::{
use crate::{ use crate::{
service::{ServiceRequest, ServiceResponse}, service::{ServiceRequest, ServiceResponse},
Error, FromRequest, HttpResponse, Responder, Error, FromRequestX, HttpResponse, Responder,
}; };
// TODO inaccessible docs
/// A request handler is an async function that accepts zero or more parameters that can be /// A request handler is an async function that accepts zero or more parameters that can be
/// extracted from a request (i.e., [`impl FromRequest`](crate::FromRequest)) and returns a type /// extracted from a request (i.e., [`impl FromRequest`](crate::FromRequest)) and returns a type
/// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait). /// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
/// ///
/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not /// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
/// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information. /// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information.
pub trait Handler<T, R>: Clone + 'static pub trait Handler<'a, T: FromRequestX<'a>>: Clone + 'static {
where // TODO why 'static ??
R: Future, type Response: Responder + 'static;
R::Output: Responder, type Future: Future<Output = Self::Response>;
{
fn call(&self, param: T) -> R; fn handle(&'a self, _: T::Output) -> Self::Future;
} }
pub fn handler_service<F, T, R>( impl<'a, F, T, Fut, Resp> Handler<'a, T> for F
handler: F, where
F: FnX<T>,
F: FnX<T::Output, Output = Fut>,
F: Clone + 'static,
T: FromRequestX<'a>,
Fut: Future<Output = Resp>,
Resp: Responder + 'static,
{
type Response = Resp;
type Future = Fut;
fn handle(&'a self, data: T::Output) -> Self::Future {
self.call(data)
}
}
pub fn handler_service<H, T>(
handler: H,
) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()> ) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>
where where
F: Handler<T, R>, H: for<'a> Handler<'a, T>,
T: FromRequest, T: for<'a> FromRequestX<'a>,
R: Future,
R::Output: Responder,
{ {
boxed::factory(fn_service(move |req: ServiceRequest| { boxed::factory(fn_service(move |req: ServiceRequest| {
let handler = handler.clone(); let handler = handler.clone();
@ -39,37 +55,95 @@ where
let (req, mut payload) = req.into_parts(); let (req, mut payload) = req.into_parts();
let res = match T::from_request(&req, &mut payload).await { let res = match T::from_request(&req, &mut payload).await {
Err(err) => HttpResponse::from_error(err), Err(err) => HttpResponse::from_error(err),
Ok(data) => handler.call(data).await.respond_to(&req), Ok(data) => handler.handle(data).await.respond_to(&req),
}; };
Ok(ServiceResponse::new(req, res)) Ok(ServiceResponse::new(req, res))
} }
})) }))
} }
/// Same as [`std::ops::Fn`]
pub trait FnX<Args> {
type Output;
fn call(&self, args: Args) -> Self::Output;
}
/// FromRequest trait impl for tuples /// FromRequest trait impl for tuples
macro_rules! factory_tuple ({ $($param:ident)* } => { macro_rules! fn_tuple ({ $($param:ident)* } => {
impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func impl<Func, $($param,)* O> FnX<($($param,)*)> for Func
where Func: Fn($($param),*) -> Res + Clone + 'static, where Func: Fn($($param),*) -> O,
Res: Future,
Res::Output: Responder,
{ {
type Output = O;
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn call(&self, ($($param,)*): ($($param,)*)) -> Res { fn call(&self, ($($param,)*): ($($param,)*)) -> O {
(self)($($param,)*) (self)($($param,)*)
} }
} }
}); });
factory_tuple! {} fn_tuple! {}
factory_tuple! { A } fn_tuple! { A }
factory_tuple! { A B } fn_tuple! { A B }
factory_tuple! { A B C } fn_tuple! { A B C }
factory_tuple! { A B C D } fn_tuple! { A B C D }
factory_tuple! { A B C D E } fn_tuple! { A B C D E }
factory_tuple! { A B C D E F } fn_tuple! { A B C D E F }
factory_tuple! { A B C D E F G } fn_tuple! { A B C D E F G }
factory_tuple! { A B C D E F G H } fn_tuple! { A B C D E F G H }
factory_tuple! { A B C D E F G H I } fn_tuple! { A B C D E F G H I }
factory_tuple! { A B C D E F G H I J } fn_tuple! { A B C D E F G H I J }
factory_tuple! { A B C D E F G H I J K } fn_tuple! { A B C D E F G H I J K }
factory_tuple! { A B C D E F G H I J K L } fn_tuple! { A B C D E F G H I J K L }
#[cfg(test)]
mod test {
use serde::Deserialize;
use super::*;
use crate::{
dev::Service,
http::StatusCode,
test::{init_service, TestRequest},
web, App, HttpRequest, HttpResponse,
};
#[derive(Deserialize)]
struct Params<'a> {
name: &'a str,
}
#[derive(Deserialize)]
struct ParamsOwned {
name: String,
}
async fn handler(
req: &HttpRequest,
(data, params1): (Option<&web::Data<usize>>, web::Path<ParamsOwned>),
) -> HttpResponse {
let params2 = web::Path::<Params<'_>>::extract(req).await.unwrap();
assert_eq!(params1.name, "named");
assert_eq!(params2.name, "named");
assert_eq!(data.unwrap().as_ref(), &42);
HttpResponse::Ok().finish()
}
#[actix_rt::test]
async fn test_borrowed_extractor() {
let srv = init_service(
App::new().service(
web::resource("/{name}")
.app_data(web::Data::new(42usize))
.route(web::get().to(handler)),
),
)
.await;
let req = TestRequest::with_uri("/named").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
}

View File

@ -105,7 +105,7 @@ pub use cookie;
pub use crate::app::App; pub use crate::app::App;
pub use crate::error::{Error, ResponseError, Result}; pub use crate::error::{Error, ResponseError, Result};
pub use crate::extract::FromRequest; pub use crate::extract::{FromRequest, FromRequestX};
pub use crate::request::HttpRequest; pub use crate::request::HttpRequest;
pub use crate::resource::Resource; pub use crate::resource::Resource;
pub use crate::responder::Responder; pub use crate::responder::Responder;

View File

@ -16,12 +16,12 @@ use futures_util::future::join_all;
use crate::{ use crate::{
data::Data, data::Data,
dev::{ensure_leading_slash, AppService, HttpServiceFactory, ResourceDef}, dev::{ensure_leading_slash, AppService, HttpServiceFactory, ResourceDef},
extract::FromRequestX,
guard::Guard, guard::Guard,
handler::Handler, handler::Handler,
responder::Responder,
route::{Route, RouteService}, route::{Route, RouteService},
service::{ServiceRequest, ServiceResponse}, service::{ServiceRequest, ServiceResponse},
Error, FromRequest, HttpResponse, Error, HttpResponse,
}; };
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>; type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
@ -236,12 +236,10 @@ where
/// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() }
/// App::new().service(web::resource("/").route(web::route().to(index))); /// App::new().service(web::resource("/").route(web::route().to(index)));
/// ``` /// ```
pub fn to<F, I, R>(mut self, handler: F) -> Self pub fn to<F, I>(mut self, handler: F) -> Self
where where
F: Handler<I, R>, F: for<'a> Handler<'a, I>,
I: FromRequest + 'static, I: for<'a> FromRequestX<'a>,
R: Future + 'static,
R::Output: Responder + 'static,
{ {
self.routes.push(Route::new().to(handler)); self.routes.push(Route::new().to(handler));
self self

View File

@ -1,6 +1,6 @@
#![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`) #![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`)
use std::{future::Future, rc::Rc}; use std::rc::Rc;
use actix_http::http::Method; use actix_http::http::Method;
use actix_service::{ use actix_service::{
@ -10,10 +10,11 @@ use actix_service::{
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use crate::{ use crate::{
extract::FromRequestX,
guard::{self, Guard}, guard::{self, Guard},
handler::{handler_service, Handler}, handler::{handler_service, Handler},
service::{ServiceRequest, ServiceResponse}, service::{ServiceRequest, ServiceResponse},
Error, FromRequest, HttpResponse, Responder, Error, HttpResponse,
}; };
/// Resource route definition /// Resource route definition
@ -175,12 +176,10 @@ impl Route {
/// ); /// );
/// } /// }
/// ``` /// ```
pub fn to<F, T, R>(mut self, handler: F) -> Self pub fn to<F, T>(mut self, handler: F) -> Self
where where
F: Handler<T, R>, F: for<'a> Handler<'a, T>,
T: FromRequest + 'static, T: for<'a> FromRequestX<'a>,
R: Future + 'static,
R::Output: Responder + 'static,
{ {
self.service = handler_service(handler); self.service = handler_service(handler);
self self

View File

@ -9,7 +9,7 @@ use serde::de;
use crate::{ use crate::{
dev::Payload, dev::Payload,
error::{Error, ErrorNotFound, PathError}, error::{Error, ErrorNotFound, PathError},
FromRequest, HttpRequest, FromRequestX, HttpRequest,
}; };
/// Extract typed data from request path segments. /// Extract typed data from request path segments.
@ -91,15 +91,16 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
} }
/// See [here](#usage) for example of usage as an extractor. /// See [here](#usage) for example of usage as an extractor.
impl<T> FromRequest for Path<T> impl<'a, T> FromRequestX<'a> for Path<T>
where where
T: de::DeserializeOwned, T: de::Deserialize<'a>,
{ {
type Output = Self;
type Error = Error; type Error = Error;
type Future = Ready<Result<Self, Self::Error>>; type Future = Ready<Result<Self::Output, Self::Error>>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &'a HttpRequest, _: &mut Payload) -> Self::Future {
let error_handler = req let error_handler = req
.app_data::<PathConfig>() .app_data::<PathConfig>()
.and_then(|c| c.err_handler.clone()); .and_then(|c| c.err_handler.clone());
@ -266,6 +267,7 @@ mod tests {
resource.capture_match_info(req.match_info_mut()); resource.capture_match_info(req.match_info_mut());
let (req, mut pl) = req.into_parts(); let (req, mut pl) = req.into_parts();
let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap(); let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(s.as_ref().key, "name"); assert_eq!(s.as_ref().key, "name");
assert_eq!(s.value, 32); assert_eq!(s.value, 32);

View File

@ -7,8 +7,8 @@ use actix_router::IntoPatterns;
pub use bytes::{Buf, BufMut, Bytes, BytesMut}; pub use bytes::{Buf, BufMut, Bytes, BytesMut};
use crate::{ use crate::{
error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource, error::BlockingError, extract::FromRequestX, handler::Handler, resource::Resource,
responder::Responder, route::Route, scope::Scope, service::WebService, route::Route, scope::Scope, service::WebService,
}; };
pub use crate::config::ServiceConfig; pub use crate::config::ServiceConfig;
@ -139,12 +139,10 @@ pub fn method(method: Method) -> Route {
/// web::to(index)) /// web::to(index))
/// ); /// );
/// ``` /// ```
pub fn to<F, I, R>(handler: F) -> Route pub fn to<F, I>(handler: F) -> Route
where where
F: Handler<I, R>, F: for<'a> Handler<'a, I>,
I: FromRequest + 'static, I: for<'a> FromRequestX<'a>,
R: Future + 'static,
R::Output: Responder + 'static,
{ {
Route::new().to(handler) Route::new().to(handler)
} }