actix_web_validator/
path.rs

1//! Path extractor.
2use std::fmt;
3use std::ops::Deref;
4use std::sync::Arc;
5
6use actix_router::PathDeserializer;
7use actix_web::dev::Payload;
8use actix_web::{FromRequest, HttpRequest};
9use futures::future::{ready, Ready};
10use serde::de::{Deserialize, DeserializeOwned};
11use validator::Validate;
12
13use crate::error::{DeserializeErrors, Error};
14
15/// Extract typed information from the request's path.
16///
17/// ## Example
18///
19/// It is possible to extract path information to a specific type that
20/// implements `Deserialize` trait from *serde* and `Validate` trait from *validator*.
21///
22/// ```rust
23/// use actix_web::{web, App, Error};
24/// use serde::Deserialize;
25/// use actix_web_validator::Path;
26/// use validator::Validate;
27///
28/// #[derive(Deserialize, Validate)]
29/// struct Info {
30///     #[validate(length(min = 1))]
31///     username: String,
32/// }
33///
34/// /// extract `Info` from a path using serde
35/// async fn index(info: Path<Info>) -> Result<String, Error> {
36///     Ok(format!("Welcome {}!", info.username))
37/// }
38///
39/// fn main() {
40///     let app = App::new().service(
41///         web::resource("/{username}/index.html") // <- define path parameters
42///              .route(web::get().to(index)) // <- use handler with Path` extractor
43///     );
44/// }
45/// ```
46#[derive(PartialEq, Eq, PartialOrd, Ord)]
47pub struct Path<T> {
48    inner: T,
49}
50
51impl<T> Path<T> {
52    /// Deconstruct to an inner value
53    pub fn into_inner(self) -> T {
54        self.inner
55    }
56}
57
58impl<T> AsRef<T> for Path<T> {
59    fn as_ref(&self) -> &T {
60        &self.inner
61    }
62}
63
64impl<T> Deref for Path<T> {
65    type Target = T;
66
67    fn deref(&self) -> &T {
68        &self.inner
69    }
70}
71
72impl<T: fmt::Debug> fmt::Debug for Path<T> {
73    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74        self.inner.fmt(f)
75    }
76}
77
78impl<T: fmt::Display> fmt::Display for Path<T> {
79    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80        self.inner.fmt(f)
81    }
82}
83
84/// Extract typed information from the request's path.
85///
86/// ## Example
87///
88/// It is possible to extract path information to a specific type that
89/// implements `Deserialize` trait from *serde* and `Validate` trait from *validator*.
90///
91/// ```rust
92/// use actix_web::{web, App, Error};
93/// use serde::Deserialize;
94/// use actix_web_validator::Path;
95/// use validator::Validate;
96///
97/// #[derive(Deserialize, Validate)]
98/// struct Info {
99///     #[validate(length(min = 1))]
100///     username: String,
101/// }
102///
103/// /// extract `Info` from a path using serde
104/// async fn index(info: Path<Info>) -> Result<String, Error> {
105///     Ok(format!("Welcome {}!", info.username))
106/// }
107///
108/// fn main() {
109///     let app = App::new().service(
110///         web::resource("/{username}/index.html") // <- define path parameters
111///              .route(web::get().to(index)) // <- use handler with Path` extractor
112///     );
113/// }
114/// ```
115impl<T> FromRequest for Path<T>
116where
117    T: DeserializeOwned + Validate,
118{
119    type Error = actix_web::Error;
120    type Future = Ready<Result<Self, Self::Error>>;
121
122    #[inline]
123    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
124        let error_handler = req
125            .app_data::<PathConfig>()
126            .map(|c| c.ehandler.clone())
127            .unwrap_or(None);
128        ready(
129            Deserialize::deserialize(PathDeserializer::new(req.match_info()))
130                .map_err(|error| Error::Deserialize(DeserializeErrors::DeserializePath(error)))
131                .and_then(|value: T| {
132                    value
133                        .validate()
134                        .map(move |_| value)
135                        .map_err(Error::Validate)
136                })
137                .map(|inner| Path { inner })
138                .map_err(move |e| {
139                    log::debug!(
140                        "Failed during Path extractor deserialization. \
141                         Request path: {:?}",
142                        req.path()
143                    );
144                    if let Some(error_handler) = error_handler {
145                        (error_handler)(e, req)
146                    } else {
147                        actix_web::error::ErrorNotFound(e)
148                    }
149                }),
150        )
151    }
152}
153
154type ErrHandler = Arc<dyn Fn(Error, &HttpRequest) -> actix_web::Error + Send + Sync>;
155
156/// Path extractor configuration
157///
158/// ```rust
159/// use actix_web_validator::{PathConfig, Path};
160/// use actix_web::{error, web, App, FromRequest, HttpResponse};
161/// use serde::Deserialize;
162/// use validator::Validate;
163///
164/// #[derive(Deserialize, Debug)]
165/// enum Folder {
166///     #[serde(rename = "inbox")]
167///     Inbox,
168///     #[serde(rename = "outbox")]
169///     Outbox,
170/// }
171///
172/// #[derive(Deserialize, Debug, Validate)]
173/// struct Filter {
174///     folder: Folder,
175///     #[validate(range(min = 1024))]
176///     id: u64,
177/// }
178///
179/// // deserialize `Info` from request's path
180/// async fn index(folder: Path<Filter>) -> String {
181///     format!("Selected folder: {:?}!", folder)
182/// }
183///
184/// fn main() {
185///     let app = App::new().service(
186///         web::resource("/messages/{folder}")
187///             .app_data(PathConfig::default().error_handler(|err, req| {
188///                 error::InternalError::from_response(
189///                     err,
190///                     HttpResponse::Conflict().finish(),
191///                 )
192///                 .into()
193///             }))
194///             .route(web::post().to(index)),
195///     );
196/// }
197/// ```
198#[derive(Clone, Default)]
199pub struct PathConfig {
200    ehandler: Option<ErrHandler>,
201}
202
203impl PathConfig {
204    /// Set custom error handler
205    pub fn error_handler<F>(mut self, f: F) -> Self
206    where
207        F: Fn(Error, &HttpRequest) -> actix_web::Error + Send + Sync + 'static,
208    {
209        self.ehandler = Some(Arc::new(f));
210        self
211    }
212}