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