diff --git a/CHANGES.md b/CHANGES.md index ac302ed0a..e73b929aa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/MIGRATION.md b/MIGRATION.md index 29bf0c348..910e99a4a 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -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 diff --git a/src/application.rs b/src/application.rs index 6885185f2..4c8946c4e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -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 { @@ -249,7 +250,7 @@ where /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> App where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/extractor.rs b/src/extractor.rs index 233ad6ce5..6d156d47a 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -332,7 +332,7 @@ impl fmt::Display for Form { /// |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 FromRequest 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 /// }) /// }); /// } diff --git a/src/json.rs b/src/json.rs index c76aeaa7d..86eefca96 100644 --- a/src/json.rs +++ b/src/json.rs @@ -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() diff --git a/src/resource.rs b/src/resource.rs index 1bf8d88fa..d884dd447 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -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 Resource { /// ``` pub fn with(&mut self, handler: F) where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/route.rs b/src/route.rs index d383d90be..e2635aa65 100644 --- a/src/route.rs +++ b/src/route.rs @@ -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 Route { /// ``` pub fn with(&mut self, handler: F) where - F: Fn(T) -> R + 'static, + F: WithFactory + 'static, R: Responder + 'static, T: FromRequest + 'static, { - self.h(With::new(handler, ::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 Route { /// 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(&mut self, handler: F, cfg_f: C) where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, C: FnOnce(&mut T::Config), { let mut cfg = ::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 Route { /// ``` pub fn with_async(&mut self, handler: F) where - F: Fn(T) -> R + 'static, + F: WithAsyncFactory, R: Future + 'static, I: Responder + 'static, E: Into + 'static, T: FromRequest + 'static, { - self.h(WithAsync::new(handler, ::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 Route { /// "/{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(&mut self, handler: F, cfg: C) where - F: Fn(T) -> R + 'static, + F: WithAsyncFactory, R: Future + 'static, I: Responder + 'static, E: Into + 'static, @@ -291,7 +292,7 @@ impl Route { { let mut extractor_cfg = ::default(); cfg(&mut extractor_cfg); - self.h(WithAsync::new(handler, extractor_cfg)); + self.h(handler.create_with_config(extractor_cfg)); } } diff --git a/src/router.rs b/src/router.rs index ff52eac5f..6dc6224ac 100644 --- a/src/router.rs +++ b/src/router.rs @@ -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 Router { pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/scope.rs b/src/scope.rs index d8a0a81ad..baf891c36 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -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 Scope { /// ``` pub fn route(mut self, path: &str, method: Method, f: F) -> Scope where - F: Fn(T) -> R + 'static, + F: WithFactory, R: Responder + 'static, T: FromRequest + 'static, { diff --git a/src/with.rs b/src/with.rs index 0af626c8b..caffe0acb 100644 --- a/src/with.rs +++ b/src/with.rs @@ -7,24 +7,74 @@ use handler::{AsyncResult, AsyncResultItem, FromRequest, Handler, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -pub(crate) struct With +trait FnWith: 'static { + fn call_with(self: &Self, T) -> R; +} + +impl R + 'static> FnWith 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: 'static +where T: FromRequest, + R: Responder, +{ + fn create(self) -> With; + + fn create_with_config(self, T::Config) -> With; +} + +#[doc(hidden)] +pub trait WithAsyncFactory: 'static +where T: FromRequest, + R: Future, + I: Responder, + E: Into, +{ + fn create(self) -> WithAsync; + + fn create_with_config(self, T::Config) -> WithAsync; +} + +// impl WithFactory<(T1, T2, T3), S, R> for F +// where F: Fn(T1, T2, T3) -> R + 'static, +// T1: FromRequest + 'static, +// T2: FromRequest + 'static, +// T3: FromRequest + '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 where - F: Fn(T) -> R, T: FromRequest, S: 'static, { - hnd: Rc, + hnd: Rc>, cfg: Rc, _s: PhantomData, } -impl With +impl With where - F: Fn(T) -> R, T: FromRequest, S: 'static, { - pub fn new(f: F, cfg: T::Config) -> Self { + pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { With { cfg: Rc::new(cfg), hnd: Rc::new(f), @@ -33,9 +83,8 @@ where } } -impl Handler for With +impl Handler for With where - F: Fn(T) -> R + 'static, R: Responder + 'static, T: FromRequest + 'static, S: 'static, @@ -60,24 +109,22 @@ where } } -struct WithHandlerFut +struct WithHandlerFut where - F: Fn(T) -> R, R: Responder, T: FromRequest + 'static, S: 'static, { started: bool, - hnd: Rc, + hnd: Rc>, cfg: Rc, req: HttpRequest, fut1: Option>>, fut2: Option>>, } -impl Future for WithHandlerFut +impl Future for WithHandlerFut where - F: Fn(T) -> R, R: Responder + 'static, T: FromRequest + '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 +#[doc(hidden)] +pub struct WithAsync where - F: Fn(T) -> R, R: Future, I: Responder, E: Into, T: FromRequest, S: 'static, { - hnd: Rc, + hnd: Rc>, cfg: Rc, _s: PhantomData, } -impl WithAsync +impl WithAsync where - F: Fn(T) -> R, R: Future, I: Responder, E: Into, T: FromRequest, S: 'static, { - pub fn new(f: F, cfg: T::Config) -> Self { + pub fn new R + 'static>(f: F, cfg: T::Config) -> Self { WithAsync { cfg: Rc::new(cfg), hnd: Rc::new(f), @@ -156,9 +202,8 @@ where } } -impl Handler for WithAsync +impl Handler for WithAsync where - F: Fn(T) -> R + 'static, R: Future + 'static, I: Responder + 'static, E: Into + 'static, @@ -186,9 +231,8 @@ where } } -struct WithAsyncHandlerFut +struct WithAsyncHandlerFut where - F: Fn(T) -> R, R: Future + 'static, I: Responder + 'static, E: Into + 'static, @@ -196,7 +240,7 @@ where S: 'static, { started: bool, - hnd: Rc, + hnd: Rc>, cfg: Rc, req: HttpRequest, fut1: Option>>, @@ -204,9 +248,8 @@ where fut3: Option>>, } -impl Future for WithAsyncHandlerFut +impl Future for WithAsyncHandlerFut where - F: Fn(T) -> R, R: Future + 'static, I: Responder + 'static, E: Into + '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 + '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 + 'static,)+ + Res: Future, + Item: Responder + 'static, + Err: Into, + 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)); diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs index c86a3e9c0..4243cd3a8 100644 --- a/tests/test_handlers.rs +++ b/tests/test_handlers.rs @@ -208,7 +208,7 @@ fn test_form_extractor2() { r.route().with_config( |form: Form| 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, Path, Query)| { + |data: Json, p: Path, _: Query| { Delay::new(Instant::now() + Duration::from_millis(10)) .and_then(move |_| { Ok(format!("Welcome {} - {}!", p.username, data.0)) diff --git a/tests/test_server.rs b/tests/test_server.rs index 5c4385680..8739b4f71 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -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())