Skip to main content

rama_http/matcher/
header.rs

1use crate::{HeaderName, HeaderValue, Request};
2use rama_core::{extensions::Extensions, matcher::Matcher};
3
4#[derive(Debug, Clone)]
5/// Matcher based on the [`Request`]'s headers.
6///
7/// [`Request`]: crate::Request
8pub struct HeaderMatcher {
9    name: HeaderName,
10    kind: HeaderMatcherKind,
11}
12
13#[derive(Debug, Clone)]
14enum HeaderMatcherKind {
15    Exists,
16    Is(HeaderValue),
17    Contains(HeaderValue),
18}
19
20impl HeaderMatcher {
21    /// Create a new header matcher to match on the existence of a header.
22    pub fn exists(name: HeaderName) -> Self {
23        Self {
24            name,
25            kind: HeaderMatcherKind::Exists,
26        }
27    }
28
29    /// Create a new header matcher to match on an exact header value match.
30    pub fn is(name: HeaderName, value: HeaderValue) -> Self {
31        Self {
32            name,
33            kind: HeaderMatcherKind::Is(value),
34        }
35    }
36
37    /// Create a new header matcher to match that the header contains the given value.
38    pub fn contains(name: HeaderName, value: HeaderValue) -> Self {
39        Self {
40            name,
41            kind: HeaderMatcherKind::Contains(value),
42        }
43    }
44}
45
46impl<Body> Matcher<Request<Body>> for HeaderMatcher {
47    fn matches(&self, _ext: Option<&Extensions>, req: &Request<Body>) -> bool {
48        let headers = req.headers();
49        match self.kind {
50            HeaderMatcherKind::Exists => headers.contains_key(&self.name),
51            HeaderMatcherKind::Is(ref value) => headers.get(&self.name) == Some(value),
52            HeaderMatcherKind::Contains(ref value) => {
53                headers.get_all(&self.name).iter().any(|v| v == value)
54            }
55        }
56    }
57}
58
59#[cfg(test)]
60mod test {
61    use super::*;
62
63    #[test]
64    fn test_header_matcher_exists() {
65        let matcher = HeaderMatcher::exists("content-type".parse().unwrap());
66        let req = Request::builder()
67            .header("content-type", "text/plain")
68            .body(())
69            .unwrap();
70        assert!(matcher.matches(None, &req));
71    }
72
73    #[test]
74    fn test_header_matcher_exists_no_match() {
75        let matcher = HeaderMatcher::exists("content-type".parse().unwrap());
76        let req = Request::builder().body(()).unwrap();
77        assert!(!matcher.matches(None, &req));
78    }
79
80    #[test]
81    fn test_header_matcher_is() {
82        let matcher = HeaderMatcher::is(
83            "content-type".parse().unwrap(),
84            "text/plain".parse().unwrap(),
85        );
86        let req = Request::builder()
87            .header("content-type", "text/plain")
88            .body(())
89            .unwrap();
90        assert!(matcher.matches(None, &req));
91    }
92
93    #[test]
94    fn test_header_matcher_is_no_match() {
95        let matcher = HeaderMatcher::is(
96            "content-type".parse().unwrap(),
97            "text/plain".parse().unwrap(),
98        );
99        let req = Request::builder()
100            .header("content-type", "text/html")
101            .body(())
102            .unwrap();
103        assert!(!matcher.matches(None, &req));
104    }
105
106    #[test]
107    fn test_header_matcher_contains() {
108        let matcher = HeaderMatcher::contains(
109            "content-type".parse().unwrap(),
110            "text/plain".parse().unwrap(),
111        );
112        let req = Request::builder()
113            .header("content-type", "text/plain")
114            .body(())
115            .unwrap();
116        assert!(matcher.matches(None, &req));
117    }
118
119    #[test]
120    fn test_header_matcher_contains_no_match() {
121        let matcher = HeaderMatcher::contains(
122            "content-type".parse().unwrap(),
123            "text/plain".parse().unwrap(),
124        );
125        let req = Request::builder()
126            .header("content-type", "text/html")
127            .body(())
128            .unwrap();
129        assert!(!matcher.matches(None, &req));
130    }
131
132    #[test]
133    fn test_header_matcher_contains_multiple() {
134        let matcher = HeaderMatcher::contains(
135            "content-type".parse().unwrap(),
136            "text/plain".parse().unwrap(),
137        );
138        let req = Request::builder()
139            .header("content-type", "text/html")
140            .header("content-type", "text/plain")
141            .body(())
142            .unwrap();
143        assert!(matcher.matches(None, &req));
144    }
145
146    #[test]
147    fn test_header_matcher_contains_multiple_no_match() {
148        let matcher = HeaderMatcher::contains(
149            "content-type".parse().unwrap(),
150            "text/plain".parse().unwrap(),
151        );
152        let req = Request::builder()
153            .header("content-type", "text/html")
154            .header("content-type", "text/xml")
155            .body(())
156            .unwrap();
157        assert!(!matcher.matches(None, &req));
158    }
159}