diff --git a/src/extractor.rs b/src/extractor.rs index 3fcbeeae2..aca43562f 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -8,7 +8,7 @@ use error::Error; use httprequest::HttpRequest; -pub trait HttpRequestExtractor: Sized where T: DeserializeOwned, S: 'static +pub trait HttpRequestExtractor: Sized where S: 'static { type Result: Future; @@ -88,7 +88,7 @@ impl Path { } -impl HttpRequestExtractor for Path +impl HttpRequestExtractor for Path where T: DeserializeOwned, S: 'static { type Result = FutureResult; @@ -175,7 +175,7 @@ impl Query { } } -impl HttpRequestExtractor for Query +impl HttpRequestExtractor for Query where T: de::DeserializeOwned, S: 'static { type Result = FutureResult; diff --git a/src/json.rs b/src/json.rs index b97f6f75c..18ec2be68 100644 --- a/src/json.rs +++ b/src/json.rs @@ -63,7 +63,7 @@ impl Responder for Json { } } -impl HttpRequestExtractor for Json +impl HttpRequestExtractor for Json where T: DeserializeOwned + 'static, S: 'static { type Result = Box>; diff --git a/src/lib.rs b/src/lib.rs index 1f3af3b6e..ed2fa3336 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,7 +186,7 @@ pub mod dev { pub use context::Drain; pub use info::ConnectionInfo; pub use handler::Handler; - pub use with::{With, WithHandler}; + pub use with::WithHandler; pub use json::JsonBody; pub use router::{Router, Pattern}; pub use param::{FromParam, Params}; diff --git a/src/resource.rs b/src/resource.rs index 5a202115b..cb0963e31 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -3,7 +3,6 @@ use std::marker::PhantomData; use smallvec::SmallVec; use http::{Method, StatusCode}; -use serde::de::DeserializeOwned; use pred; use body::Body; @@ -142,10 +141,9 @@ impl Resource { /// ```rust,ignore /// Resource::resource("/", |r| r.route().with(index) /// ``` - pub fn with(&mut self, handler: H) - where H: WithHandler, - D: HttpRequestExtractor + 'static, - T: DeserializeOwned + 'static, + pub fn with(&mut self, handler: H) + where H: WithHandler, + T: HttpRequestExtractor + 'static, { self.routes.push(Route::default()); self.routes.last_mut().unwrap().with(handler) diff --git a/src/route.rs b/src/route.rs index 37bb13576..bacf12fc6 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,7 +1,6 @@ use std::mem; use std::rc::Rc; use std::marker::PhantomData; -use serde::de::DeserializeOwned; use futures::{Async, Future, Poll}; use error::Error; @@ -12,7 +11,7 @@ use middleware::{Middleware, Response as MiddlewareResponse, Started as Middlewa use httpcodes::HttpNotFound; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use with::{with, WithHandler}; +use with::{with, with2, with3, WithHandler}; use extractor::HttpRequestExtractor; /// Resource route definition @@ -137,13 +136,62 @@ impl Route { /// |r| r.method(Method::GET).with(index)); // <- use `with` extractor /// } /// ``` - pub fn with(&mut self, handler: H) - where H: WithHandler, - D: HttpRequestExtractor + 'static, - T: DeserializeOwned + 'static, + pub fn with(&mut self, handler: H) + where H: WithHandler, + T: HttpRequestExtractor + 'static, { self.h(with(handler)) } + + /// Set handler function, function has to accept two request extractors. + /// + /// ```rust + /// # extern crate bytes; + /// # extern crate actix_web; + /// # extern crate futures; + /// #[macro_use] extern crate serde_derive; + /// use actix_web::*; + /// use actix_web::Path; + /// + /// #[derive(Deserialize)] + /// struct PParam { + /// username: String, + /// } + /// + /// #[derive(Deserialize)] + /// struct QParam { + /// count: u32, + /// } + /// + /// /// extract path info using serde + /// fn index(p: Path, q: Query) -> Result { + /// Ok(format!("Welcome {}!", p.username)) + /// } + /// + /// fn main() { + /// let app = Application::new().resource( + /// "/{username}/index.html", // <- define path parameters + /// |r| r.method(Method::GET).with2(index)); // <- use `with` extractor + /// } + /// ``` + pub fn with2(&mut self, handler: F) + where F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor + 'static, + T2: HttpRequestExtractor + 'static, + { + self.h(with2(handler)) + } + + pub fn with3(&mut self, handler: F) + where F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor + 'static, + T2: HttpRequestExtractor + 'static, + T3: HttpRequestExtractor + 'static, + { + self.h(with3(handler)) + } } /// `RouteHandler` wrapper. This struct is required because it needs to be shared diff --git a/src/test.rs b/src/test.rs index ff358fde1..b43f8cf07 100644 --- a/src/test.rs +++ b/src/test.rs @@ -27,6 +27,7 @@ use application::{Application, HttpApplication}; use param::Params; use router::Router; use payload::Payload; +use resource::Resource; use httprequest::HttpRequest; use httpresponse::HttpResponse; use server::{HttpServer, IntoHttpHandler, ServerSettings}; @@ -395,6 +396,15 @@ impl TestApp { self.app = Some(self.app.take().unwrap().middleware(mw)); self } + + /// Register resource. This method is similar + /// to `Application::resource()` method. + pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp + where F: FnOnce(&mut Resource) + 'static + { + self.app = Some(self.app.take().unwrap().resource(path, f)); + self + } } impl IntoHttpHandler for TestApp { diff --git a/src/with.rs b/src/with.rs index 0bdcd7acb..f0a139c5c 100644 --- a/src/with.rs +++ b/src/with.rs @@ -1,7 +1,6 @@ use std::rc::Rc; use std::cell::UnsafeCell; use std::marker::PhantomData; -use serde::de::DeserializeOwned; use futures::{Async, Future, Poll}; use error::Error; @@ -13,62 +12,57 @@ use extractor::HttpRequestExtractor; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] -pub trait WithHandler: 'static - where D: HttpRequestExtractor, T: DeserializeOwned, S: 'static +pub trait WithHandler: 'static + where T: HttpRequestExtractor, S: 'static { /// The type of value that handler will return. type Result: Responder; /// Handle request - fn handle(&mut self, data: D) -> Self::Result; + fn handle(&mut self, data: T) -> Self::Result; } /// WithHandler for Fn() -impl WithHandler for F - where F: Fn(D) -> R + 'static, +impl WithHandler for F + where F: Fn(T) -> R + 'static, R: Responder + 'static, - D: HttpRequestExtractor, - T: DeserializeOwned, + T: HttpRequestExtractor, S: 'static, { type Result = R; - fn handle(&mut self, item: D) -> R { + fn handle(&mut self, item: T) -> R { (self)(item) } } -pub(crate) fn with(h: H) -> With - where H: WithHandler, - D: HttpRequestExtractor, - T: DeserializeOwned, +pub(crate) +fn with(h: H) -> With + where H: WithHandler, + T: HttpRequestExtractor, { - With{hnd: Rc::new(UnsafeCell::new(h)), - _t: PhantomData, _d: PhantomData, _s: PhantomData} + With{hnd: Rc::new(UnsafeCell::new(h)), _t: PhantomData, _s: PhantomData} } -pub struct With - where H: WithHandler, - D: HttpRequestExtractor, - T: DeserializeOwned, +pub struct With + where H: WithHandler, + T: HttpRequestExtractor, S: 'static, { hnd: Rc>, _t: PhantomData, - _d: PhantomData, _s: PhantomData, } -impl Handler for With - where H: WithHandler, - D: HttpRequestExtractor, - T: DeserializeOwned, - T: 'static, D: 'static, S: 'static, H: 'static +impl Handler for With + where H: WithHandler, + T: HttpRequestExtractor + 'static, + S: 'static, H: 'static { type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { - let fut = Box::new(D::extract(&req)); + let fut = Box::new(T::extract(&req)); Reply::async( WithHandlerFut{ @@ -76,31 +70,25 @@ impl Handler for With hnd: Rc::clone(&self.hnd), fut1: Some(fut), fut2: None, - _t: PhantomData, - _d: PhantomData, }) } } -struct WithHandlerFut - where H: WithHandler, - D: HttpRequestExtractor, - T: DeserializeOwned, - T: 'static, D: 'static, S: 'static +struct WithHandlerFut + where H: WithHandler, + T: HttpRequestExtractor, + T: 'static, S: 'static { hnd: Rc>, req: HttpRequest, - fut1: Option>>, + fut1: Option>>, fut2: Option>>, - _t: PhantomData, - _d: PhantomData, } -impl Future for WithHandlerFut - where H: WithHandler, - D: HttpRequestExtractor, - T: DeserializeOwned, - T: 'static, D: 'static, S: 'static +impl Future for WithHandlerFut + where H: WithHandler, + T: HttpRequestExtractor + 'static, + S: 'static { type Item = HttpResponse; type Error = Error; @@ -131,3 +119,248 @@ impl Future for WithHandlerFut self.poll() } } + +pub(crate) +fn with2(h: F) -> With2 + where F: Fn(T1, T2) -> R, + R: Responder, + T1: HttpRequestExtractor, + T2: HttpRequestExtractor, +{ + With2{hnd: Rc::new(UnsafeCell::new(h)), + _t1: PhantomData, _t2: PhantomData, _s: PhantomData} +} + +pub struct With2 + where F: Fn(T1, T2) -> R, + R: Responder, + T1: HttpRequestExtractor, + T2: HttpRequestExtractor, + S: 'static, +{ + hnd: Rc>, + _t1: PhantomData, + _t2: PhantomData, + _s: PhantomData, +} + +impl Handler for With2 + where F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor + 'static, + T2: HttpRequestExtractor + 'static, + S: 'static +{ + type Result = Reply; + + fn handle(&mut self, req: HttpRequest) -> Self::Result { + let fut = Box::new(T1::extract(&req)); + + Reply::async( + WithHandlerFut2{ + req, + hnd: Rc::clone(&self.hnd), + item: None, + fut1: Some(fut), + fut2: None, + fut3: None, + }) + } +} + +struct WithHandlerFut2 + where F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor + 'static, + T2: HttpRequestExtractor + 'static, + S: 'static +{ + hnd: Rc>, + req: HttpRequest, + item: Option, + fut1: Option>>, + fut2: Option>>, + fut3: Option>>, +} + +impl Future for WithHandlerFut2 + where F: Fn(T1, T2) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor + 'static, + T2: HttpRequestExtractor + 'static, + S: 'static +{ + type Item = HttpResponse; + type Error = Error; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut3 { + return fut.poll() + } + + if self.fut1.is_some() { + match self.fut1.as_mut().unwrap().poll()? { + Async::Ready(item) => { + self.item = Some(item); + self.fut1.take(); + self.fut2 = Some(Box::new(T2::extract(&self.req))); + }, + Async::NotReady => return Ok(Async::NotReady), + } + } + + let item = match self.fut2.as_mut().unwrap().poll()? { + Async::Ready(item) => item, + Async::NotReady => return Ok(Async::NotReady), + }; + + let hnd: &mut F = unsafe{&mut *self.hnd.get()}; + let item = match (*hnd)(self.item.take().unwrap(), item) + .respond_to(self.req.without_state()) + { + Ok(item) => item.into(), + Err(err) => return Err(err.into()), + }; + + match item.into() { + ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => self.fut3 = Some(fut), + } + + self.poll() + } +} + +pub(crate) +fn with3(h: F) -> With3 + where F: Fn(T1, T2, T3) -> R + 'static, + R: Responder, + T1: HttpRequestExtractor, + T2: HttpRequestExtractor, + T3: HttpRequestExtractor, +{ + With3{hnd: Rc::new(UnsafeCell::new(h)), + _s: PhantomData, _t1: PhantomData, _t2: PhantomData, _t3: PhantomData} +} + +pub struct With3 + where F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor, + T2: HttpRequestExtractor, + T3: HttpRequestExtractor, + S: 'static, +{ + hnd: Rc>, + _t1: PhantomData, + _t2: PhantomData, + _t3: PhantomData, + _s: PhantomData, +} + +impl Handler for With3 + where F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor, + T2: HttpRequestExtractor, + T3: HttpRequestExtractor, + T1: 'static, T2: 'static, T3: 'static, S: 'static +{ + type Result = Reply; + + fn handle(&mut self, req: HttpRequest) -> Self::Result { + let fut = Box::new(T1::extract(&req)); + + Reply::async( + WithHandlerFut3{ + req, + hnd: Rc::clone(&self.hnd), + item1: None, + item2: None, + fut1: Some(fut), + fut2: None, + fut3: None, + fut4: None, + }) + } +} + +struct WithHandlerFut3 + where F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor + 'static, + T2: HttpRequestExtractor + 'static, + T3: HttpRequestExtractor + 'static, + S: 'static +{ + hnd: Rc>, + req: HttpRequest, + item1: Option, + item2: Option, + fut1: Option>>, + fut2: Option>>, + fut3: Option>>, + fut4: Option>>, +} + +impl Future for WithHandlerFut3 + where F: Fn(T1, T2, T3) -> R + 'static, + R: Responder + 'static, + T1: HttpRequestExtractor + 'static, + T2: HttpRequestExtractor + 'static, + T3: HttpRequestExtractor + 'static, + S: 'static +{ + type Item = HttpResponse; + type Error = Error; + + fn poll(&mut self) -> Poll { + if let Some(ref mut fut) = self.fut4 { + return fut.poll() + } + + if self.fut1.is_some() { + match self.fut1.as_mut().unwrap().poll()? { + Async::Ready(item) => { + self.item1 = Some(item); + self.fut1.take(); + self.fut2 = Some(Box::new(T2::extract(&self.req))); + }, + Async::NotReady => return Ok(Async::NotReady), + } + } + + if self.fut2.is_some() { + match self.fut2.as_mut().unwrap().poll()? { + Async::Ready(item) => { + self.item2 = Some(item); + self.fut2.take(); + self.fut3 = Some(Box::new(T3::extract(&self.req))); + }, + Async::NotReady => return Ok(Async::NotReady), + } + } + + let item = match self.fut3.as_mut().unwrap().poll()? { + Async::Ready(item) => item, + Async::NotReady => return Ok(Async::NotReady), + }; + + let hnd: &mut F = unsafe{&mut *self.hnd.get()}; + let item = match (*hnd)(self.item1.take().unwrap(), + self.item2.take().unwrap(), + item) + .respond_to(self.req.without_state()) + { + Ok(item) => item.into(), + Err(err) => return Err(err.into()), + }; + + match item.into() { + ReplyItem::Message(resp) => return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => self.fut4 = Some(fut), + } + + self.poll() + } +} diff --git a/tests/test_handlers.rs b/tests/test_handlers.rs new file mode 100644 index 000000000..b38fdfbd0 --- /dev/null +++ b/tests/test_handlers.rs @@ -0,0 +1,63 @@ +extern crate actix; +extern crate actix_web; +extern crate tokio_core; +extern crate futures; +extern crate h2; +extern crate http; +extern crate bytes; +#[macro_use] extern crate serde_derive; + +use actix_web::*; +use bytes::Bytes; +use http::StatusCode; + +#[derive(Deserialize)] +struct PParam { + username: String, +} + +#[test] +fn test_path_extractor() { + let mut srv = test::TestServer::new(|app| { + app.resource( + "/{username}/index.html", |r| r.with( + |p: Path| format!("Welcome {}!", p.username))); + } + ); + + // client request + let request = srv.get().uri(srv.url("/test/index.html")) + .finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); +} + +#[test] +fn test_query_extractor() { + let mut srv = test::TestServer::new(|app| { + app.resource( + "/index.html", |r| r.with( + |p: Query| format!("Welcome {}!", p.username))); + } + ); + + // client request + let request = srv.get().uri(srv.url("/index.html?username=test")) + .finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.execute(response.body()).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"Welcome test!")); + + // client request + let request = srv.get().uri(srv.url("/index.html")) + .finish().unwrap(); + let response = srv.execute(request.send()).unwrap(); + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +}