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)]
57pub struct Path<T>(T);
58
59impl<T> Path<T> {
60 pub fn into_inner(self) -> T {
62 self.0
63 }
64}
65
66impl<T> FromRequest for Path<T>
68where
69 T: de::DeserializeOwned,
70{
71 type Error = Error;
72 type Future = Ready<Result<Self, Self::Error>>;
73
74 #[inline]
75 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
76 let error_handler = req
77 .app_data::<PathConfig>()
78 .or_else(|| req.app_data::<Data<PathConfig>>().map(Data::get_ref))
79 .and_then(|c| c.err_handler.clone());
80
81 ready(
82 de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
83 .map(Path)
84 .map_err(move |err| {
85 log::debug!(
86 "Failed during Path extractor deserialization. \
87 Request path: {:?}",
88 req.path()
89 );
90
91 if let Some(error_handler) = error_handler {
92 let err = PathError::Deserialize(err);
93 (error_handler)(err, req)
94 } else {
95 ErrorNotFound(err)
96 }
97 }),
98 )
99 }
100}
101
102#[derive(Clone, Default)]
136pub struct PathConfig {
137 #[allow(clippy::type_complexity)]
138 err_handler: Option<Arc<dyn Fn(PathError, &HttpRequest) -> Error + Send + Sync>>,
139}
140
141impl PathConfig {
142 pub fn error_handler<F>(mut self, f: F) -> Self
144 where
145 F: Fn(PathError, &HttpRequest) -> Error + Send + Sync + 'static,
146 {
147 self.err_handler = Some(Arc::new(f));
148 self
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use actix_router::ResourceDef;
155 use derive_more::Display;
156 use serde::Deserialize;
157
158 use super::*;
159 use crate::{error, http, test::TestRequest, HttpResponse};
160
161 #[derive(Deserialize, Debug, Display)]
162 #[display("MyStruct({}, {})", key, value)]
163 struct MyStruct {
164 key: String,
165 value: String,
166 }
167
168 #[derive(Deserialize)]
169 struct Test2 {
170 key: String,
171 value: u32,
172 }
173
174 #[actix_rt::test]
175 async fn test_extract_path_single() {
176 let resource = ResourceDef::new("/{value}/");
177
178 let mut req = TestRequest::with_uri("/32/").to_srv_request();
179 resource.capture_match_info(req.match_info_mut());
180
181 let (req, mut pl) = req.into_parts();
182 assert_eq!(*Path::<i8>::from_request(&req, &mut pl).await.unwrap(), 32);
183 assert!(Path::<MyStruct>::from_request(&req, &mut pl).await.is_err());
184 }
185
186 #[allow(clippy::let_unit_value)]
187 #[actix_rt::test]
188 async fn test_tuple_extract() {
189 let resource = ResourceDef::new("/{key}/{value}/");
190
191 let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
192 resource.capture_match_info(req.match_info_mut());
193
194 let (req, mut pl) = req.into_parts();
195 let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
196 .await
197 .unwrap();
198 assert_eq!(res.0, "name");
199 assert_eq!(res.1, "user1");
200
201 let (Path(a), Path(b)) =
202 <(Path<(String, String)>, Path<(String, String)>)>::from_request(&req, &mut pl)
203 .await
204 .unwrap();
205 assert_eq!(a.0, "name");
206 assert_eq!(a.1, "user1");
207 assert_eq!(b.0, "name");
208 assert_eq!(b.1, "user1");
209
210 let () = <()>::from_request(&req, &mut pl).await.unwrap();
211 }
212
213 #[actix_rt::test]
214 async fn test_request_extract() {
215 let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
216
217 let resource = ResourceDef::new("/{key}/{value}/");
218 resource.capture_match_info(req.match_info_mut());
219
220 let (req, mut pl) = req.into_parts();
221 let mut s = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
222 assert_eq!(s.key, "name");
223 assert_eq!(s.value, "user1");
224 s.value = "user2".to_string();
225 assert_eq!(s.value, "user2");
226 assert_eq!(
227 format!("{}, {:?}", s, s),
228 "MyStruct(name, user2), Path(MyStruct { key: \"name\", value: \"user2\" })"
229 );
230 let s = s.into_inner();
231 assert_eq!(s.value, "user2");
232
233 let Path(s) = Path::<(String, String)>::from_request(&req, &mut pl)
234 .await
235 .unwrap();
236 assert_eq!(s.0, "name");
237 assert_eq!(s.1, "user1");
238
239 let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
240 let resource = ResourceDef::new("/{key}/{value}/");
241 resource.capture_match_info(req.match_info_mut());
242
243 let (req, mut pl) = req.into_parts();
244 let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
245 assert_eq!(s.as_ref().key, "name");
246 assert_eq!(s.value, 32);
247
248 let Path(s) = Path::<(String, u8)>::from_request(&req, &mut pl)
249 .await
250 .unwrap();
251 assert_eq!(s.0, "name");
252 assert_eq!(s.1, 32);
253
254 let res = Path::<Vec<String>>::from_request(&req, &mut pl)
255 .await
256 .unwrap();
257 assert_eq!(res[0], "name".to_owned());
258 assert_eq!(res[1], "32".to_owned());
259 }
260
261 #[actix_rt::test]
262 async fn paths_decoded() {
263 let resource = ResourceDef::new("/{key}/{value}");
264 let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%254%32").to_srv_request();
265 resource.capture_match_info(req.match_info_mut());
266
267 let (req, mut pl) = req.into_parts();
268 let path_items = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
269 assert_eq!(path_items.key, "na+me");
270 assert_eq!(path_items.value, "us/er%42");
271 assert_eq!(req.match_info().as_str(), "/na%2Bme/us%2Fer%2542");
272 }
273
274 #[actix_rt::test]
275 async fn test_custom_err_handler() {
276 let (req, mut pl) = TestRequest::with_uri("/name/user1/")
277 .app_data(PathConfig::default().error_handler(|err, _| {
278 error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into()
279 }))
280 .to_http_parts();
281
282 let s = Path::<(usize,)>::from_request(&req, &mut pl)
283 .await
284 .unwrap_err();
285 let res = HttpResponse::from_error(s);
286
287 assert_eq!(res.status(), http::StatusCode::CONFLICT);
288 }
289}