Skip to main content

rama_http/matcher/
method.rs

1use crate::{Method, Request};
2use rama_core::extensions::Extensions;
3use std::{
4    fmt,
5    fmt::{Debug, Formatter},
6};
7
8/// A matcher that matches one or more HTTP methods.
9#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
10pub struct MethodMatcher(u16);
11
12impl MethodMatcher {
13    /// Match `CONNECT` requests.
14    pub const CONNECT: Self = Self::from_bits(0b0_0000_0001);
15    /// Match `DELETE` requests.
16    pub const DELETE: Self = Self::from_bits(0b0_0000_0010);
17    /// Match `GET` requests.
18    pub const GET: Self = Self::from_bits(0b0_0000_0100);
19    /// Match `HEAD` requests.
20    pub const HEAD: Self = Self::from_bits(0b0_0000_1000);
21    /// Match `OPTIONS` requests.
22    pub const OPTIONS: Self = Self::from_bits(0b0_0001_0000);
23    /// Match `PATCH` requests.
24    pub const PATCH: Self = Self::from_bits(0b0_0010_0000);
25    /// Match `POST` requests.
26    pub const POST: Self = Self::from_bits(0b0_0100_0000);
27    /// Match `PUT` requests.
28    pub const PUT: Self = Self::from_bits(0b0_1000_0000);
29    /// Match `QUERY` requests.
30    pub const QUERY: Self = Self::from_bits(0b1_0000_0000);
31    /// Match `TRACE` requests.
32    pub const TRACE: Self = Self::from_bits(0b10_0000_0000);
33
34    const fn bits(self) -> u16 {
35        let bits = self;
36        bits.0
37    }
38
39    const fn from_bits(bits: u16) -> Self {
40        Self(bits)
41    }
42
43    /// An empty matcher that matches no methods — the identity element for [`or`].
44    pub(crate) const NONE: Self = Self::from_bits(0);
45    pub(crate) const ALL_KNOWN: Self = Self::CONNECT
46        .or_method(Self::DELETE)
47        .or_method(Self::GET)
48        .or_method(Self::HEAD)
49        .or_method(Self::OPTIONS)
50        .or_method(Self::PATCH)
51        .or_method(Self::POST)
52        .or_method(Self::PUT)
53        .or_method(Self::QUERY)
54        .or_method(Self::TRACE);
55
56    /// Returns `true` if `self` contains all bits set in `other`.
57    pub const fn contains(self, other: Self) -> bool {
58        self.bits() & other.bits() == other.bits()
59    }
60
61    /// Performs the OR operation between the [`MethodMatcher`] in `self` with `other`.
62    #[must_use]
63    pub const fn or_method(self, other: Self) -> Self {
64        Self(self.0 | other.0)
65    }
66
67    /// Performs the AND operation between the [`MethodMatcher`] in `self` with `other`.
68    ///
69    /// Useful for intersecting two method sets (e.g. inside an `All` compound matcher).
70    #[must_use]
71    pub const fn and_method(self, other: Self) -> Self {
72        Self(self.0 & other.0)
73    }
74
75    /// Returns the complement: all known methods NOT in `self`.
76    ///
77    /// Used when a method matcher is negated — e.g. `NOT GET` → every known method except GET.
78    #[must_use]
79    pub const fn complement(self) -> Self {
80        Self(Self::ALL_KNOWN.bits() & !self.bits())
81    }
82
83    /// Returns an iterator over the [`Method`] variants represented by this matcher.
84    ///
85    /// Yields methods in declaration order (CONNECT, DELETE, GET, HEAD, OPTIONS,
86    /// PATCH, POST, PUT, QUERY, TRACE) for only the bits that are set.
87    pub fn iter(self) -> impl Iterator<Item = Method> {
88        [
89            (Self::CONNECT, Method::CONNECT),
90            (Self::DELETE, Method::DELETE),
91            (Self::GET, Method::GET),
92            (Self::HEAD, Method::HEAD),
93            (Self::OPTIONS, Method::OPTIONS),
94            (Self::PATCH, Method::PATCH),
95            (Self::POST, Method::POST),
96            (Self::PUT, Method::PUT),
97            (Self::QUERY, Method::QUERY),
98            (Self::TRACE, Method::TRACE),
99        ]
100        .into_iter()
101        .filter_map(move |(m, method)| self.contains(m).then_some(method))
102    }
103}
104
105impl<Body> rama_core::matcher::Matcher<Request<Body>> for MethodMatcher {
106    /// returns true on a match, false otherwise
107    fn matches(&self, _ext: Option<&Extensions>, req: &Request<Body>) -> bool {
108        Self::try_from(req.method())
109            .ok()
110            .map(|method| self.contains(method))
111            .unwrap_or_default()
112    }
113}
114
115/// Error type used when converting a [`Method`] to a [`MethodMatcher`] fails.
116#[derive(Debug)]
117pub struct NoMatchingMethodMatcher {
118    method: Method,
119}
120
121impl NoMatchingMethodMatcher {
122    /// Get the [`Method`] that couldn't be converted to a [`MethodMatcher`].
123    pub fn method(&self) -> &Method {
124        &self.method
125    }
126}
127
128impl fmt::Display for NoMatchingMethodMatcher {
129    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
130        write!(f, "no `MethodMatcher` for `{}`", self.method.as_str())
131    }
132}
133
134impl std::error::Error for NoMatchingMethodMatcher {}
135
136impl TryFrom<&Method> for MethodMatcher {
137    type Error = NoMatchingMethodMatcher;
138
139    fn try_from(m: &Method) -> Result<Self, Self::Error> {
140        match m {
141            &Method::CONNECT => Ok(Self::CONNECT),
142            &Method::DELETE => Ok(Self::DELETE),
143            &Method::GET => Ok(Self::GET),
144            &Method::HEAD => Ok(Self::HEAD),
145            &Method::OPTIONS => Ok(Self::OPTIONS),
146            &Method::PATCH => Ok(Self::PATCH),
147            &Method::POST => Ok(Self::POST),
148            &Method::PUT => Ok(Self::PUT),
149            &Method::QUERY => Ok(Self::QUERY),
150            &Method::TRACE => Ok(Self::TRACE),
151            other => Err(Self::Error {
152                method: other.clone(),
153            }),
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn from_http_method() {
164        assert_eq!(
165            MethodMatcher::try_from(&Method::CONNECT).unwrap(),
166            MethodMatcher::CONNECT
167        );
168
169        assert_eq!(
170            MethodMatcher::try_from(&Method::DELETE).unwrap(),
171            MethodMatcher::DELETE
172        );
173
174        assert_eq!(
175            MethodMatcher::try_from(&Method::GET).unwrap(),
176            MethodMatcher::GET
177        );
178
179        assert_eq!(
180            MethodMatcher::try_from(&Method::HEAD).unwrap(),
181            MethodMatcher::HEAD
182        );
183
184        assert_eq!(
185            MethodMatcher::try_from(&Method::OPTIONS).unwrap(),
186            MethodMatcher::OPTIONS
187        );
188
189        assert_eq!(
190            MethodMatcher::try_from(&Method::PATCH).unwrap(),
191            MethodMatcher::PATCH
192        );
193
194        assert_eq!(
195            MethodMatcher::try_from(&Method::POST).unwrap(),
196            MethodMatcher::POST
197        );
198
199        assert_eq!(
200            MethodMatcher::try_from(&Method::PUT).unwrap(),
201            MethodMatcher::PUT
202        );
203
204        assert_eq!(
205            MethodMatcher::try_from(&Method::QUERY).unwrap(),
206            MethodMatcher::QUERY
207        );
208
209        assert_eq!(
210            MethodMatcher::try_from(&Method::TRACE).unwrap(),
211            MethodMatcher::TRACE
212        );
213    }
214
215    #[test]
216    fn query_is_a_known_method() {
217        // QUERY (RFC 10008) must participate in ALL_KNOWN so it round-trips
218        // through `iter()` and shows up in `Allow` headers / 405 responses.
219        assert!(MethodMatcher::ALL_KNOWN.contains(MethodMatcher::QUERY));
220        let methods: Vec<_> = MethodMatcher::QUERY.iter().collect();
221        assert_eq!(methods, vec![Method::QUERY]);
222        assert!(MethodMatcher::ALL_KNOWN.iter().any(|m| m == Method::QUERY));
223    }
224}