mirror of
https://github.com/actix/actix-web.git
synced 2024-11-26 03:21:08 +00:00
add a way to configure error treatment for Query and Path extractors (#550)
* add a way to configure error treatment for Query extractor * allow error handler to be customized for Path extractor
This commit is contained in:
parent
960274ada8
commit
42d5d48e71
2 changed files with 114 additions and 12 deletions
|
@ -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
|
||||
|
|
122
src/extractor.rs
122
src/extractor.rs
|
@ -111,18 +111,64 @@ impl<T, S> FromRequest<S> for Path<T>
|
|||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
type Config = ();
|
||||
type Config = PathConfig<S>;
|
||||
type Result = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
fn from_request(req: &HttpRequest<S>, 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<String> {
|
||||
/// 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<S> {
|
||||
ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
|
||||
}
|
||||
impl<S> PathConfig<S> {
|
||||
/// Set custom error handler
|
||||
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error + 'static,
|
||||
{
|
||||
self.ehandler = Rc::new(f);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for PathConfig<S> {
|
||||
fn default() -> Self {
|
||||
PathConfig {
|
||||
ehandler: Rc::new(|e, _| ErrorNotFound(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Path<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
|
@ -200,17 +246,69 @@ impl<T, S> FromRequest<S> for Query<T>
|
|||
where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
type Config = ();
|
||||
type Config = QueryConfig<S>;
|
||||
type Result = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
|
||||
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
|
||||
let req2 = req.clone();
|
||||
let err = Rc::clone(&cfg.ehandler);
|
||||
serde_urlencoded::from_str::<T>(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<Info>) -> Result<String> {
|
||||
/// 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<S> {
|
||||
ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
|
||||
}
|
||||
impl<S> QueryConfig<S> {
|
||||
/// Set custom error handler
|
||||
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error + 'static,
|
||||
{
|
||||
self.ehandler = Rc::new(f);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for QueryConfig<S> {
|
||||
fn default() -> Self {
|
||||
QueryConfig {
|
||||
ehandler: Rc::new(|e, _| e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for Query<T> {
|
||||
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::<MyStruct>::from_request(&req, &()).unwrap();
|
||||
let s = Path::<MyStruct>::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::<Id>::from_request(&req, &()).unwrap();
|
||||
let s = Query::<Id>::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::<Test2>::from_request(&req, &()).unwrap();
|
||||
let s = Path::<Test2>::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::<i8>::from_request(&req, &()).unwrap(), 32);
|
||||
assert_eq!(*Path::<i8>::from_request(&req, &&PathConfig::default()).unwrap(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Reference in a new issue