From 1079c5c56202449a449b23ef39880b3816b2a385 Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Mon, 23 Jul 2018 15:19:04 +0200 Subject: [PATCH] Add FromRequest implementation for Result and Option where T:FromRequest --- src/extractor.rs | 218 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/src/extractor.rs b/src/extractor.rs index 7696e992..458b7f1a 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -460,6 +460,126 @@ impl FromRequest for String { } } +/// Optionally extract a field from the request +/// +/// If the FromRequest for T fails, return None rather than returning an error response +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// extern crate rand; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; +/// use actix_web::error::ErrorBadRequest; +/// +/// #[derive(Debug, Deserialize)] +/// struct Thing { name: String } +/// +/// impl FromRequest for Thing { +/// type Config = (); +/// type Result = Result; +/// +/// #[inline] +/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// if rand::random() { +/// Ok(Thing { name: "thingy".into() }) +/// } else { +/// Err(ErrorBadRequest("no luck")) +/// } +/// +/// } +/// } +/// +/// /// extract text data from request +/// fn index(supplied_thing: Option) -> Result { +/// match supplied_thing { +/// // Puns not intended +/// Some(thing) => Ok(format!("Got something: {:?}", thing)), +/// None => Ok(format!("No thing!")) +/// } +/// } +/// +/// fn main() { +/// let app = App::new().resource("/users/:first", |r| { +/// r.method(http::Method::POST).with(index) +/// }); +/// } +/// ``` +impl FromRequest for Option where T: FromRequest { + type Config = T::Config; + type Result = Box, Error = Error>>; + + #[inline] + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + Box::new(T::from_request(req, cfg).into().then( |r| { + match r { + Ok(v) => future::ok(Some(v)), + Err(e) => { +// if true { panic!("{:?}", e.as_response_error()); } + + future::ok(None) + } + } + })) + } +} + +/// Optionally extract a field from the request or extract the Error if unsuccessful +/// +/// If the FromRequest for T fails, inject Err into handler rather than returning an error response +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// extern crate rand; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest}; +/// use actix_web::error::ErrorBadRequest; +/// +/// #[derive(Debug, Deserialize)] +/// struct Thing { name: String } +/// +/// impl FromRequest for Thing { +/// type Config = (); +/// type Result = Result; +/// +/// #[inline] +/// fn from_request(req: &HttpRequest, _cfg: &Self::Config) -> Self::Result { +/// if rand::random() { +/// Ok(Thing { name: "thingy".into() }) +/// } else { +/// Err(ErrorBadRequest("no luck")) +/// } +/// +/// } +/// } +/// +/// /// extract text data from request +/// fn index(supplied_thing: Result) -> Result { +/// match supplied_thing { +/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)), +/// Err(e) => Ok(format!("Error extracting thing: {}", e)) +/// } +/// } +/// +/// fn main() { +/// let app = App::new().resource("/users/:first", |r| { +/// r.method(http::Method::POST).with(index) +/// }); +/// } +/// ``` +impl FromRequest for Result where T: FromRequest{ + type Config = T::Config; + type Result = Box, Error = Error>>; + + #[inline] + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + Box::new(T::from_request(req, cfg).into().then( |r| { future::ok(r) })) + } +} + /// Payload configuration for request's payload. pub struct PayloadConfig { limit: usize, @@ -685,6 +805,75 @@ mod tests { } } + #[test] + fn test_option() { + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).finish(); + + let mut cfg = FormConfig::default(); + cfg.limit(4096); + + match Option::>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, None), + _ => unreachable!(), + } + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); + + match Option::>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, Some(Form(Info { hello: "world".into() }))), + _ => unreachable!(), + } + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"bye=world")) + .finish(); + + match Option::>::from_request(&req, &cfg).poll().unwrap() { + Async::Ready(r) => assert_eq!(r, None), + _ => unreachable!(), + } + } + + #[test] + fn test_result() { + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .finish(); + + match Result::>::from_request(&req, &FormConfig::default()).poll().unwrap() { + Async::Ready(Ok(r)) => assert_eq!(r, Form(Info { hello: "world".into() })), + _ => unreachable!(), + } + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ).header(header::CONTENT_LENGTH, "9") + .set_payload(Bytes::from_static(b"bye=world")) + .finish(); + + match Result::>::from_request(&req, &FormConfig::default()).poll().unwrap() { + Async::Ready(r) => assert!(r.is_err()), + _ => unreachable!(), + } + } + + + #[test] fn test_payload_config() { let req = TestRequest::default().finish(); @@ -797,4 +986,33 @@ mod tests { assert_eq!((res.1).0, "name"); assert_eq!((res.1).1, "user1"); } + +// #[test] +// fn test_tuple_optional() { +// let mut router = Router::<()>::new(); +// router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); +// +// let req = TestRequest::with_uri("/name/?id=test").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); +// +// let res = match <(Path<(Option, Option)>,)>::extract(&req).wait() { +// Ok(res) => res, +// e => panic!("error {:?}", e), +// }; +// assert_eq!((res.0).0, Some("name".into())); +// assert_eq!((res.0).1, None); +// +// let req = TestRequest::with_uri("/user/?id=test").finish(); +// let info = router.recognize(&req, &(), 0); +// let req = req.with_route_info(info); +// +// let res = match <(Path<(Option, Option)>,)>::extract(&req).wait() { +// Ok(res) => res, +// _ => panic!("error"), +// }; +// assert_eq!((res.0).0, None); +// assert_eq!((res.0).1, Some("user".into())); +// } + }