diff --git a/src/error.rs b/src/error.rs index e1cc79845..d8e2b1d00 100644 --- a/src/error.rs +++ b/src/error.rs @@ -92,6 +92,25 @@ impl ResponseError for JsonPayloadError { } } +/// A set of errors that can occur during parsing request paths +#[derive(Debug, Display, From)] +pub enum PathPayloadError { + /// Deserialize error + #[display(fmt = "Path deserialize error: {}", _0)] + Deserialize(de::Error), +} + +/// Return `BadRequest` for `PathPayloadError` +impl ResponseError for PathPayloadError { + fn error_response(&self) -> HttpResponse { + match *self { + PathPayloadError::Deserialize(_) => { + HttpResponse::new(StatusCode::BAD_REQUEST) + } + } + } +} + /// A set of errors that can occur during parsing query strings #[derive(Debug, Display, From)] pub enum QueryPayloadError { diff --git a/src/types/mod.rs b/src/types/mod.rs index d01d597b7..43a189e2c 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -9,6 +9,6 @@ pub(crate) mod readlines; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; -pub use self::path::Path; +pub use self::path::{Path, PathConfig}; pub use self::payload::{Payload, PayloadConfig}; pub use self::query::{Query, QueryConfig}; diff --git a/src/types/path.rs b/src/types/path.rs index 5f0a05af9..4f1d3d54a 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -1,5 +1,6 @@ //! Path extractor +use std::sync::Arc; use std::{fmt, ops}; use actix_http::error::{Error, ErrorNotFound}; @@ -7,6 +8,7 @@ use actix_router::PathDeserializer; use serde::de; use crate::dev::Payload; +use crate::error::PathPayloadError; use crate::request::HttpRequest; use crate::FromRequest; @@ -156,15 +158,89 @@ impl FromRequest for Path where T: de::DeserializeOwned, { - type Config = (); type Error = Error; type Future = Result; + type Config = PathConfig; #[inline] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let error_handler = req + .app_data::() + .map(|c| c.ehandler.clone()) + .unwrap_or(None); + de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) .map(|inner| Path { inner }) - .map_err(ErrorNotFound) + .map_err(move |e| { + log::debug!( + "Failed during Path extractor deserialization. \ + Request path: {:?}", + req.path() + ); + if let Some(error_handler) = error_handler { + let e = PathPayloadError::Deserialize(e); + (error_handler)(e, req) + } else { + ErrorNotFound(e) + } + }) + } +} + +/// Path extractor configuration +/// +/// ```rust +// #[macro_use] +// extern crate serde_derive; +// use actix_web::web::PathConfig; +// use actix_web::{error, web, App, FromRequest, HttpResponse}; + +// #[derive(Deserialize, Debug)] +// enum Folder { +// #[serde(rename = "inbox")] +// Inbox, +// #[serde(rename = "outbox")] +// Outbox, +// } + +// /// deserialize `Info` from request's path +// fn index(folder: web::Path) -> String { +// format!("Selected folder: {}!", folder) +// } + +// fn main() { +// let app = App::new().service( +// web::resource("messages/{folder}") +// .data(PathConfig::default().error_handler(|err, req| { +// error::InternalError::from_response( +// err, +// HttpResponse::Conflict().finish(), +// ) +// .into() +// })) +// .route(web::post().to(index)), +// ); +// } +/// ``` +#[derive(Clone)] +pub struct PathConfig { + ehandler: Option Error + Send + Sync>>, +} + +impl PathConfig { + /// Set custom error handler + pub fn error_handler(mut self, f: F) -> Self + where + F: Fn(PathPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, + { + self.ehandler = Some(Arc::new(f)); + self + } +} + +impl Default for PathConfig { + fn default() -> Self { + PathConfig { ehandler: None } } }