Skip to main content

rama_http/matcher/
version.rs

1use crate::{Request, Version};
2use rama_core::extensions::Extensions;
3use std::fmt::{self, Debug, Formatter};
4
5/// A matcher that matches one or more HTTP methods.
6#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
7pub struct VersionMatcher(u16);
8
9impl VersionMatcher {
10    /// A matcher that matches HTTP/0.9 requests.
11    pub const HTTP_09: Self = Self::from_bits(0b0_0000_0010);
12
13    /// A matcher that matches HTTP/1.0 requests.
14    pub const HTTP_10: Self = Self::from_bits(0b0_0000_0100);
15
16    /// A matcher that matches HTTP/1.1 requests.
17    pub const HTTP_11: Self = Self::from_bits(0b0_0000_1000);
18
19    /// A matcher that matches HTTP/2.0 (h2) requests.
20    pub const HTTP_2: Self = Self::from_bits(0b0_0001_0000);
21
22    /// A matcher that matches HTTP/3.0 (h3) requests.
23    pub const HTTP_3: Self = Self::from_bits(0b0_0010_0000);
24
25    const fn bits(self) -> u16 {
26        let bits = self;
27        bits.0
28    }
29
30    const fn from_bits(bits: u16) -> Self {
31        Self(bits)
32    }
33
34    pub(crate) const fn contains(self, other: Self) -> bool {
35        self.bits() & other.bits() == other.bits()
36    }
37
38    /// Performs the OR operation between the [`VersionMatcher`] in `self` with `other`.
39    #[must_use]
40    pub const fn or(self, other: Self) -> Self {
41        Self(self.0 | other.0)
42    }
43}
44
45impl<Body> rama_core::matcher::Matcher<Request<Body>> for VersionMatcher {
46    /// returns true on a match, false otherwise
47    fn matches(&self, _ext: Option<&Extensions>, req: &Request<Body>) -> bool {
48        Self::try_from(req.version())
49            .ok()
50            .map(|version| self.contains(version))
51            .unwrap_or_default()
52    }
53}
54
55/// Error type used when converting a [`Version`] to a [`VersionMatcher`] fails.
56#[derive(Debug)]
57pub struct NoMatchingVersionMatcher {
58    version: Version,
59}
60
61impl NoMatchingVersionMatcher {
62    /// Get the [`Version`] that couldn't be converted to a [`VersionMatcher`].
63    pub fn version(&self) -> &Version {
64        &self.version
65    }
66}
67
68impl fmt::Display for NoMatchingVersionMatcher {
69    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
70        write!(f, "no `VersionMatcher` for `{:?}`", self.version)
71    }
72}
73
74impl std::error::Error for NoMatchingVersionMatcher {}
75
76impl TryFrom<Version> for VersionMatcher {
77    type Error = NoMatchingVersionMatcher;
78
79    fn try_from(m: Version) -> Result<Self, Self::Error> {
80        match m {
81            Version::HTTP_09 => Ok(Self::HTTP_09),
82            Version::HTTP_10 => Ok(Self::HTTP_10),
83            Version::HTTP_11 => Ok(Self::HTTP_11),
84            Version::HTTP_2 => Ok(Self::HTTP_2),
85            Version::HTTP_3 => Ok(Self::HTTP_3),
86        }
87    }
88}
89
90#[cfg(test)]
91mod test {
92    use super::*;
93    use rama_core::matcher::Matcher;
94
95    #[test]
96    fn test_version_matcher() {
97        let matcher = VersionMatcher::HTTP_11;
98        let req = Request::builder()
99            .version(Version::HTTP_11)
100            .body(())
101            .unwrap();
102        assert!(matcher.matches(None, &req));
103    }
104
105    #[test]
106    fn test_version_matcher_any() {
107        let matcher = VersionMatcher::HTTP_11
108            .or(VersionMatcher::HTTP_10)
109            .or(VersionMatcher::HTTP_11);
110
111        let req = Request::builder()
112            .version(Version::HTTP_10)
113            .body(())
114            .unwrap();
115        assert!(matcher.matches(None, &req));
116
117        let req = Request::builder()
118            .version(Version::HTTP_11)
119            .body(())
120            .unwrap();
121        assert!(matcher.matches(None, &req));
122
123        let req = Request::builder()
124            .version(Version::HTTP_2)
125            .body(())
126            .unwrap();
127        assert!(!matcher.matches(None, &req));
128    }
129
130    #[test]
131    fn test_version_matcher_fail() {
132        let matcher = VersionMatcher::HTTP_11;
133        let req = Request::builder()
134            .version(Version::HTTP_10)
135            .body(())
136            .unwrap();
137        assert!(!matcher.matches(None, &req));
138    }
139}