1use actix_router::PathDeserializer;
4use actix_utils::future::{Ready, ready};
5use actix_web::{
6 FromRequest, HttpRequest,
7 dev::Payload,
8 error::{Error, ErrorNotFound},
9};
10use derive_more::Display;
11use serde::de;
12use tracing::debug;
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Display)]
57pub struct Path<T>(pub T);
58
59impl<T> Path<T> {
60 pub fn into_inner(self) -> T {
62 self.0
63 }
64}
65
66impl_more::impl_as_ref!(Path<T> => T);
67impl_more::impl_from!(<T> in T => Path<T>);
68
69impl<T> FromRequest for Path<T>
71where
72 T: de::DeserializeOwned,
73{
74 type Error = Error;
75 type Future = Ready<Result<Self, Self::Error>>;
76
77 #[inline]
78 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
79 ready(
80 de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
81 .map(Path)
82 .map_err(move |err| {
83 debug!(
84 "Failed during Path extractor deserialization. \
85 Request path: {:?}",
86 req.path()
87 );
88
89 ErrorNotFound(err)
90 }),
91 )
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use actix_web::{dev::ResourceDef, test::TestRequest};
98 use derive_more::Display;
99 use serde::Deserialize;
100
101 use super::*;
102
103 #[derive(Deserialize, Debug, Display)]
104 #[display("MyStruct({key}, {value})")]
105 struct MyStruct {
106 key: String,
107 value: String,
108 }
109
110 #[derive(Deserialize)]
111 struct Test2 {
112 key: String,
113 value: u32,
114 }
115
116 #[actix_web::test]
117 async fn test_extract_path_single() {
118 let resource = ResourceDef::new("/{value}/");
119
120 let mut req = TestRequest::with_uri("/32/").to_srv_request();
121 resource.capture_match_info(req.match_info_mut());
122
123 let (req, mut pl) = req.into_parts();
124 assert_eq!(
125 Path::<i8>::from_request(&req, &mut pl)
126 .await
127 .unwrap()
128 .into_inner(),
129 32
130 );
131 assert!(Path::<MyStruct>::from_request(&req, &mut pl).await.is_err());
132 }
133
134 #[actix_web::test]
135 async fn test_tuple_extract() {
136 let resource = ResourceDef::new("/{key}/{value}/");
137
138 let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
139 resource.capture_match_info(req.match_info_mut());
140
141 let (req, mut pl) = req.into_parts();
142 let (Path(res),) = <(Path<(String, String)>,)>::from_request(&req, &mut pl)
143 .await
144 .unwrap();
145 assert_eq!(res.0, "name");
146 assert_eq!(res.1, "user1");
147
148 let (Path(a), Path(b)) =
149 <(Path<(String, String)>, Path<(String, String)>)>::from_request(&req, &mut pl)
150 .await
151 .unwrap();
152 assert_eq!(a.0, "name");
153 assert_eq!(a.1, "user1");
154 assert_eq!(b.0, "name");
155 assert_eq!(b.1, "user1");
156
157 <()>::from_request(&req, &mut pl).await.unwrap();
158 }
159
160 #[actix_web::test]
161 async fn test_request_extract() {
162 let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
163
164 let resource = ResourceDef::new("/{key}/{value}/");
165 resource.capture_match_info(req.match_info_mut());
166
167 let (req, mut pl) = req.into_parts();
168 let s = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
169 assert_eq!(format!("{s}"), "MyStruct(name, user1)");
170 assert_eq!(
171 format!("{s:?}"),
172 "Path(MyStruct { key: \"name\", value: \"user1\" })"
173 );
174 let mut s = s.into_inner();
175 assert_eq!(s.key, "name");
176 assert_eq!(s.value, "user1");
177 s.value = "user2".to_string();
178 assert_eq!(s.value, "user2");
179
180 let Path(s) = Path::<(String, String)>::from_request(&req, &mut pl)
181 .await
182 .unwrap();
183 assert_eq!(s.0, "name");
184 assert_eq!(s.1, "user1");
185
186 let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
187 let resource = ResourceDef::new("/{key}/{value}/");
188 resource.capture_match_info(req.match_info_mut());
189
190 let (req, mut pl) = req.into_parts();
191 let s = Path::<Test2>::from_request(&req, &mut pl).await.unwrap();
192 assert_eq!(s.as_ref().key, "name");
193 let s = s.into_inner();
194 assert_eq!(s.value, 32);
195
196 let Path(s) = Path::<(String, u8)>::from_request(&req, &mut pl)
197 .await
198 .unwrap();
199 assert_eq!(s.0, "name");
200 assert_eq!(s.1, 32);
201
202 let s = Path::<Vec<String>>::from_request(&req, &mut pl)
203 .await
204 .unwrap();
205 let s = s.into_inner();
206 assert_eq!(s[0], "name".to_owned());
207 assert_eq!(s[1], "32".to_owned());
208 }
209
210 #[actix_web::test]
211 async fn paths_decoded() {
212 let resource = ResourceDef::new("/{key}/{value}");
213 let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%254%32").to_srv_request();
214 resource.capture_match_info(req.match_info_mut());
215
216 let (req, mut pl) = req.into_parts();
217 let path_items = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
218 let path_items = path_items.into_inner();
219 assert_eq!(path_items.key, "na+me");
220 assert_eq!(path_items.value, "us/er%42");
221 assert_eq!(req.match_info().as_str(), "/na%2Bme/us%2Fer%2542");
222 }
223}