diff --git a/CHANGES.md b/CHANGES.md index 8ac1724a..f5adb82c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,10 @@ * HttpServer now treats streaming bodies the same for HTTP/1.x protocols. #549 +### Added + +* Add method to configure custom error handler to `Query` and `Path` extractors. + ## [0.7.13] - 2018-10-14 ### Fixed diff --git a/src/extractor.rs b/src/extractor.rs index 7b0b4b00..45e29ace 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -111,18 +111,64 @@ impl FromRequest for Path where T: DeserializeOwned, { - type Config = (); + type Config = PathConfig; type Result = Result; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { let req = req.clone(); + let req2 = req.clone(); + let err = Rc::clone(&cfg.ehandler); de::Deserialize::deserialize(PathDeserializer::new(&req)) - .map_err(ErrorNotFound) + .map_err(move |e| (*err)(e, &req2)) .map(|inner| Path { inner }) } } +/// Path extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{error, http, App, HttpResponse, Path, Result}; +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: Path<(u32, String)>) -> Result { +/// Ok(format!("Welcome {}!", info.1)) +/// } +/// +/// fn main() { +/// let app = App::new().resource("/index.html/{id}/{name}", |r| { +/// r.method(http::Method::GET).with_config(index, |cfg| { +/// cfg.0.error_handler(|err, req| { +/// // <- create custom error response +/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// }); +/// }) +/// }); +/// } +/// ``` +pub struct PathConfig { + ehandler: Rc) -> Error>, +} +impl PathConfig { + /// Set custom error handler + pub fn error_handler(&mut self, f: F) -> &mut Self + where + F: Fn(serde_urlencoded::de::Error, &HttpRequest) -> Error + 'static, + { + self.ehandler = Rc::new(f); + self + } +} + +impl Default for PathConfig { + fn default() -> Self { + PathConfig { + ehandler: Rc::new(|e, _| ErrorNotFound(e)), + } + } +} + impl fmt::Debug for Path { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) @@ -200,17 +246,69 @@ impl FromRequest for Query where T: de::DeserializeOwned, { - type Config = (); + type Config = QueryConfig; type Result = Result; #[inline] - fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + let req2 = req.clone(); + let err = Rc::clone(&cfg.ehandler); serde_urlencoded::from_str::(req.query_string()) - .map_err(|e| e.into()) + .map_err(move |e| (*err)(e, &req2)) .map(Query) } } +/// Query extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{error, http, App, HttpResponse, Query, Result}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: Query) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource("/index.html", |r| { +/// r.method(http::Method::GET).with_config(index, |cfg| { +/// cfg.0.error_handler(|err, req| { +/// // <- create custom error response +/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// }); +/// }) +/// }); +/// } +/// ``` +pub struct QueryConfig { + ehandler: Rc) -> Error>, +} +impl QueryConfig { + /// Set custom error handler + pub fn error_handler(&mut self, f: F) -> &mut Self + where + F: Fn(serde_urlencoded::de::Error, &HttpRequest) -> Error + 'static, + { + self.ehandler = Rc::new(f); + self + } +} + +impl Default for QueryConfig { + fn default() -> Self { + QueryConfig { + ehandler: Rc::new(|e, _| e.into()), + } + } +} + impl fmt::Debug for Query { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) @@ -951,15 +1049,15 @@ mod tests { let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); - let s = Path::::from_request(&req, &()).unwrap(); + let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); - let s = Path::<(String, String)>::from_request(&req, &()).unwrap(); + let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); - let s = Query::::from_request(&req, &()).unwrap(); + let s = Query::::from_request(&req, &QueryConfig::default()).unwrap(); assert_eq!(s.id, "test"); let mut router = Router::<()>::default(); @@ -968,11 +1066,11 @@ mod tests { let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); - let s = Path::::from_request(&req, &()).unwrap(); + let s = Path::::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32); - let s = Path::<(String, u8)>::from_request(&req, &()).unwrap(); + let s = Path::<(String, u8)>::from_request(&req, &PathConfig::default()).unwrap(); assert_eq!(s.0, "name"); assert_eq!(s.1, 32); @@ -989,7 +1087,7 @@ mod tests { let req = TestRequest::with_uri("/32/").finish(); let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); - assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); + assert_eq!(*Path::::from_request(&req, &&PathConfig::default()).unwrap(), 32); } #[test]