bouillon/
method.rs

1use Inner::*;
2use std::fmt;
3
4/// An HTTP method.
5#[derive(Clone, PartialEq, Eq, Hash)]
6pub struct Method(Inner);
7
8impl Method {
9    /// GET
10    pub const GET: Method = Method(Get);
11
12    /// POST
13    pub const POST: Method = Method(Post);
14
15    /// PUT
16    pub const PUT: Method = Method(Put);
17
18    /// DELETE
19    pub const DELETE: Method = Method(Delete);
20
21    /// HEAD
22    pub const HEAD: Method = Method(Head);
23
24    /// OPTIONS
25    pub const OPTIONS: Method = Method(Options);
26
27    /// CONNECT
28    pub const CONNECT: Method = Method(Connect);
29
30    /// PATCH
31    pub const PATCH: Method = Method(Patch);
32
33    /// TRACE
34    pub const TRACE: Method = Method(Trace);
35
36    pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
37        match src {
38            b"" => Err(InvalidMethod {}),
39            b"GET" => Ok(Method::GET),
40            b"PUT" => Ok(Method::PUT),
41            b"POST" => Ok(Method::POST),
42            b"HEAD" => Ok(Method::HEAD),
43            b"PATCH" => Ok(Method::PATCH),
44            b"TRACE" => Ok(Method::TRACE),
45            b"DELETE" => Ok(Method::DELETE),
46            b"OPTIONS" => Ok(Method::OPTIONS),
47            b"CONNECT" => Ok(Method::CONNECT),
48            _ if src.iter().all(|b| METHOD_CHARS.contains(b)) => {
49                // SAFETY: All METHOD_CHARS are valid UTF-8, so src is too.
50                let string = unsafe { str::from_utf8_unchecked(src) };
51                Ok(Method(Inner::Other(string.to_owned())))
52            }
53            _ => Err(InvalidMethod {}),
54        }
55    }
56
57    /// Return a &str representation of the HTTP method
58    #[inline]
59    pub fn as_str(&self) -> &str {
60        match self.0 {
61            Options => "OPTIONS",
62            Get => "GET",
63            Post => "POST",
64            Put => "PUT",
65            Delete => "DELETE",
66            Head => "HEAD",
67            Trace => "TRACE",
68            Connect => "CONNECT",
69            Patch => "PATCH",
70            Other(ref inline) => inline.as_str(),
71        }
72    }
73}
74
75impl Default for Method {
76    fn default() -> Method {
77        Method::GET
78    }
79}
80
81impl fmt::Debug for Method {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        f.write_str(self.as_str())
84    }
85}
86
87impl fmt::Display for Method {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        f.write_str(self.as_str())
90    }
91}
92
93#[derive(Debug)]
94#[non_exhaustive]
95pub struct InvalidMethod {}
96
97#[derive(Clone, PartialEq, Eq, Hash)]
98enum Inner {
99    Options,
100    Get,
101    Post,
102    Put,
103    Delete,
104    Head,
105    Trace,
106    Connect,
107    Patch,
108    Other(String),
109}
110
111// From the RFC 9110 HTTP Semantics, section 9.1, the HTTP method is case-sensitive and can
112// contain the following characters:
113//
114// ```
115// method = token
116// token = 1*tchar
117// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
118//     "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
119// ```
120//
121// https://datatracker.ietf.org/doc/html/rfc9110#section-9.1
122//
123// Note that this definition means that any &[u8] that consists solely of valid
124// characters is also valid UTF-8 because the valid method characters are a
125// subset of the valid 1 byte UTF-8 encoding.
126#[rustfmt::skip]
127const METHOD_CHARS: [u8; 256] = [
128    //  0      1      2      3      4      5      6      7      8      9
129    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //   x
130    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  1x
131    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  2x
132    b'\0', b'\0', b'\0',  b'!', b'\0',  b'#',  b'$',  b'%',  b'&', b'\'', //  3x
133    b'\0', b'\0',  b'*',  b'+', b'\0',  b'-',  b'.', b'\0',  b'0',  b'1', //  4x
134     b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9', b'\0', b'\0', //  5x
135    b'\0', b'\0', b'\0', b'\0', b'\0',  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
136     b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
137     b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
138     b'Z', b'\0', b'\0', b'\0',  b'^',  b'_',  b'`',  b'a',  b'b',  b'c', //  9x
139     b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
140     b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
141     b'x',  b'y',  b'z', b'\0',  b'|', b'\0',  b'~', b'\0', b'\0', b'\0', // 12x
142    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x
143    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x
144    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x
145    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x
146    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x
147    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x
148    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x
149    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x
150    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x
151    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x
152    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x
153    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x
154    b'\0', b'\0', b'\0', b'\0', b'\0', b'\0'                              // 25x
155];