front_line_router/
method.rs

1/// Represents HTTP methods.
2///
3/// These methods are tokens that indicate the desired action to be performed
4/// on the identified resource.
5#[derive(Eq, PartialEq, Copy, Clone, Debug)]
6pub enum Method {
7    /// Represents the HTTP `GET` method.
8    ///
9    /// Used to retrieve data from a server.
10    Get,
11
12    /// Represents the HTTP `POST` method.
13    ///
14    /// Used to submit data to a server for processing.
15    Post,
16
17    /// Represents the HTTP `PUT` method.
18    ///
19    /// Used to update a resource or create a new resource if it does not exist.
20    Put,
21
22    /// Represents the HTTP `DELETE` method.
23    ///
24    /// Used to delete a resource.
25    Delete,
26
27    /// Represents the HTTP `HEAD` method.
28    ///
29    /// Used to retrieve metadata about a resource.
30    Head,
31
32    /// Represents the HTTP `OPTIONS` method.
33    ///
34    /// Used to describe the communication options for the target resource.
35    Options,
36
37    /// Represents the HTTP `CONNECT` method.
38    ///
39    /// Used to establish a network connection for a given URI.
40    Connect,
41
42    /// Represents the HTTP `TRACE` method.
43    ///
44    /// Used for diagnostic purposes.
45    Trace,
46
47    /// Represents the HTTP `PATCH` method.
48    ///
49    /// Used to apply partial modifications to a resource.
50    Patch,
51}
52
53impl Method {
54    /// Parse an HTTP request line to determine the method.
55    ///
56    /// This function will attempt to parse the provided request line slice and
57    /// return the identified HTTP method and the remaining part of the request line.
58    ///
59    /// # Arguments
60    ///
61    /// * `request_line` - A byte slice containing the request line to parse.
62    ///
63    /// # Returns
64    ///
65    /// Returns `Some((Method, &[u8]))` if a valid HTTP method is found. Otherwise,
66    /// returns `None`.
67    pub fn parse(request_line: &[u8]) -> Option<(Self, &[u8])> {
68        // method parsers are sorted by method length, and max length was calculated
69        // from "[METHOD_NAME] / HTTP/1.1".len()
70        if request_line.len() < 14 {
71            return None;
72        }
73        if &request_line[..4] == b"GET " {
74            return Some((Method::Get, &request_line[4..]));
75        }
76        if &request_line[..4] == b"PUT " {
77            return Some((Method::Put, &request_line[4..]));
78        }
79        if request_line.len() < 15 {
80            return None;
81        }
82        if &request_line[..5] == b"POST " {
83            return Some((Method::Post, &request_line[5..]));
84        }
85        if &request_line[..5] == b"HEAD " {
86            return Some((Method::Head, &request_line[5..]));
87        }
88        if request_line.len() < 16 {
89            return None;
90        }
91        if &request_line[..6] == b"TRACE " {
92            return Some((Method::Trace, &request_line[6..]));
93        }
94        if &request_line[..6] == b"PATCH " {
95            return Some((Method::Patch, &request_line[6..]));
96        }
97        if request_line.len() < 17 {
98            return None;
99        }
100        if &request_line[..7] == b"DELETE " {
101            return Some((Method::Delete, &request_line[7..]));
102        }
103        if request_line.len() < 18 {
104            return None;
105        }
106        if &request_line[..8] == b"OPTIONS " {
107            return Some((Method::Options, &request_line[8..]));
108        }
109        if &request_line[..8] == b"CONNECT " {
110            return Some((Method::Connect, &request_line[8..]));
111        }
112        None
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::Method;
119    use rstest::rstest;
120
121    #[rstest]
122    #[case(b"GET / HTTP/1.1", Some((Method::Get, b"/ HTTP/1.1".as_slice())))]
123    #[case(b"PUT / HTTP/1.1", Some((Method::Put, b"/ HTTP/1.1".as_slice())))]
124    #[case(b"POST / HTTP/1.1", Some((Method::Post, b"/ HTTP/1.1".as_slice())))]
125    #[case(b"HEAD / HTTP/1.1", Some((Method::Head, b"/ HTTP/1.1".as_slice())))]
126    #[case(b"TRACE / HTTP/1.1", Some((Method::Trace, b"/ HTTP/1.1".as_slice())))]
127    #[case(b"PATCH / HTTP/1.1", Some((Method::Patch, b"/ HTTP/1.1".as_slice())))]
128    #[case(b"DELETE / HTTP/1.1", Some((Method::Delete, b"/ HTTP/1.1".as_slice())))]
129    #[case(b"OPTIONS / HTTP/1.1", Some((Method::Options, b"/ HTTP/1.1".as_slice())))]
130    #[case(b"CONNECT / HTTP/1.1", Some((Method::Connect, b"/ HTTP/1.1".as_slice())))]
131    #[case(b"INVALIDMETHOD / HTTP/1.1", None)]
132    fn test_parse_method(#[case] request: &[u8], #[case] expected: Option<(Method, &[u8])>) {
133        assert_eq!(Method::parse(request), expected);
134    }
135
136    #[test]
137    fn test_remaining_request_line() {
138        let request = b"GET /foo/bar HTTP/1.1".as_slice();
139        assert_eq!(
140            Method::parse(request),
141            Some((Method::Get, b"/foo/bar HTTP/1.1".as_slice()))
142        );
143    }
144
145    #[rstest]
146    #[case(b"GET/ HTTP/1.1")]
147    #[case(b"PUT/ HTTP/1.1")]
148    #[case(b"POST/ HTTP/1.1")]
149    #[case(b"HEAD/ HTTP/1.1")]
150    fn test_malformed_request(#[case] request: &[u8]) {
151        assert_eq!(Method::parse(request), None);
152    }
153
154    #[rstest]
155    #[case(b"GE")]
156    #[case(b"POS")]
157    #[case(b"TRAC")]
158    #[case(b"DELET")]
159    #[case(b"OPTION")]
160    #[case(b"GET / HTTP/1.")]
161    #[case(b"POST / HTTP/1.")]
162    #[case(b"TRACE / HTTP/1.")]
163    #[case(b"DELETE / HTTP/1.")]
164    #[case(b"OPTIONS / HTTP/1.")]
165    fn test_short_request(#[case] request: &[u8]) {
166        assert_eq!(Method::parse(request), None);
167    }
168}