Skip to main content

http_net/
method.rs

1//! Request methods and sets of them.
2
3use alloc::vec::Vec;
4
5/// The request method.
6///
7/// Methods are case-sensitive (RFC 9110 section 9.1): `get` is a different,
8/// unregistered method and parses as [`Method::Other`]. The exact text is
9/// always available from [`RequestHead::method_str`](crate::RequestHead::method_str).
10/// A variant is added only when an RFC registers the method.
11///
12/// # Examples
13///
14/// ```
15/// use http_net::Method;
16///
17/// assert_eq!(Method::from_bytes(b"GET"), Method::Get);
18/// assert_eq!(Method::from_bytes(b"get"), Method::Other);
19/// assert_eq!(Method::Post.as_str(), Some("POST"));
20/// ```
21#[non_exhaustive]
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum Method {
24    /// `GET`
25    Get,
26    /// `HEAD`: the response carries headers but never a body.
27    Head,
28    /// `POST`
29    Post,
30    /// `PUT`
31    Put,
32    /// `DELETE`
33    Delete,
34    /// `CONNECT`: the target is in authority form; a 2xx response turns the
35    /// connection into a tunnel.
36    Connect,
37    /// `OPTIONS`: the only method that may use the `*` target.
38    Options,
39    /// `TRACE`
40    Trace,
41    /// `PATCH`
42    Patch,
43    /// Any other token.
44    Other,
45}
46
47/// Every variant in [`Method::index`] order.
48const ALL: [Method; 10] = [
49    Method::Get,
50    Method::Head,
51    Method::Post,
52    Method::Put,
53    Method::Delete,
54    Method::Connect,
55    Method::Options,
56    Method::Trace,
57    Method::Patch,
58    Method::Other,
59];
60
61impl Method {
62    /// Classifies a method token (case-sensitive).
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// assert_eq!(http_net::Method::from_bytes(b"PATCH"), http_net::Method::Patch);
68    /// ```
69    #[must_use]
70    pub fn from_bytes(token: &[u8]) -> Self {
71        match token {
72            b"GET" => Self::Get,
73            b"HEAD" => Self::Head,
74            b"POST" => Self::Post,
75            b"PUT" => Self::Put,
76            b"DELETE" => Self::Delete,
77            b"CONNECT" => Self::Connect,
78            b"OPTIONS" => Self::Options,
79            b"TRACE" => Self::Trace,
80            b"PATCH" => Self::Patch,
81            _ => Self::Other,
82        }
83    }
84
85    /// The method's text, or `None` for [`Method::Other`].
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// assert_eq!(http_net::Method::Other.as_str(), None);
91    /// ```
92    #[must_use]
93    pub const fn as_str(&self) -> Option<&'static str> {
94        Some(match self {
95            Self::Get => "GET",
96            Self::Head => "HEAD",
97            Self::Post => "POST",
98            Self::Put => "PUT",
99            Self::Delete => "DELETE",
100            Self::Connect => "CONNECT",
101            Self::Options => "OPTIONS",
102            Self::Trace => "TRACE",
103            Self::Patch => "PATCH",
104            Self::Other => return None,
105        })
106    }
107
108    /// Whether the method is safe (RFC 9110 section 9.2.1): `GET`, `HEAD`,
109    /// `OPTIONS`, `TRACE`. [`Method::Other`] is not; decide from the text.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use http_net::Method;
115    ///
116    /// assert!(Method::Head.is_safe());
117    /// assert!(!Method::Put.is_safe());
118    /// ```
119    #[must_use]
120    pub const fn is_safe(&self) -> bool {
121        matches!(self, Self::Get | Self::Head | Self::Options | Self::Trace)
122    }
123
124    /// Whether the method is idempotent (RFC 9110 section 9.2.2): the safe
125    /// methods plus `PUT` and `DELETE`.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use http_net::Method;
131    ///
132    /// assert!(Method::Delete.is_idempotent());
133    /// assert!(!Method::Post.is_idempotent());
134    /// ```
135    #[must_use]
136    pub const fn is_idempotent(&self) -> bool {
137        self.is_safe() || matches!(self, Self::Put | Self::Delete)
138    }
139
140    /// A dense index, `0..=9` in declaration order (`Other` is 9), for bit
141    /// sets and lookup tables.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use http_net::Method;
147    ///
148    /// assert_eq!(Method::Get.index(), 0);
149    /// assert_eq!(Method::Other.index(), 9);
150    /// ```
151    #[must_use]
152    pub const fn index(&self) -> u8 {
153        match self {
154            Self::Get => 0,
155            Self::Head => 1,
156            Self::Post => 2,
157            Self::Put => 3,
158            Self::Delete => 4,
159            Self::Connect => 5,
160            Self::Options => 6,
161            Self::Trace => 7,
162            Self::Patch => 8,
163            Self::Other => 9,
164        }
165    }
166}
167
168/// A set of methods: one bit per [`Method`], so a membership check is one
169/// AND. For routers and for the `Allow` field of a `405` response.
170///
171/// [`Method::Other`] cannot be represented precisely: `contains(Method::Other)`
172/// means "some extension method is allowed", and
173/// [`write_allow`](Self::write_allow) leaves it out.
174///
175/// # Examples
176///
177/// ```
178/// use http_net::{Method, MethodSet};
179///
180/// const READ: MethodSet = MethodSet::of(&[Method::Get, Method::Head]);
181/// let allowed = READ.with(Method::Post);
182/// assert!(allowed.contains(Method::Post));
183/// assert!(!allowed.contains(Method::Delete));
184///
185/// let mut allow = Vec::new();
186/// allowed.write_allow(&mut allow);
187/// assert_eq!(allow, b"GET, HEAD, POST");
188/// ```
189#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
190pub struct MethodSet {
191    bits: u16,
192}
193
194impl MethodSet {
195    /// The empty set.
196    ///
197    /// # Examples
198    ///
199    /// ```
200    /// assert!(http_net::MethodSet::EMPTY.is_empty());
201    /// ```
202    pub const EMPTY: Self = Self { bits: 0 };
203
204    /// The set of `methods`.
205    ///
206    /// # Examples
207    ///
208    /// ```
209    /// use http_net::{Method, MethodSet};
210    ///
211    /// let set = MethodSet::of(&[Method::Put, Method::Delete, Method::Put]);
212    /// assert_eq!(set.iter().count(), 2);
213    /// ```
214    #[must_use]
215    pub const fn of(methods: &[Method]) -> Self {
216        let mut set = Self::EMPTY;
217        let mut i = 0;
218        while i < methods.len() {
219            set = set.with(methods[i]);
220            i += 1;
221        }
222        set
223    }
224
225    /// This set with `method` added.
226    ///
227    /// # Examples
228    ///
229    /// ```
230    /// use http_net::{Method, MethodSet};
231    ///
232    /// assert!(MethodSet::EMPTY.with(Method::Get).contains(Method::Get));
233    /// ```
234    #[must_use]
235    pub const fn with(self, method: Method) -> Self {
236        Self {
237            bits: self.bits | (1 << method.index()),
238        }
239    }
240
241    /// Whether `method` is in the set.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use http_net::{Method, MethodSet};
247    ///
248    /// assert!(!MethodSet::EMPTY.contains(Method::Get));
249    /// ```
250    #[must_use]
251    pub const fn contains(&self, method: Method) -> bool {
252        self.bits & (1 << method.index()) != 0
253    }
254
255    /// The methods in either set.
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use http_net::{Method, MethodSet};
261    ///
262    /// let both = MethodSet::of(&[Method::Get]).union(MethodSet::of(&[Method::Post]));
263    /// assert!(both.contains(Method::Get) && both.contains(Method::Post));
264    /// ```
265    #[must_use]
266    pub const fn union(self, other: Self) -> Self {
267        Self {
268            bits: self.bits | other.bits,
269        }
270    }
271
272    /// Whether the set is empty.
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use http_net::{Method, MethodSet};
278    ///
279    /// assert!(!MethodSet::of(&[Method::Get]).is_empty());
280    /// ```
281    #[must_use]
282    pub const fn is_empty(&self) -> bool {
283        self.bits == 0
284    }
285
286    /// The members in [`Method::index`] order.
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use http_net::{Method, MethodSet};
292    ///
293    /// let set = MethodSet::of(&[Method::Post, Method::Get]);
294    /// assert_eq!(set.iter().collect::<Vec<_>>(), [Method::Get, Method::Post]);
295    /// ```
296    pub fn iter(&self) -> impl Iterator<Item = Method> + use<> {
297        let set = *self;
298        ALL.into_iter().filter(move |m| set.contains(*m))
299    }
300
301    /// Appends the registered members to `out` as an `Allow` field value,
302    /// in [`Method::index`] order, separated by `", "`. `Other` is left out
303    /// (it has no name to write).
304    ///
305    /// # Examples
306    ///
307    /// ```
308    /// use http_net::{Method, MethodSet};
309    ///
310    /// let mut out = Vec::new();
311    /// MethodSet::of(&[Method::Options, Method::Get, Method::Other]).write_allow(&mut out);
312    /// assert_eq!(out, b"GET, OPTIONS");
313    /// ```
314    pub fn write_allow(&self, out: &mut Vec<u8>) {
315        let mut first = true;
316        for name in self.iter().filter_map(|m| m.as_str()) {
317            if !first {
318                out.extend_from_slice(b", ");
319            }
320            first = false;
321            out.extend_from_slice(name.as_bytes());
322        }
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[test]
331    fn test_method_properties_match_rfc_9110() {
332        let safe = [Method::Get, Method::Head, Method::Options, Method::Trace];
333        let idempotent = [
334            Method::Get,
335            Method::Head,
336            Method::Options,
337            Method::Trace,
338            Method::Put,
339            Method::Delete,
340        ];
341        for m in ALL {
342            assert_eq!(m.is_safe(), safe.contains(&m), "{m:?}");
343            assert_eq!(m.is_idempotent(), idempotent.contains(&m), "{m:?}");
344        }
345    }
346
347    #[test]
348    fn test_method_index_dense_round_trips() {
349        for (i, m) in ALL.iter().enumerate() {
350            assert_eq!(usize::from(m.index()), i);
351            if let Some(text) = m.as_str() {
352                assert_eq!(Method::from_bytes(text.as_bytes()), *m);
353            }
354        }
355    }
356
357    #[test]
358    fn test_method_set_operations_cover_every_method() {
359        let all = MethodSet::of(&ALL);
360        assert_eq!(all.iter().count(), 10);
361        for m in ALL {
362            assert!(all.contains(m));
363            assert!(!MethodSet::EMPTY.contains(m));
364            assert_eq!(MethodSet::EMPTY.with(m).iter().collect::<Vec<_>>(), [m]);
365        }
366        assert!(MethodSet::default().is_empty());
367        let mut out = Vec::new();
368        all.write_allow(&mut out);
369        assert_eq!(
370            out,
371            b"GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH"
372        );
373        let mut out = Vec::new();
374        MethodSet::of(&[Method::Other]).write_allow(&mut out);
375        assert!(out.is_empty());
376    }
377}