1use crate::service::web::response::IntoResponse;
2use crate::{Request, StatusCode};
3use rama_core::{Context, context::Extensions};
4use std::collections::HashMap;
5
6mod de;
7
8#[derive(Debug, Clone, Default)]
9pub struct UriParams {
12 params: Option<HashMap<String, String>>,
13 glob: Option<String>,
14}
15
16impl UriParams {
17 fn insert(&mut self, name: String, value: String) {
18 self.params
19 .get_or_insert_with(HashMap::new)
20 .insert(name, value);
21 }
22
23 pub fn get(&self, name: impl AsRef<str>) -> Option<&str> {
25 self.params
26 .as_ref()
27 .and_then(|params| params.get(name.as_ref()))
28 .map(String::as_str)
29 }
30
31 fn append_glob(&mut self, value: &str) {
32 match self.glob {
33 Some(ref mut glob) => {
34 glob.push('/');
35 glob.push_str(value);
36 }
37 None => self.glob = Some(format!("/{}", value)),
38 }
39 }
40
41 pub fn glob(&self) -> Option<&str> {
44 self.glob.as_deref()
45 }
46
47 pub fn deserialize<T>(&self) -> Result<T, UriParamsDeserializeError>
49 where
50 T: serde::de::DeserializeOwned,
51 {
52 match self.params {
53 Some(ref params) => {
54 let params: Vec<_> = params
55 .iter()
56 .map(|(k, v)| (k.as_str(), v.as_str()))
57 .collect();
58 let deserializer = de::PathDeserializer::new(¶ms);
59 T::deserialize(deserializer)
60 }
61 None => Err(de::PathDeserializationError::new(de::ErrorKind::NoParams)),
62 }
63 .map_err(UriParamsDeserializeError)
64 }
65
66 pub fn extend<I, K, V>(&mut self, iter: I) -> &mut Self
68 where
69 I: IntoIterator<Item = (K, V)>,
70 K: Into<String>,
71 V: Into<String>,
72 {
73 let params = self.params.get_or_insert_with(HashMap::new);
74 for (k, v) in iter {
75 params.insert(k.into(), v.into());
76 }
77 self
78 }
79
80 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
81 self.params
82 .as_ref()
83 .map(|params| params.iter().map(|(k, v)| (k.as_str(), v.as_str())))
84 .into_iter()
85 .flatten()
86 }
87}
88
89impl<'a> FromIterator<(&'a str, &'a str)> for UriParams {
90 fn from_iter<T: IntoIterator<Item = (&'a str, &'a str)>>(iter: T) -> Self {
91 let mut params = UriParams::default();
92 for (k, v) in iter {
93 params.insert(k.to_owned(), v.to_owned());
94 }
95 params
96 }
97}
98
99#[derive(Debug)]
100pub struct UriParamsDeserializeError(de::PathDeserializationError);
104
105impl UriParamsDeserializeError {
106 pub fn body_text(&self) -> String {
108 use de::ErrorKind;
109 match self.0.kind {
110 ErrorKind::Message(_)
111 | ErrorKind::NoParams
112 | ErrorKind::ParseError { .. }
113 | ErrorKind::ParseErrorAtIndex { .. }
114 | ErrorKind::ParseErrorAtKey { .. } => format!("Invalid URL: {}", self.0.kind),
115 ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => {
116 self.0.kind.to_string()
117 }
118 }
119 }
120
121 pub fn status(&self) -> StatusCode {
123 use de::ErrorKind;
124 match self.0.kind {
125 ErrorKind::Message(_)
126 | ErrorKind::NoParams
127 | ErrorKind::ParseError { .. }
128 | ErrorKind::ParseErrorAtIndex { .. }
129 | ErrorKind::ParseErrorAtKey { .. } => StatusCode::BAD_REQUEST,
130 ErrorKind::WrongNumberOfParameters { .. } | ErrorKind::UnsupportedType { .. } => {
131 StatusCode::INTERNAL_SERVER_ERROR
132 }
133 }
134 }
135}
136
137impl std::fmt::Display for UriParamsDeserializeError {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 self.0.fmt(f)
140 }
141}
142
143impl std::error::Error for UriParamsDeserializeError {}
144
145impl IntoResponse for UriParamsDeserializeError {
146 fn into_response(self) -> crate::Response {
147 crate::utils::macros::log_http_rejection!(
148 rejection_type = UriParamsDeserializeError,
149 body_text = self.body_text(),
150 status = self.status(),
151 );
152 (self.status(), self.body_text()).into_response()
153 }
154}
155
156#[derive(Debug, Clone)]
157enum PathFragment {
158 Literal(String),
159 Param(String),
160 Glob,
161}
162
163#[derive(Debug, Clone)]
164enum PathMatcherKind {
165 Literal(String),
166 FragmentList(Vec<PathFragment>),
167}
168
169#[derive(Debug, Clone)]
170pub struct PathMatcher {
172 kind: PathMatcherKind,
173}
174
175impl PathMatcher {
176 pub fn new(path: impl AsRef<str>) -> Self {
178 let path = path.as_ref();
179 let path = path.trim().trim_matches('/');
180
181 if !path.contains([':', '*', '{', '}']) {
182 return Self {
183 kind: PathMatcherKind::Literal(path.to_lowercase()),
184 };
185 }
186
187 let path_parts: Vec<_> = path.split('/').filter(|s| !s.is_empty()).collect();
188 let fragment_length = path_parts.len();
189 if fragment_length == 1 && path_parts[0].is_empty() {
190 return Self {
191 kind: PathMatcherKind::FragmentList(vec![PathFragment::Glob]),
192 };
193 }
194
195 let fragments: Vec<PathFragment> = path_parts
196 .into_iter()
197 .enumerate()
198 .filter_map(|(index, s)| {
199 if s.is_empty() {
200 return None;
201 }
202 if s.starts_with(':') {
203 Some(PathFragment::Param(
204 s.trim_start_matches(':').to_lowercase(),
205 ))
206 } else if s.starts_with('{') && s.ends_with('}') && s.len() > 2 {
207 let param_name = s[1..s.len() - 1].to_lowercase();
208 Some(PathFragment::Param(param_name))
209 } else if s == "*" && index == fragment_length - 1 {
210 Some(PathFragment::Glob)
211 } else {
212 Some(PathFragment::Literal(s.to_lowercase()))
213 }
214 })
215 .collect();
216
217 Self {
218 kind: PathMatcherKind::FragmentList(fragments),
219 }
220 }
221
222 pub(crate) fn matches_path(&self, path: &str) -> Option<UriParams> {
223 let path = path.trim().trim_matches('/');
224 match &self.kind {
225 PathMatcherKind::Literal(literal) => {
226 if literal.eq_ignore_ascii_case(path) {
227 Some(UriParams::default())
228 } else {
229 None
230 }
231 }
232 PathMatcherKind::FragmentList(fragments) => {
233 let fragments_iter = fragments.iter().map(Some).chain(std::iter::repeat(None));
234 let mut params = UriParams::default();
235 for (segment, fragment) in path
236 .split('/')
237 .map(Some)
238 .chain(std::iter::repeat(None))
239 .zip(fragments_iter)
240 {
241 match (segment, fragment) {
242 (Some(segment), Some(fragment)) => match fragment {
243 PathFragment::Literal(literal) => {
244 if !literal.eq_ignore_ascii_case(segment) {
245 return None;
246 }
247 }
248 PathFragment::Param(name) => {
249 if segment.is_empty() {
250 return None;
251 }
252 let segment = percent_encoding::percent_decode(segment.as_bytes())
253 .decode_utf8()
254 .map(|s| s.to_string())
255 .unwrap_or_else(|_| segment.to_owned());
256 params.insert(name.to_owned(), segment);
257 }
258 PathFragment::Glob => {
259 params.append_glob(segment);
260 }
261 },
262 (None, None) => {
263 break;
264 }
265 (Some(segment), None) => {
266 params.glob()?;
267 params.append_glob(segment);
268 }
269 _ => {
270 return None;
271 }
272 }
273 }
274
275 Some(params)
276 }
277 }
278 }
279}
280
281impl<State, Body> rama_core::matcher::Matcher<State, Request<Body>> for PathMatcher {
282 fn matches(
283 &self,
284 ext: Option<&mut Extensions>,
285 _ctx: &Context<State>,
286 req: &Request<Body>,
287 ) -> bool {
288 match self.matches_path(req.uri().path()) {
289 None => false,
290 Some(params) => {
291 if let Some(ext) = ext {
292 ext.insert(params);
293 }
294 true
295 }
296 }
297 }
298}
299
300#[cfg(test)]
301mod test {
302 use super::*;
303
304 #[test]
305 fn test_path_matcher_match_path() {
306 struct TestCase {
307 path: &'static str,
308 matcher_path: &'static str,
309 result: Option<UriParams>,
310 }
311
312 impl TestCase {
313 fn some(path: &'static str, matcher_path: &'static str, result: UriParams) -> Self {
314 Self {
315 path,
316 matcher_path,
317 result: Some(result),
318 }
319 }
320
321 fn none(path: &'static str, matcher_path: &'static str) -> Self {
322 Self {
323 path,
324 matcher_path,
325 result: None,
326 }
327 }
328 }
329
330 let test_cases = vec![
331 TestCase::some("/", "/", UriParams::default()),
332 TestCase::some("", "/", UriParams::default()),
333 TestCase::some("/", "", UriParams::default()),
334 TestCase::some("", "", UriParams::default()),
335 TestCase::some("/foo", "/foo", UriParams::default()),
336 TestCase::some("/foo", "//foo//", UriParams::default()),
337 TestCase::some("/*foo", "/*foo", UriParams::default()),
338 TestCase::some("/foo/*bar/baz", "/foo/*bar/baz", UriParams::default()),
339 TestCase::none("/foo/*bar/baz", "/foo/*bar"),
340 TestCase::none("/", "/:foo"),
341 TestCase::some(
342 "/",
343 "/*",
344 UriParams {
345 glob: Some("/".to_owned()),
346 ..UriParams::default()
347 },
348 ),
349 TestCase::none("/", "//:foo"),
350 TestCase::none("", "/:foo"),
351 TestCase::none("/foo", "/bar"),
352 TestCase::some(
353 "/person/glen%20dc/age",
354 "/person/:name/age",
355 UriParams {
356 params: Some({
357 let mut params = HashMap::new();
358 params.insert("name".to_owned(), "glen dc".to_owned());
359 params
360 }),
361 ..UriParams::default()
362 },
363 ),
364 TestCase::none("/foo", "/bar"),
365 TestCase::some("/foo", "foo", UriParams::default()),
366 TestCase::some("/foo/bar/", "foo/bar", UriParams::default()),
367 TestCase::none("/foo/bar/", "foo/baz"),
368 TestCase::some("/foo/bar/", "/foo/bar", UriParams::default()),
369 TestCase::some("/foo/bar", "/foo/bar", UriParams::default()),
370 TestCase::some("/foo/bar", "foo/bar", UriParams::default()),
371 TestCase::some("/book/oxford-dictionary/author", "/book/:title/author", {
372 let mut params = UriParams::default();
373 params.insert("title".to_owned(), "oxford-dictionary".to_owned());
374 params
375 }),
376 TestCase::some("/book/oxford-dictionary/author", "/book/{title}/author", {
377 let mut params = UriParams::default();
378 params.insert("title".to_owned(), "oxford-dictionary".to_owned());
379 params
380 }),
381 TestCase::some(
382 "/book/oxford-dictionary/author/0",
383 "/book/:title/author/:index",
384 {
385 let mut params = UriParams::default();
386 params.insert("title".to_owned(), "oxford-dictionary".to_owned());
387 params.insert("index".to_owned(), "0".to_owned());
388 params
389 },
390 ),
391 TestCase::some(
392 "/book/oxford-dictionary/author/1",
393 "/book/{title}/author/{index}",
394 {
395 let mut params = UriParams::default();
396 params.insert("title".to_owned(), "oxford-dictionary".to_owned());
397 params.insert("index".to_owned(), "1".to_owned());
398 params
399 },
400 ),
401 TestCase::none("/book/oxford-dictionary", "/book/:title/author"),
402 TestCase::none(
403 "/book/oxford-dictionary/author/birthdate",
404 "/book/:title/author",
405 ),
406 TestCase::none("oxford-dictionary/author", "/book/:title/author"),
407 TestCase::none("/foo", "/"),
408 TestCase::none("/foo", "/*f"),
409 TestCase::some(
410 "/foo",
411 "/*",
412 UriParams {
413 glob: Some("/foo".to_owned()),
414 ..UriParams::default()
415 },
416 ),
417 TestCase::some(
418 "/assets/css/reset.css",
419 "/assets/*",
420 UriParams {
421 glob: Some("/css/reset.css".to_owned()),
422 ..UriParams::default()
423 },
424 ),
425 TestCase::some("/assets/eu/css/reset.css", "/assets/:local/*", {
426 let mut params = UriParams::default();
427 params.insert("local".to_owned(), "eu".to_owned());
428 params.glob = Some("/css/reset.css".to_owned());
429 params
430 }),
431 TestCase::some("/assets/eu/css/reset.css", "/assets/:local/css/*", {
432 let mut params = UriParams::default();
433 params.insert("local".to_owned(), "eu".to_owned());
434 params.glob = Some("/reset.css".to_owned());
435 params
436 }),
437 ];
438 for test_case in test_cases.into_iter() {
439 let matcher = PathMatcher::new(test_case.matcher_path);
440 let result = matcher.matches_path(test_case.path);
441 match (result.as_ref(), test_case.result.as_ref()) {
442 (None, None) => (),
443 (Some(result), Some(expected_result)) => {
444 assert_eq!(
445 result.params,
446 expected_result.params,
447 "unexpected result params: ({}).matcher({}) => {:?} != {:?}",
448 test_case.matcher_path,
449 test_case.path,
450 result.params,
451 expected_result.params,
452 );
453 assert_eq!(
454 result.glob, expected_result.glob,
455 "unexpected result glob: ({}).matcher({}) => {:?} != {:?}",
456 test_case.matcher_path, test_case.path, result.glob, expected_result.glob,
457 );
458 }
459 _ => {
460 panic!(
461 "unexpected result: ({}).matcher({}) => {:?} != {:?}",
462 test_case.matcher_path, test_case.path, result, test_case.result
463 )
464 }
465 }
466 }
467 }
468
469 #[test]
470 fn test_deserialize_uri_params() {
471 let params = UriParams {
472 params: Some({
473 let mut params = HashMap::new();
474 params.insert("name".to_owned(), "glen dc".to_owned());
475 params.insert("age".to_owned(), "42".to_owned());
476 params
477 }),
478 glob: Some("/age".to_owned()),
479 };
480
481 #[derive(serde::Deserialize)]
482 struct Person {
483 name: String,
484 age: u8,
485 }
486
487 let person: Person = params.deserialize().unwrap();
488 assert_eq!(person.name, "glen dc");
489 assert_eq!(person.age, 42);
490 }
491}