route_pattern/
lib.rs

1#[cfg(feature = "handparser")]
2mod hand_parser;
3#[cfg(feature = "with-nom")]
4mod nom_parser;
5use regex::Regex;
6use thiserror::Error;
7
8#[derive(Error, Debug, PartialEq, Eq)]
9pub enum ParseError {
10    #[error("Cannot parse expression")]
11    ParseError(String),
12    #[error("Embedded regex cannot compile")]
13    BadRegex(String),
14}
15
16#[cfg(feature = "handparser")]
17pub fn compile_impl(pattern: &str, dstart: char, dend: char) -> Result<Regex, ParseError> {
18    let parsed = hand_parser::parse(pattern, dstart, dend)
19        .map_err(|err| ParseError::ParseError(err.to_string()))?;
20    let mut out = String::new();
21
22    for exp in parsed {
23        match exp {
24            hand_parser::Token::Regex(re) => out.push_str(&re),
25            hand_parser::Token::String(s) => {
26                out.push_str(&regex::escape(&s));
27            }
28        }
29    }
30
31    out = format!("^{}$", out);
32    Regex::new(&out).map_err(|err| ParseError::BadRegex(err.to_string()))
33}
34
35#[cfg(feature = "with-nom")]
36fn compile_impl(pattern: &str, dstart: char, dend: char) -> Result<Regex, ParseError> {
37    let (rest, parsed) = nom_parser::parse(pattern, dstart, dend)
38        .map_err(|err| ParseError::ParseError(err.to_string()))?;
39    if !rest.is_empty() {
40        return Err(ParseError::ParseError(format!(
41            "input not consumed entirely: {}",
42            rest
43        )));
44    }
45    let mut out = String::new();
46
47    for exp in parsed {
48        match exp {
49            nom_parser::Token::Regex(re) => out.push_str(re),
50            nom_parser::Token::String(s) => {
51                out.push_str(&regex::escape(s));
52            }
53        }
54    }
55    out = format!("^{}$", out);
56    Regex::new(&out).map_err(|err| ParseError::BadRegex(err.to_string()))
57}
58
59/// Compile a route pattern. Get back a `Regex` which you can use as you see fit.
60///
61/// # Errors
62///
63/// This function will return an error if parsing or regex compilation fails
64pub fn compile(pattern: &str, dstart: char, dend: char) -> Result<Regex, ParseError> {
65    compile_impl(pattern, dstart, dend)
66}
67/// Match a route pattern.
68///
69/// # Errors
70///
71/// This function will return an error if parsing or regex compilation fails
72pub fn is_match(pattern: &str, dstart: char, dend: char, text: &str) -> Result<bool, ParseError> {
73    let re = compile(pattern, dstart, dend)?;
74
75    Ok(re.is_match(text))
76}
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use pretty_assertions::assert_eq;
81
82    #[test]
83    fn match_glob() {
84        assert_eq!(is_match("foo/<.*>", '<', '>', "foo/bar").unwrap(), true);
85    }
86
87    #[test]
88    fn match_nested() {
89        assert_eq!(
90            is_match("foo/{a{1,4}}/", '{', '}', "foo/aaa/").unwrap(),
91            true
92        );
93    }
94
95    #[test]
96    fn dont_match_nested() {
97        assert_eq!(
98            is_match("foo/{a{1,4}}/", '{', '}', "foo/aaaaaaaa/").unwrap(),
99            false
100        );
101    }
102
103    #[test]
104    fn match_multiple() {
105        assert_eq!(
106            is_match("foo/{b{1,4}}/{[0-9]+}", '{', '}', "foo/bbb/123").unwrap(),
107            true
108        );
109    }
110}