1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-23 23:51:06 +01:00

allow to use fn with multiple arguments with .with()/.with_async()

This commit is contained in:
Nikolay Kim 2018-08-16 20:29:06 -07:00
parent 248bd388ca
commit eb1e9a785f
12 changed files with 169 additions and 55 deletions

View File

@ -1,6 +1,6 @@
# Changes
## [0.7.4] - 2018-08-xx
## [0.8.0] - 2018-08-xx
### Added
@ -12,6 +12,8 @@
### Changed
* It is allowed to use function with up to 10 parameters for handler with `extractor parameters`.
* native-tls - 0.2
* `Content-Disposition` is re-worked. Its parser is now more robust and handles quoted content better. See #461

View File

@ -1,3 +1,9 @@
## 0.8
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
even for handler with one parameter.
## 0.7
* `HttpRequest` does not implement `Stream` anymore. If you need to read request payload

View File

@ -12,6 +12,7 @@ use resource::Resource;
use router::{ResourceDef, Router};
use scope::Scope;
use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request};
use with::WithFactory;
/// Application
pub struct HttpApplication<S = ()> {
@ -249,7 +250,7 @@ where
/// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
where
F: Fn(T) -> R + 'static,
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{

View File

@ -332,7 +332,7 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
/// |r| {
/// r.method(http::Method::GET)
/// // register form handler and change form extractor configuration
/// .with_config(index, |cfg| {cfg.limit(4096);})
/// .with_config(index, |cfg| {cfg.0.limit(4096);})
/// },
/// );
/// }
@ -427,7 +427,7 @@ impl<S: 'static> FromRequest<S> for Bytes {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler with extractor params
/// cfg.limit(4096); // <- limit size of the payload
/// cfg.0.limit(4096); // <- limit size of the payload
/// })
/// });
/// }

View File

@ -172,7 +172,7 @@ where
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::POST)
/// .with_config(index, |cfg| {
/// cfg.limit(4096) // <- change json extractor configuration
/// cfg.0.limit(4096) // <- change json extractor configuration
/// .error_handler(|err, req| { // <- create custom error response
/// error::InternalError::from_response(
/// err, HttpResponse::Conflict().finish()).into()

View File

@ -13,6 +13,7 @@ use middleware::Middleware;
use pred;
use route::Route;
use router::ResourceDef;
use with::WithFactory;
#[derive(Copy, Clone)]
pub(crate) struct RouteId(usize);
@ -217,7 +218,7 @@ impl<S: 'static> Resource<S> {
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{

View File

@ -16,7 +16,7 @@ use middleware::{
Started as MiddlewareStarted,
};
use pred::Predicate;
use with::{With, WithAsync};
use with::{WithAsyncFactory, WithFactory};
/// Resource route definition
///
@ -166,15 +166,15 @@ impl<S: 'static> Route<S> {
/// ```
pub fn with<T, F, R>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
F: WithFactory<T, S, R> + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{
self.h(With::new(handler, <T::Config as Default>::default()));
self.h(handler.create());
}
/// Set handler function. Same as `.with()` but it allows to configure
/// extractor.
/// extractor. Configuration closure accepts config objects as tuple.
///
/// ```rust
/// # extern crate bytes;
@ -192,21 +192,21 @@ impl<S: 'static> Route<S> {
/// let app = App::new().resource("/index.html", |r| {
/// r.method(http::Method::GET)
/// .with_config(index, |cfg| { // <- register handler
/// cfg.limit(4096); // <- limit size of the payload
/// cfg.0.limit(4096); // <- limit size of the payload
/// })
/// });
/// }
/// ```
pub fn with_config<T, F, R, C>(&mut self, handler: F, cfg_f: C)
where
F: Fn(T) -> R + 'static,
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
C: FnOnce(&mut T::Config),
{
let mut cfg = <T::Config as Default>::default();
cfg_f(&mut cfg);
self.h(With::new(handler, cfg));
self.h(handler.create_with_config(cfg));
}
/// Set async handler function, use request extractor for parameters.
@ -240,17 +240,18 @@ impl<S: 'static> Route<S> {
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F)
where
F: Fn(T) -> R + 'static,
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
T: FromRequest<S> + 'static,
{
self.h(WithAsync::new(handler, <T::Config as Default>::default()));
self.h(handler.create());
}
/// Set async handler function, use request extractor for parameters.
/// This method allows to configure extractor.
/// This method allows to configure extractor. Configuration closure
/// accepts config objects as tuple.
///
/// ```rust
/// # extern crate bytes;
@ -275,14 +276,14 @@ impl<S: 'static> Route<S> {
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async_config(index, |cfg| {
/// cfg.limit(4096);
/// cfg.0.limit(4096);
/// }),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async_config<T, F, R, I, E, C>(&mut self, handler: F, cfg: C)
where
F: Fn(T) -> R + 'static,
F: WithAsyncFactory<T, S, R, I, E>,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
@ -291,7 +292,7 @@ impl<S: 'static> Route<S> {
{
let mut extractor_cfg = <T::Config as Default>::default();
cfg(&mut extractor_cfg);
self.h(WithAsync::new(handler, extractor_cfg));
self.h(handler.create_with_config(extractor_cfg));
}
}

View File

@ -17,6 +17,7 @@ use pred::Predicate;
use resource::{DefaultResource, Resource};
use scope::Scope;
use server::Request;
use with::WithFactory;
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum ResourceId {
@ -398,7 +399,7 @@ impl<S: 'static> Router<S> {
pub(crate) fn register_route<T, F, R>(&mut self, path: &str, method: Method, f: F)
where
F: Fn(T) -> R + 'static,
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{

View File

@ -17,6 +17,7 @@ use pred::Predicate;
use resource::{DefaultResource, Resource};
use router::{ResourceDef, Router};
use server::Request;
use with::WithFactory;
/// Resources scope
///
@ -222,7 +223,7 @@ impl<S: 'static> Scope<S> {
/// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> Scope<S>
where
F: Fn(T) -> R + 'static,
F: WithFactory<T, S, R>,
R: Responder + 'static,
T: FromRequest<S> + 'static,
{

View File

@ -7,24 +7,74 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
pub(crate) struct With<T, S, F, R>
trait FnWith<T, R>: 'static {
fn call_with(self: &Self, T) -> R;
}
impl<T, R, F: Fn(T) -> R + 'static> FnWith<T, R> for F {
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local))]
fn call_with(self: &Self, arg: T) -> R {
(*self)(arg)
}
}
#[doc(hidden)]
pub trait WithFactory<T, S, R>: 'static
where T: FromRequest<S>,
R: Responder,
{
fn create(self) -> With<T, S, R>;
fn create_with_config(self, T::Config) -> With<T, S, R>;
}
#[doc(hidden)]
pub trait WithAsyncFactory<T, S, R, I, E>: 'static
where T: FromRequest<S>,
R: Future<Item=I, Error=E>,
I: Responder,
E: Into<Error>,
{
fn create(self) -> WithAsync<T, S, R, I, E>;
fn create_with_config(self, T::Config) -> WithAsync<T, S, R, I, E>;
}
// impl<T1, T2, T3, S, F, R> WithFactory<(T1, T2, T3), S, R> for F
// where F: Fn(T1, T2, T3) -> R + 'static,
// T1: FromRequest<S> + 'static,
// T2: FromRequest<S> + 'static,
// T3: FromRequest<S> + 'static,
// R: Responder + 'static,
// S: 'static,
// {
// fn create(self) -> With<(T1, T2, T3), S, R> {
// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), (
// T1::Config::default(), T2::Config::default(), T3::Config::default()))
// }
// fn create_with_config(self, cfg: (T1::Config, T2::Config, T3::Config,)) -> With<(T1, T2, T3), S, R> {
// With::new(move |(t1, t2, t3)| (self)(t1, t2, t3), cfg)
// }
// }
#[doc(hidden)]
pub struct With<T, S, R>
where
F: Fn(T) -> R,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<F>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
_s: PhantomData<S>,
}
impl<T, S, F, R> With<T, S, F, R>
impl<T, S, R> With<T, S, R>
where
F: Fn(T) -> R,
T: FromRequest<S>,
S: 'static,
{
pub fn new(f: F, cfg: T::Config) -> Self {
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
With {
cfg: Rc::new(cfg),
hnd: Rc::new(f),
@ -33,9 +83,8 @@ where
}
}
impl<T, S, F, R> Handler<S> for With<T, S, F, R>
impl<T, S, R> Handler<S> for With<T, S, R>
where
F: Fn(T) -> R + 'static,
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
@ -60,24 +109,22 @@ where
}
}
struct WithHandlerFut<T, S, F, R>
struct WithHandlerFut<T, S, R>
where
F: Fn(T) -> R,
R: Responder,
T: FromRequest<S> + 'static,
S: 'static,
{
started: bool,
hnd: Rc<F>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
fut2: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, F, R> Future for WithHandlerFut<T, S, F, R>
impl<T, S, R> Future for WithHandlerFut<T, S, R>
where
F: Fn(T) -> R,
R: Responder + 'static,
T: FromRequest<S> + 'static,
S: 'static,
@ -108,7 +155,7 @@ where
}
};
let item = match (*self.hnd)(item).respond_to(&self.req) {
let item = match self.hnd.as_ref().call_with(item).respond_to(&self.req) {
Ok(item) => item.into(),
Err(e) => return Err(e.into()),
};
@ -124,30 +171,29 @@ where
}
}
pub(crate) struct WithAsync<T, S, F, R, I, E>
#[doc(hidden)]
pub struct WithAsync<T, S, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<E>,
T: FromRequest<S>,
S: 'static,
{
hnd: Rc<F>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
_s: PhantomData<S>,
}
impl<T, S, F, R, I, E> WithAsync<T, S, F, R, I, E>
impl<T, S, R, I, E> WithAsync<T, S, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E>,
I: Responder,
E: Into<Error>,
T: FromRequest<S>,
S: 'static,
{
pub fn new(f: F, cfg: T::Config) -> Self {
pub fn new<F: Fn(T) -> R + 'static>(f: F, cfg: T::Config) -> Self {
WithAsync {
cfg: Rc::new(cfg),
hnd: Rc::new(f),
@ -156,9 +202,8 @@ where
}
}
impl<T, S, F, R, I, E> Handler<S> for WithAsync<T, S, F, R, I, E>
impl<T, S, R, I, E> Handler<S> for WithAsync<T, S, R, I, E>
where
F: Fn(T) -> R + 'static,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
@ -186,9 +231,8 @@ where
}
}
struct WithAsyncHandlerFut<T, S, F, R, I, E>
struct WithAsyncHandlerFut<T, S, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
@ -196,7 +240,7 @@ where
S: 'static,
{
started: bool,
hnd: Rc<F>,
hnd: Rc<FnWith<T, R>>,
cfg: Rc<T::Config>,
req: HttpRequest<S>,
fut1: Option<Box<Future<Item = T, Error = Error>>>,
@ -204,9 +248,8 @@ where
fut3: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
}
impl<T, S, F, R, I, E> Future for WithAsyncHandlerFut<T, S, F, R, I, E>
impl<T, S, R, I, E> Future for WithAsyncHandlerFut<T, S, R, I, E>
where
F: Fn(T) -> R,
R: Future<Item = I, Error = E> + 'static,
I: Responder + 'static,
E: Into<Error> + 'static,
@ -257,7 +300,64 @@ where
}
};
self.fut2 = Some((*self.hnd)(item));
self.fut2 = Some(self.hnd.as_ref().call_with(item));
self.poll()
}
}
macro_rules! with_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res> WithFactory<($($T,)+), State, Res> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Responder + 'static,
State: 'static,
{
fn create(self) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> With<($($T,)+), State, Res> {
With::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
macro_rules! with_async_factory_tuple ({$(($n:tt, $T:ident)),+} => {
impl<$($T,)+ State, Func, Res, Item, Err> WithAsyncFactory<($($T,)+), State, Res, Item, Err> for Func
where Func: Fn($($T,)+) -> Res + 'static,
$($T: FromRequest<State> + 'static,)+
Res: Future<Item=Item, Error=Err>,
Item: Responder + 'static,
Err: Into<Error>,
State: 'static,
{
fn create(self) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), ($($T::Config::default(),)+))
}
fn create_with_config(self, cfg: ($($T::Config,)+)) -> WithAsync<($($T,)+), State, Res, Item, Err> {
WithAsync::new(move |($($n,)+)| (self)($($n,)+), cfg)
}
}
});
with_factory_tuple!((a, A));
with_factory_tuple!((a, A), (b, B));
with_factory_tuple!((a, A), (b, B), (c, C));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H));
with_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I));
with_async_factory_tuple!((a, A));
with_async_factory_tuple!((a, A), (b, B));
with_async_factory_tuple!((a, A), (b, B), (c, C));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H));
with_async_factory_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F), (g, G), (h, H), (i, I));

