1use lazy_static::lazy_static;
2use regex::Regex;
3use serde::de::DeserializeOwned;
4use std::collections::HashMap;
5
6#[cfg(test)]
7mod tests;
8
9pub trait FromPattern<T>
10where
11 T: DeserializeOwned,
12{
13 fn from_pattern(pattern: &str, url: &str) -> Result<T, Error> {
14 let re = Regex::new(&make_expression(pattern))?;
15 Ok(recap::from_captures::<T>(&re, url)?)
16 }
17}
18
19pub fn matcher<'a>(pattern: &'a str, url: &'a str) -> Result<HashMap<String, &'a str>, Error> {
20 let mut map = HashMap::new();
21
22 let expr = make_expression(pattern);
23 let re = Regex::new(&expr)?;
24
25 let caps = re.captures(url).ok_or(Error::NoCaptures)?;
26
27 for (index, key) in re.capture_names().enumerate() {
28 if let (Some(k), Some(c)) = (key, caps.get(index)) {
29 map.insert(k.to_owned(), c.as_str());
30 }
31 }
32
33 Ok(map)
34}
35
36#[derive(thiserror::Error, Debug)]
37pub enum Error {
38 #[error("Failed to find any captures")]
39 NoCaptures,
40 #[error(transparent)]
41 RegexError(#[from] regex::Error),
42 #[error(transparent)]
43 RecapError(#[from] recap::Error),
44}
45
46pub fn make_expression(pattern: &str) -> String {
47 lazy_static! {
48 static ref CHARS: Regex = Regex::new(r":(?P<key>[a-zA-Z0-9_.+-]+)").unwrap();
49 static ref KEY: Regex = Regex::new(r"(?P<key>[?&.])").unwrap();
50 }
51
52 let escaped = KEY.replace_all(pattern, r"\$key");
53
54 let mut exp = String::from(r"^");
55
56 exp.push_str(&CHARS.replace_all(&escaped, r"(?P<$key>[a-zA-Z0-9_.+-]+)"));
57 exp.push('$');
58
59 exp
60}