1use crate::ByteStr;
4use std::fmt;
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub enum Method {
13 Get,
15 Head,
17 Post,
19 Put,
21 Delete,
23 Connect,
25 Options,
27 Trace,
29 Patch,
31 Query,
33 Other(ByteStr),
35}
36
37impl Method {
38 #[inline]
48 pub fn from_bytes(token: &[u8]) -> Option<Method> {
49 match token.len() {
50 3 => match token {
51 b"GET" => Some(Method::Get),
52 b"PUT" => Some(Method::Put),
53 _ => None,
54 },
55 4 => match token {
56 b"HEAD" => Some(Method::Head),
57 b"POST" => Some(Method::Post),
58 _ => None,
59 },
60 5 => match token {
61 b"PATCH" => Some(Method::Patch),
62 b"TRACE" => Some(Method::Trace),
63 b"QUERY" => Some(Method::Query),
64 _ => None,
65 },
66 6 => match token {
67 b"DELETE" => Some(Method::Delete),
68 _ => None,
69 },
70 7 => match token {
71 b"CONNECT" => Some(Method::Connect),
72 b"OPTIONS" => Some(Method::Options),
73 _ => None,
74 },
75 _ => None,
76 }
77 }
78
79 #[inline]
81 pub fn as_str(&self) -> &str {
82 match self {
83 Method::Get => "GET",
84 Method::Head => "HEAD",
85 Method::Post => "POST",
86 Method::Put => "PUT",
87 Method::Delete => "DELETE",
88 Method::Connect => "CONNECT",
89 Method::Options => "OPTIONS",
90 Method::Trace => "TRACE",
91 Method::Patch => "PATCH",
92 Method::Query => "QUERY",
93 Method::Other(s) => s.as_str(),
94 }
95 }
96
97 #[inline]
101 pub fn is_safe(&self) -> bool {
102 matches!(
103 self,
104 Method::Get | Method::Head | Method::Options | Method::Trace | Method::Query
105 )
106 }
107
108 #[inline]
113 pub fn expects_response_body(&self) -> bool {
114 !matches!(self, Method::Head)
115 }
116}
117
118impl From<&str> for Method {
119 #[inline]
126 fn from(token: &str) -> Self {
127 Method::from_bytes(token.as_bytes()).unwrap_or_else(|| Method::Other(ByteStr::from(token)))
128 }
129}
130
131impl From<String> for Method {
132 #[inline]
133 fn from(token: String) -> Self {
134 Method::from_bytes(token.as_bytes()).unwrap_or_else(|| Method::Other(ByteStr::from(token)))
135 }
136}
137
138impl PartialEq<str> for Method {
139 #[inline]
140 fn eq(&self, other: &str) -> bool {
141 self.as_str() == other
142 }
143}
144
145impl PartialEq<&str> for Method {
146 #[inline]
147 fn eq(&self, other: &&str) -> bool {
148 self.as_str() == *other
149 }
150}
151
152impl fmt::Display for Method {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 f.write_str(self.as_str())
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
160pub enum Version {
161 Http10,
163 Http11,
165}
166
167impl Version {
168 #[inline]
173 pub fn from_httparse(minor: u8) -> Option<Version> {
174 match minor {
175 0 => Some(Version::Http10),
176 1 => Some(Version::Http11),
177 _ => None,
178 }
179 }
180
181 #[inline]
183 pub fn as_bytes(&self) -> &'static [u8] {
184 match self {
185 Version::Http10 => b"HTTP/1.0",
186 Version::Http11 => b"HTTP/1.1",
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn well_known_methods_parse() {
197 assert_eq!(Method::from_bytes(b"GET"), Some(Method::Get));
198 assert_eq!(Method::from_bytes(b"HEAD"), Some(Method::Head));
199 assert_eq!(Method::from_bytes(b"POST"), Some(Method::Post));
200 assert_eq!(Method::from_bytes(b"PUT"), Some(Method::Put));
201 assert_eq!(Method::from_bytes(b"DELETE"), Some(Method::Delete));
202 assert_eq!(Method::from_bytes(b"CONNECT"), Some(Method::Connect));
203 assert_eq!(Method::from_bytes(b"OPTIONS"), Some(Method::Options));
204 assert_eq!(Method::from_bytes(b"TRACE"), Some(Method::Trace));
205 assert_eq!(Method::from_bytes(b"PATCH"), Some(Method::Patch));
206 assert_eq!(Method::from_bytes(b"QUERY"), Some(Method::Query));
207 }
208
209 #[test]
211 fn methods_are_case_sensitive() {
212 assert_eq!(Method::from_bytes(b"get"), None);
213 assert_eq!(Method::from_bytes(b"Get"), None);
214 }
215
216 #[test]
217 fn unknown_method_is_not_well_known() {
218 assert_eq!(Method::from_bytes(b"PROPFIND"), None);
219 assert_eq!(Method::from_bytes(b""), None);
220 assert_eq!(Method::from_bytes(b"GETX"), None);
221 assert_eq!(Method::from_bytes(b"GE"), None);
222 }
223
224 #[test]
225 fn as_str_round_trips() {
226 for m in [
227 Method::Get,
228 Method::Head,
229 Method::Post,
230 Method::Put,
231 Method::Delete,
232 Method::Connect,
233 Method::Options,
234 Method::Trace,
235 Method::Patch,
236 Method::Query,
237 ] {
238 assert_eq!(Method::from_bytes(m.as_str().as_bytes()), Some(m.clone()));
239 }
240 assert_eq!(
241 Method::Other(ByteStr::from_static("PROPFIND")).as_str(),
242 "PROPFIND"
243 );
244 }
245
246 #[test]
247 fn head_expects_no_response_body() {
248 assert!(!Method::Head.expects_response_body());
249 assert!(Method::Get.expects_response_body());
250 }
251
252 #[test]
253 fn safe_methods_classified() {
254 assert!(Method::Get.is_safe());
255 assert!(Method::Head.is_safe());
256 assert!(Method::Options.is_safe());
257 assert!(Method::Trace.is_safe());
258 assert!(Method::Query.is_safe());
259 assert!(!Method::Post.is_safe());
260 assert!(!Method::Delete.is_safe());
261 assert!(!Method::Other(ByteStr::from_static("PROPFIND")).is_safe());
262 }
263
264 #[test]
265 fn versions_map_from_httparse() {
266 assert_eq!(Version::from_httparse(0), Some(Version::Http10));
267 assert_eq!(Version::from_httparse(1), Some(Version::Http11));
268 assert_eq!(Version::from_httparse(2), None);
269 assert_eq!(Version::Http11.as_bytes(), b"HTTP/1.1");
270 assert_eq!(Version::Http10.as_bytes(), b"HTTP/1.0");
271 }
272
273 #[test]
274 fn from_str_maps_well_known_and_preserves_unknown_case() {
275 assert_eq!(Method::from("GET"), Method::Get);
276 assert_eq!(Method::from("QUERY"), Method::Query);
277 assert_eq!(
280 Method::from("get"),
281 Method::Other(ByteStr::from_static("get"))
282 );
283 assert_eq!(
284 Method::from("PURGE".to_string()),
285 Method::Other(ByteStr::from_static("PURGE"))
286 );
287 }
288
289 #[test]
290 fn compares_against_str_and_displays_as_its_token() {
291 assert!(Method::Delete == "DELETE");
292 assert!(Method::Delete != "GET");
293 let unknown = Method::from("PURGE");
297 assert!(unknown == "PURGE");
298 assert_eq!(format!("{}", Method::Patch), "PATCH");
299 assert_eq!(format!("{unknown}"), "PURGE");
300 }
301}