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}