View File

@ -208,7 +208,7 @@ fn test_form_extractor2() {
r.route().with_config(
|form: Form<FormData>| format!("{}", form.username),
|cfg| {
cfg.error_handler(|err, _| {
cfg.0.error_handler(|err, _| {
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
@ -423,7 +423,7 @@ fn test_path_and_query_extractor2_async3() {
let mut srv = test::TestServer::new(|app| {
app.resource("/{username}/index.html", |r| {
r.route().with(
|(data, p, _q): (Json<Value>, Path<PParam>, Query<PParam>)| {
|data: Json<Value>, p: Path<PParam>, _: Query<PParam>| {
Delay::new(Instant::now() + Duration::from_millis(10))
.and_then(move |_| {
Ok(format!("Welcome {} - {}!", p.username, data.0))

View File

@ -13,8 +13,8 @@ extern crate tokio_reactor;
extern crate tokio_tcp;
use std::io::{Read, Write};
use std::sync::{mpsc, Arc};
use std::{net, thread, time};
use std::sync::Arc;
use std::{thread, time};
#[cfg(feature = "brotli")]
use brotli2::write::{BrotliDecoder, BrotliEncoder};
@ -32,7 +32,6 @@ use tokio::executor::current_thread;
use tokio::runtime::current_thread::Runtime;
use tokio_tcp::TcpStream;
use actix::System;
use actix_web::*;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@ -60,11 +59,13 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
#[test]
#[cfg(unix)]
fn test_start() {
use std::{mpsc, net};
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(|| {
System::run(move || {
actix::System::run(move || {
let srv = server::new(|| {
vec![App::new().resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())