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