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}