axum/routing/
method_filter.rs

1use http::Method;
2use std::{
3    fmt,
4    fmt::{Debug, Formatter},
5};
6
7/// A filter that matches one or more HTTP methods.
8#[derive(Debug, Copy, Clone, PartialEq)]
9pub struct MethodFilter(u16);
10
11impl MethodFilter {
12    /// Match `CONNECT` requests.
13    ///
14    /// This is useful for implementing HTTP/2's [extended CONNECT method],
15    /// in which the `:protocol` pseudoheader is read
16    /// (using [`hyper::ext::Protocol`])
17    /// and the connection upgraded to a bidirectional byte stream
18    /// (using [`hyper::upgrade::on`]).
19    ///
20    /// As seen in the [HTTP Upgrade Token Registry],
21    /// common uses include WebSockets and proxying UDP or IP –
22    /// though note that when using [`WebSocketUpgrade`]
23    /// it's more useful to use [`any`](crate::routing::any)
24    /// as HTTP/1.1 WebSockets need to support `GET`.
25    ///
26    /// [extended CONNECT]: https://www.rfc-editor.org/rfc/rfc8441.html#section-4
27    /// [HTTP Upgrade Token Registry]: https://www.iana.org/assignments/http-upgrade-tokens/http-upgrade-tokens.xhtml
28    /// [`WebSocketUpgrade`]: crate::extract::WebSocketUpgrade
29    pub const CONNECT: Self = Self::from_bits(0b0_0000_0001);
30    /// Match `DELETE` requests.
31    pub const DELETE: Self = Self::from_bits(0b0_0000_0010);
32    /// Match `GET` requests.
33    pub const GET: Self = Self::from_bits(0b0_0000_0100);
34    /// Match `HEAD` requests.
35    pub const HEAD: Self = Self::from_bits(0b0_0000_1000);
36    /// Match `OPTIONS` requests.
37    pub const OPTIONS: Self = Self::from_bits(0b0_0001_0000);
38    /// Match `PATCH` requests.
39    pub const PATCH: Self = Self::from_bits(0b0_0010_0000);
40    /// Match `POST` requests.
41    pub const POST: Self = Self::from_bits(0b0_0100_0000);
42    /// Match `PUT` requests.
43    pub const PUT: Self = Self::from_bits(0b0_1000_0000);
44    /// Match `TRACE` requests.
45    pub const TRACE: Self = Self::from_bits(0b1_0000_0000);
46
47    const fn bits(&self) -> u16 {
48        let bits = self;
49        bits.0
50    }
51
52    const fn from_bits(bits: u16) -> Self {
53        Self(bits)
54    }
55
56    pub(crate) const fn contains(&self, other: Self) -> bool {
57        self.bits() & other.bits() == other.bits()
58    }
59
60    /// Performs the OR operation between the [`MethodFilter`] in `self` with `other`.
61    pub const fn or(self, other: Self) -> Self {
62        Self(self.0 | other.0)
63    }
64}
65
66/// Error type used when converting a [`Method`] to a [`MethodFilter`] fails.
67#[derive(Debug)]
68pub struct NoMatchingMethodFilter {
69    method: Method,
70}
71
72impl NoMatchingMethodFilter {
73    /// Get the [`Method`] that couldn't be converted to a [`MethodFilter`].
74    pub fn method(&self) -> &Method {
75        &self.method
76    }
77}
78
79impl fmt::Display for NoMatchingMethodFilter {
80    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
81        write!(f, "no `MethodFilter` for `{}`", self.method.as_str())
82    }
83}
84
85impl std::error::Error for NoMatchingMethodFilter {}
86
87impl TryFrom<Method> for MethodFilter {
88    type Error = NoMatchingMethodFilter;
89
90    fn try_from(m: Method) -> Result<Self, NoMatchingMethodFilter> {
91        match m {
92            Method::CONNECT => Ok(MethodFilter::CONNECT),
93            Method::DELETE => Ok(MethodFilter::DELETE),
94            Method::GET => Ok(MethodFilter::GET),
95            Method::HEAD => Ok(MethodFilter::HEAD),
96            Method::OPTIONS => Ok(MethodFilter::OPTIONS),
97            Method::PATCH => Ok(MethodFilter::PATCH),
98            Method::POST => Ok(MethodFilter::POST),
99            Method::PUT => Ok(MethodFilter::PUT),
100            Method::TRACE => Ok(MethodFilter::TRACE),
101            other => Err(NoMatchingMethodFilter { method: other }),
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn from_http_method() {
112        assert_eq!(
113            MethodFilter::try_from(Method::CONNECT).unwrap(),
114            MethodFilter::CONNECT
115        );
116
117        assert_eq!(
118            MethodFilter::try_from(Method::DELETE).unwrap(),
119            MethodFilter::DELETE
120        );
121
122        assert_eq!(
123            MethodFilter::try_from(Method::GET).unwrap(),
124            MethodFilter::GET
125        );
126
127        assert_eq!(
128            MethodFilter::try_from(Method::HEAD).unwrap(),
129            MethodFilter::HEAD
130        );
131
132        assert_eq!(
133            MethodFilter::try_from(Method::OPTIONS).unwrap(),
134            MethodFilter::OPTIONS
135        );
136
137        assert_eq!(
138            MethodFilter::try_from(Method::PATCH).unwrap(),
139            MethodFilter::PATCH
140        );
141
142        assert_eq!(
143            MethodFilter::try_from(Method::POST).unwrap(),
144            MethodFilter::POST
145        );
146
147        assert_eq!(
148            MethodFilter::try_from(Method::PUT).unwrap(),
149            MethodFilter::PUT
150        );
151
152        assert_eq!(
153            MethodFilter::try_from(Method::TRACE).unwrap(),
154            MethodFilter::TRACE
155        );
156
157        assert!(
158            MethodFilter::try_from(http::Method::from_bytes(b"CUSTOM").unwrap())
159                .unwrap_err()
160                .to_string()
161                .contains("CUSTOM")
162        );
163    }
164}