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