1use std::sync::Arc;
4
5use actix_router::PathDeserializer;
6use actix_utils::future::{ready, Ready};
7use derive_more::{AsRef, Deref, DerefMut, Display, From};
8use serde::de;
9
10use crate::{
11 dev::Payload,
12 error::{Error, ErrorNotFound, PathError},
13 web::Data,
14 FromRequest, HttpRequest,
15};
16
17#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, AsRef, Display, From)]
77pub struct Path<T>(T);
78
79impl<T> Path<T> {
80 pub fn into_inner(self) -> T {
82 self.0
83 }
84}
85
86impl<T> FromRequest for Path<T>
88where
89 T: de::DeserializeOwned,
90{
91 type Error = Error;
92 type Future = Ready<Result<Self, Self::Error>>;
93
94 #[inline]
95 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
96 let error_handler = req
97 .app_data::<PathConfig>()
98 .or_else(|| req.app_data::<Data<PathConfig>>().map(Data::get_ref))
99 .and_then(|c| c.err_handler.clone());
100
101 ready(
102 de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
103 .map(Path)
104 .map_err(move |err| {
105 log::debug!(
106 "Failed during Path extractor deserialization. \
107 Request path: {:?}",
108 req.path()
109 );
110
111 if let Some(error_handler) = error_handler {
112 let err = PathError::Deserialize(err);
113 (error_handler)(err, req)
114 } else {
115 ErrorNotFound(err)
116 }
117 }),
118 )
119 }
120}
121
122#[derive(Clone, Default)]
156pub struct PathConfig {
157 #[allow(clippy::type_complexity)]
158 err_handler: Option<Arc<dyn Fn(PathError, &HttpRequest) -> Error + Send + Sync>>,
159}
160
161impl PathConfig {
162 pub fn error_handler<F>(mut self, f: F) -> Self
164 where
165 F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static,
166 {
167 self.err_handler = Some(Arc::new(f));
168 self
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use actix_router::ResourceDef;
175 use derive_more::Display;
176 use serde::Deserialize;
177
178 use super::*;
179 use crate::{error, http, test::TestRequest, HttpResponse};
180
181 #[derive(Deserialize, Debug, Display)]
182 #[display("MyStruct({}, {})", key, value)]
183 struct MyStruct {
184 key: String,
185 value: String,
186 }
187
188 #[derive(Deserialize)]
189 struct Test2 {
190 key: String,
191 value: u32,
192 }
193
194 #[actix_rt::test]
195 async fn test_extract_path_single() {
196 let resource = ResourceDef::new("/{value}/");
197
198 let mut req = TestRequest::with_uri("/32/").to_srv_request();
199 resource.capture_match_info(req.match_info_mut());
200
201 let (req, mut pl) = req.into_parts();
202 assert_eq!(*Path::<i8>::from_request(&req, &mut pl).await.unwrap(), 32);
203 assert!(Path::<MyStruct>::from_request(&req, &mut pl).await.is_err());
204 }
205
206 #[allow(clippy::let_unit_value)]
207 #[actix_rt::test]
208 async fn test_tuple_extract() {
209 let resource = ResourceDef::new("/{key}/{value}/");
210
211 let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
212 resource.capture_match_info(req.match_info_mut());
213
214 let (req, mut pl) = req.into_parts();
215 let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
216 .await
217 .unwrap();
218 assert_eq!(res.0, "name");
219 assert_eq!(res.1, "user1");
220
221 let (Path(a), Path(b)) =
222 <(Path<(String, String)>, Path<(String, String)>)>::from_request(&req, &mut pl)
223 .await
224 .unwrap();
225 assert_eq!(a.0, "name");
226 assert_eq!(a.1, "user1");
227 assert_eq!(b.0, "name");
228 assert_eq!(b.1, "user1");
229
230 let () = <()>::from_request(&req, &mut pl).await.unwrap();
231 }
232
233 #[actix_rt::test]
234 async fn test_request_extract() {
235 let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
236
237 let resource = ResourceDef::new("/{key}/{value}/");
238 resource.capture_match_info(req.match_info_mut());
239
240 let (req, mut pl) = req.into_parts();
241 let mut s = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
242 assert_eq!(s.key, "name");
243 assert_eq!(s.value, "user1");
244 s.value = "user2".to_string();
245 assert_eq!(s.value, "user2");
246 assert_eq!(
247 format!("{}, {:?}", s, s),
248 "MyStruct(name, user2), Path(MyStruct { key: \"name\", value: \"user2\" })"
249 );
250 let s = s.into_inner();
251 assert_eq!(s.value, "user2");
252
253 let Path(s) = Path::<(String, String)>::from_request(&req, &mut pl)
254 .await
255 .unwrap();
256 assert_eq!(s.0, "name");
257 assert_eq!(s.1, "user1");
258
259 let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
260 let resource = ResourceDef::new("/{key}/{value}/");
261 resource.capture_match_info(req.match_info_mut());
262
263 let (req, mut pl) = req.into_parts();
264 let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
265 assert_eq!(s.as_ref().key, "name");
266 assert_eq!(s.value, 32);
267
268 let Path(s) = Path::<(String, u8)>::from_request(&req, &mut pl)
269 .await
270 .unwrap();
271 assert_eq!(s.0, "name");
272 assert_eq!(s.1, 32);
273
274 let res = Path::<Vec<String>>::from_request(&req, &mut pl)
275 .await
276 .unwrap();
277 assert_eq!(res[0], "name".to_owned());
278 assert_eq!(res[1], "32".to_owned());
279 }
280
281 #[actix_rt::test]
282 async fn paths_decoded() {
283 let resource = ResourceDef::new("/{key}/{value}");
284 let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%254%32").to_srv_request();
285 resource.capture_match_info(req.match_info_mut());
286
287 let (req, mut pl) = req.into_parts();
288 let path_items = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
289 assert_eq!(path_items.key, "na+me");
290 assert_eq!(path_items.value, "us/er%42");
291 assert_eq!(req.match_info().as_str(), "/na%2Bme/us%2Fer%2542");
292 }
293
294 #[actix_rt::test]
295 async fn test_custom_err_handler() {
296 let (req, mut pl) = TestRequest::with_uri("/name/user1/")
297 .app_data(PathConfig::default().error_handler(|err, _| {
298 error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
299 }))
300 .to_http_parts();
301
302 let s = Path::<(usize,)>::from_request(&req, &mut pl)
303 .await
304 .unwrap_err();
305 let res = HttpResponse::from_error(s);
306
307 assert_eq!(res.status(), http::StatusCode::CONFLICT);
308 }
309}