From a6cbdde43f358d82372c87250ebd4bd359e506c4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 14:55:42 -0700 Subject: [PATCH] add extractor for Binary type; move all extractors to separate module --- src/body.rs | 15 ++ src/de.rs | 282 +-------------------------------- src/extractor.rs | 387 +++++++++++++++++++++++++++++++++++++++++++++ src/httpmessage.rs | 76 +-------- src/lib.rs | 5 +- 5 files changed, 413 insertions(+), 352 deletions(-) create mode 100644 src/extractor.rs diff --git a/src/body.rs b/src/body.rs index 57df3528..97b8850c 100644 --- a/src/body.rs +++ b/src/body.rs @@ -6,6 +6,10 @@ use futures::Stream; use error::Error; use context::ActorHttpContext; +use handler::Responder; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; + /// Type represent streaming body pub type BodyStream = Box>; @@ -247,6 +251,17 @@ impl AsRef<[u8]> for Binary { } } +impl Responder for Binary { + type Item = HttpResponse; + type Error = Error; + + fn respond_to(self, _: HttpRequest) -> Result { + Ok(HttpResponse::Ok() + .content_type("application/octet-stream") + .body(self)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/de.rs b/src/de.rs index 637385a3..659dc10a 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,177 +1,10 @@ use std::slice::Iter; use std::borrow::Cow; use std::convert::AsRef; -use std::ops::{Deref, DerefMut}; +use serde::de::{self, Deserializer, Visitor, Error as DeError}; -use serde_urlencoded; -use serde::de::{self, Deserializer, DeserializeOwned, Visitor, Error as DeError}; -use futures::future::{FutureResult, result}; - -use error::Error; -use handler::FromRequest; use httprequest::HttpRequest; -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Path, Result, http}; -/// -/// /// extract path info from "/{username}/{count}/?index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// fn index(info: Path<(String, u32)>) -> Result { -/// Ok(format!("Welcome {}! {}", info.0, info.1)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/?index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that implements -/// `Deserialize` trait from *serde*. -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Path, Result, http}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract path info using serde -/// fn index(info: Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -/// } -/// ``` -pub struct Path{ - inner: T -} - -impl AsRef for Path { - - fn as_ref(&self) -> &T { - &self.inner - } -} - -impl Deref for Path { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl DerefMut for Path { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Path { - /// Deconstruct to an inner value - pub fn into_inner(self) -> T { - self.inner - } -} - -impl FromRequest for Path - where T: DeserializeOwned, S: 'static -{ - type Result = FutureResult; - - #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - let req = req.clone(); - result(de::Deserialize::deserialize(PathDeserializer{req: &req}) - .map_err(|e| e.into()) - .map(|inner| Path{inner})) - } -} - -/// Extract typed information from from the request's query. -/// -/// ## Example -/// -/// ```rust -/// # extern crate bytes; -/// # extern crate actix_web; -/// # extern crate futures; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Query, http}; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// // use `with` extractor for query info -/// // this handler get called only if request's query contains `username` field -/// fn index(info: Query) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor -/// } -/// ``` -pub struct Query(T); - -impl Deref for Query { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Query { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl Query { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl FromRequest for Query - where T: de::DeserializeOwned, S: 'static -{ - type Result = FutureResult; - - #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - let req = req.clone(); - result(serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) - .map(Query)) - } -} macro_rules! unsupported_type { ($trait_fn:ident, $name:expr) => { @@ -207,6 +40,12 @@ pub struct PathDeserializer<'de, S: 'de> { req: &'de HttpRequest } +impl<'de, S: 'de> PathDeserializer<'de, S> { + pub fn new(req: &'de HttpRequest) -> Self { + PathDeserializer{req} + } +} + impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> { type Error = de::value::Error; @@ -549,110 +388,3 @@ impl<'de> de::VariantAccess<'de> for UnitVariant { Err(de::value::Error::custom("not supported")) } } - -#[cfg(test)] -mod tests { - use futures::{Async, Future}; - use super::*; - use router::{Router, Resource}; - use resource::ResourceHandler; - use test::TestRequest; - use server::ServerSettings; - - #[derive(Deserialize)] - struct MyStruct { - key: String, - value: String, - } - - #[derive(Deserialize)] - struct Id { - id: String, - } - - #[derive(Deserialize)] - struct Test2 { - key: String, - value: u32, - } - - #[test] - fn test_request_extract() { - let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); - - let mut resource = ResourceHandler::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); - let (router, _) = Router::new("", ServerSettings::default(), routes); - assert!(router.recognize(&mut req).is_some()); - - match Path::::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); - }, - _ => unreachable!(), - } - - match Path::<(String, String)>::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); - }, - _ => unreachable!(), - } - - match Query::::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.id, "test"); - }, - _ => unreachable!(), - } - - let mut req = TestRequest::with_uri("/name/32/").finish(); - assert!(router.recognize(&mut req).is_some()); - - match Path::::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.as_ref().key, "name"); - assert_eq!(s.value, 32); - }, - _ => unreachable!(), - } - - match Path::<(String, u8)>::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.0, "name"); - assert_eq!(s.1, 32); - }, - _ => unreachable!(), - } - - match Path::>::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]); - }, - _ => unreachable!(), - } - } - - #[test] - fn test_extract_path_signle() { - let mut resource = ResourceHandler::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((Resource::new("index", "/{value}/"), Some(resource))); - let (router, _) = Router::new("", ServerSettings::default(), routes); - - let mut req = TestRequest::with_uri("/32/").finish(); - assert!(router.recognize(&mut req).is_some()); - - match Path::::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.into_inner(), 32); - }, - _ => unreachable!(), - } - } -} diff --git a/src/extractor.rs b/src/extractor.rs new file mode 100644 index 00000000..dd4511ed --- /dev/null +++ b/src/extractor.rs @@ -0,0 +1,387 @@ +use std::ops::{Deref, DerefMut}; + +use serde_urlencoded; +use serde::de::{self, DeserializeOwned}; +use futures::future::{Future, FutureResult, result}; + +use body::Binary; +use error::Error; +use handler::FromRequest; +use httprequest::HttpRequest; +use httpmessage::{MessageBody, UrlEncoded}; +use de::PathDeserializer; + +/// Extract typed information from the request's path. +/// +/// ## Example +/// +/// ```rust +/// # extern crate bytes; +/// # extern crate actix_web; +/// # extern crate futures; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Path, Result, http}; +/// +/// /// extract path info from "/{username}/{count}/?index.html" url +/// /// {username} - deserializes to a String +/// /// {count} - - deserializes to a u32 +/// fn index(info: Path<(String, u32)>) -> Result { +/// Ok(format!("Welcome {}! {}", info.0, info.1)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/{username}/{count}/?index.html", // <- define path parameters +/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor +/// } +/// ``` +/// +/// It is possible to extract path information to a specific type that implements +/// `Deserialize` trait from *serde*. +/// +/// ```rust +/// # extern crate bytes; +/// # extern crate actix_web; +/// # extern crate futures; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Path, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// extract path info using serde +/// fn index(info: Path) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/{username}/index.html", // <- define path parameters +/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor +/// } +/// ``` +pub struct Path{ + inner: T +} + +impl AsRef for Path { + + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl Deref for Path { + type Target = T; + + fn deref(&self) -> &T { + &self.inner + } +} + +impl DerefMut for Path { + fn deref_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl Path { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.inner + } +} + +impl FromRequest for Path + where T: DeserializeOwned, S: 'static +{ + type Result = FutureResult; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + let req = req.clone(); + result(de::Deserialize::deserialize(PathDeserializer::new(&req)) + .map_err(|e| e.into()) + .map(|inner| Path{inner})) + } +} + +/// Extract typed information from from the request's query. +/// +/// ## Example +/// +/// ```rust +/// # extern crate bytes; +/// # extern crate actix_web; +/// # extern crate futures; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Query, http}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// // use `with` extractor for query info +/// // this handler get called only if request's query contains `username` field +/// fn index(info: Query) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", +/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor +/// } +/// ``` +pub struct Query(T); + +impl Deref for Query { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Query { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl Query { + /// Deconstruct to a inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl FromRequest for Query + where T: de::DeserializeOwned, S: 'static +{ + type Result = FutureResult; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + let req = req.clone(); + result(serde_urlencoded::from_str::(req.query_string()) + .map_err(|e| e.into()) + .map(Query)) + } +} + +/// Request payload extractor. +/// +/// Loads request's payload and construct Binary instance. +impl FromRequest for Binary +{ + type Result = Box>; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + Box::new( + MessageBody::new(req.clone()).from_err().map(|b| b.into())) + } +} + +/// Extract typed information from the request's body. +/// +/// To extract typed information from request's body, the type `T` must implement the +/// `Deserialize` trait from *serde*. +/// +/// ## Example +/// +/// It is possible to extract path information to a specific type that implements +/// `Deserialize` trait from *serde*. +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Form, Result}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// /// extract form data using serde +/// /// this handle get called only if content type is *x-www-form-urlencoded* +/// /// and content of the request could be deserialized to a `FormData` struct +/// fn index(form: Form) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// # fn main() {} +/// ``` +pub struct Form(pub T); + +impl Deref for Form { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for Form { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl FromRequest for Form + where T: DeserializeOwned + 'static, S: 'static +{ + type Result = Box>; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + Box::new(UrlEncoded::new(req.clone()).from_err().map(Form)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bytes::Bytes; + use futures::{Async, Future}; + use http::header; + use router::{Router, Resource}; + use resource::ResourceHandler; + use test::TestRequest; + use server::ServerSettings; + + #[derive(Deserialize, Debug, PartialEq)] + struct Info { + hello: String, + } + + #[test] + fn test_binary() { + let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + + match Binary::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s, Binary::from(Bytes::from_static(b"hello=world"))); + }, + _ => unreachable!(), + } + } + + #[test] + fn test_form() { + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, "application/x-www-form-urlencoded") + .header(header::CONTENT_LENGTH, "11") + .finish(); + req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); + + match Form::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.hello, "world"); + }, + _ => unreachable!(), + } + } + + + #[derive(Deserialize)] + struct MyStruct { + key: String, + value: String, + } + + #[derive(Deserialize)] + struct Id { + id: String, + } + + #[derive(Deserialize)] + struct Test2 { + key: String, + value: u32, + } + + #[test] + fn test_request_extract() { + let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); + + let mut resource = ResourceHandler::<()>::default(); + resource.name("index"); + let mut routes = Vec::new(); + routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource))); + let (router, _) = Router::new("", ServerSettings::default(), routes); + assert!(router.recognize(&mut req).is_some()); + + match Path::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + }, + _ => unreachable!(), + } + + match Path::<(String, String)>::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); + }, + _ => unreachable!(), + } + + match Query::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.id, "test"); + }, + _ => unreachable!(), + } + + let mut req = TestRequest::with_uri("/name/32/").finish(); + assert!(router.recognize(&mut req).is_some()); + + match Path::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.as_ref().key, "name"); + assert_eq!(s.value, 32); + }, + _ => unreachable!(), + } + + match Path::<(String, u8)>::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); + }, + _ => unreachable!(), + } + + match Path::>::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]); + }, + _ => unreachable!(), + } + } + + #[test] + fn test_extract_path_signle() { + let mut resource = ResourceHandler::<()>::default(); + resource.name("index"); + let mut routes = Vec::new(); + routes.push((Resource::new("index", "/{value}/"), Some(resource))); + let (router, _) = Router::new("", ServerSettings::default(), routes); + + let mut req = TestRequest::with_uri("/32/").finish(); + assert!(router.recognize(&mut req).is_some()); + + match Path::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.into_inner(), 32); + }, + _ => unreachable!(), + } + } +} diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 32ccb39f..11d1d087 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,5 +1,4 @@ use std::str; -use std::ops::{Deref, DerefMut}; use bytes::{Bytes, BytesMut}; use futures::{Future, Stream, Poll}; use http_range::HttpRange; @@ -14,10 +13,8 @@ use http::{header, HeaderMap}; use json::JsonBody; use header::Header; -use handler::FromRequest; use multipart::Multipart; -use httprequest::HttpRequest; -use error::{Error, ParseError, ContentTypeError, +use error::{ParseError, ContentTypeError, HttpRangeError, PayloadError, UrlencodedError}; @@ -408,61 +405,6 @@ impl Future for UrlEncoded } } -/// Extract typed information from the request's body. -/// -/// To extract typed information from request's body, the type `T` must implement the -/// `Deserialize` trait from *serde*. -/// -/// ## Example -/// -/// It is possible to extract path information to a specific type that implements -/// `Deserialize` trait from *serde*. -/// -/// ```rust -/// # extern crate actix_web; -/// #[macro_use] extern crate serde_derive; -/// use actix_web::{App, Form, Result}; -/// -/// #[derive(Deserialize)] -/// struct FormData { -/// username: String, -/// } -/// -/// /// extract form data using serde -/// /// this handle get called only if content type is *x-www-form-urlencoded* -/// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: Form) -> Result { -/// Ok(format!("Welcome {}!", form.username)) -/// } -/// # fn main() {} -/// ``` -pub struct Form(pub T); - -impl Deref for Form { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for Form { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl FromRequest for Form - where T: DeserializeOwned + 'static, S: 'static -{ - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - Box::new(UrlEncoded::new(req.clone()).from_err().map(Form)) - } -} - #[cfg(test)] mod tests { use super::*; @@ -645,22 +587,6 @@ mod tests { assert_eq!(result, Async::Ready(Info{hello: "world".to_owned()})); } - #[test] - fn test_urlencoded_extractor() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(header::CONTENT_LENGTH, "11") - .finish(); - req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - - match Form::::from_request(&req).poll().unwrap() { - Async::Ready(s) => { - assert_eq!(s.hello, "world"); - }, - _ => unreachable!(), - } - } - #[test] fn test_message_body() { let req = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").finish(); diff --git a/src/lib.rs b/src/lib.rs index 70a61d74..11f1c00a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,7 @@ mod application; mod body; mod context; mod de; +mod extractor; mod handler; mod header; mod helpers; @@ -134,12 +135,12 @@ pub mod middleware; pub mod pred; pub mod test; pub mod server; +pub use extractor::{Path, Form, Query}; pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; pub use json::Json; -pub use de::{Path, Query}; pub use application::App; -pub use httpmessage::{HttpMessage, Form}; +pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; pub use handler::{Either, Responder, AsyncResponder, FutureResponse, State};