rama_http/matcher/
uri.rs

1//! provides a [`UriMatcher`] matcher for matching requests based on their URI.
2
3use crate::{Request, Uri};
4use rama_core::{context::Extensions, Context};
5
6pub mod dep {
7    //! dependencies for the `uri` matcher module
8
9    pub use regex;
10}
11
12use dep::regex::Regex;
13
14#[derive(Debug, Clone)]
15/// Matcher the request's URI, using a substring or regex pattern.
16pub struct UriMatcher {
17    re: Regex,
18}
19
20impl UriMatcher {
21    /// create a new Uri matcher using a regex pattern.
22    ///
23    /// See docs at <https://docs.rs/regex> for more information on regex patterns.
24    /// (e.g. to use flags like (?i) for case-insensitive matching)
25    ///
26    /// # Panics
27    ///
28    /// Panics if the regex pattern is invalid.
29    pub fn new(re: impl AsRef<str>) -> Self {
30        let re = Regex::new(re.as_ref()).expect("valid regex pattern");
31        Self { re }
32    }
33
34    pub(crate) fn matches_uri(&self, uri: &Uri) -> bool {
35        self.re.is_match(uri.to_string().as_str())
36    }
37}
38
39impl From<Regex> for UriMatcher {
40    fn from(re: Regex) -> Self {
41        Self { re }
42    }
43}
44
45impl<State, Body> rama_core::matcher::Matcher<State, Request<Body>> for UriMatcher {
46    fn matches(
47        &self,
48        _ext: Option<&mut Extensions>,
49        _ctx: &Context<State>,
50        req: &Request<Body>,
51    ) -> bool {
52        self.matches_uri(req.uri())
53    }
54}
55
56#[cfg(test)]
57mod test {
58    use super::*;
59
60    #[test]
61    fn matchest_uri_match() {
62        let test_cases: Vec<(UriMatcher, &str)> = vec![
63            (
64                UriMatcher::new(r"www\.example\.com"),
65                "http://www.example.com",
66            ),
67            (
68                UriMatcher::new(r"(?i)www\.example\.com"),
69                "http://WwW.ExamplE.COM",
70            ),
71            (
72                UriMatcher::new(r"(?i)^[^?]+\.(jpeg|png|gif|css)(\?|\z)"),
73                "http://www.example.com/assets/style.css?foo=bar",
74            ),
75            (
76                UriMatcher::new(r"(?i)^[^?]+\.(jpeg|png|gif|css)(\?|\z)"),
77                "http://www.example.com/image.png",
78            ),
79        ];
80        for (matcher, uri) in test_cases.into_iter() {
81            assert!(
82                matcher.matches_uri(&(uri.parse().unwrap())),
83                "({:?}).matches_uri({})",
84                matcher,
85                uri
86            );
87        }
88    }
89
90    #[test]
91    fn matchest_uri_no_match() {
92        let test_cases = vec![
93            (UriMatcher::new("www.example.com"), "http://WwW.ExamplE.COM"),
94            (
95                UriMatcher::new(r"(?i)^[^?]+\.(jpeg|png|gif|css)(\?|\z)"),
96                "http://www.example.com/?style.css",
97            ),
98        ];
99        for (matcher, uri) in test_cases.into_iter() {
100            assert!(
101                !matcher.matches_uri(&(uri.parse().unwrap())),
102                "!({:?}).matches_uri({})",
103                matcher,
104                uri
105            );
106        }
107    }
108}