1#![allow(
23 unreachable_pub,
24 clippy::use_self,
25 clippy::needless_lifetimes,
26 clippy::enum_glob_use,
27 clippy::collapsible_if,
28 clippy::assertions_on_result_states
29)]
30
31use self::Inner::*;
32use self::extension::{AllocatedExtension, InlineExtension};
33
34use std::convert::TryFrom;
35use std::error::Error;
36use std::str::FromStr;
37use std::{fmt, str};
38
39#[derive(Clone, PartialEq, Eq, Hash)]
60pub struct Method(Inner);
61
62pub struct InvalidMethod {
64 _priv: (),
65}
66
67#[derive(Clone, PartialEq, Eq, Hash)]
68enum Inner {
69 Options,
70 Get,
71 Post,
72 Put,
73 Delete,
74 Head,
75 Trace,
76 Connect,
77 Patch,
78 Query,
79 ExtensionInline(InlineExtension),
81 ExtensionAllocated(AllocatedExtension),
83}
84
85impl Method {
86 pub const GET: Method = Method(Get);
88
89 pub const POST: Method = Method(Post);
91
92 pub const PUT: Method = Method(Put);
94
95 pub const DELETE: Method = Method(Delete);
97
98 pub const HEAD: Method = Method(Head);
100
101 pub const OPTIONS: Method = Method(Options);
103
104 pub const CONNECT: Method = Method(Connect);
106
107 pub const PATCH: Method = Method(Patch);
109
110 pub const TRACE: Method = Method(Trace);
112
113 pub const QUERY: Method = Method(Query);
118
119 pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
121 match src.len() {
122 0 => Err(InvalidMethod::new()),
123 3 => match src {
124 b"GET" => Ok(Method(Get)),
125 b"PUT" => Ok(Method(Put)),
126 _ => Method::extension_inline(src),
127 },
128 4 => match src {
129 b"POST" => Ok(Method(Post)),
130 b"HEAD" => Ok(Method(Head)),
131 _ => Method::extension_inline(src),
132 },
133 5 => match src {
134 b"PATCH" => Ok(Method(Patch)),
135 b"TRACE" => Ok(Method(Trace)),
136 b"QUERY" => Ok(Method(Query)),
137 _ => Method::extension_inline(src),
138 },
139 6 => match src {
140 b"DELETE" => Ok(Method(Delete)),
141 _ => Method::extension_inline(src),
142 },
143 7 => match src {
144 b"OPTIONS" => Ok(Method(Options)),
145 b"CONNECT" => Ok(Method(Connect)),
146 _ => Method::extension_inline(src),
147 },
148 _ => {
149 if src.len() <= InlineExtension::MAX {
150 Method::extension_inline(src)
151 } else {
152 let allocated = AllocatedExtension::new(src)?;
153
154 Ok(Method(ExtensionAllocated(allocated)))
155 }
156 }
157 }
158 }
159
160 fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
161 let inline = InlineExtension::new(src)?;
162
163 Ok(Method(ExtensionInline(inline)))
164 }
165
166 pub fn is_safe(&self) -> bool {
172 matches!(self.0, Get | Head | Options | Trace | Query)
175 }
176
177 pub fn is_idempotent(&self) -> bool {
183 match self.0 {
184 Put | Delete => true,
185 _ => self.is_safe(),
186 }
187 }
188
189 #[inline]
191 pub fn as_str(&self) -> &str {
192 match self.0 {
193 Options => "OPTIONS",
194 Get => "GET",
195 Post => "POST",
196 Put => "PUT",
197 Delete => "DELETE",
198 Head => "HEAD",
199 Trace => "TRACE",
200 Connect => "CONNECT",
201 Patch => "PATCH",
202 Query => "QUERY",
203 ExtensionInline(ref inline) => inline.as_str(),
204 ExtensionAllocated(ref allocated) => allocated.as_str(),
205 }
206 }
207}
208
209impl AsRef<str> for Method {
210 #[inline]
211 fn as_ref(&self) -> &str {
212 self.as_str()
213 }
214}
215
216impl Ord for Method {
217 #[inline]
218 fn cmp(&self, other: &Method) -> std::cmp::Ordering {
219 self.as_ref().cmp(other.as_ref())
220 }
221}
222
223impl PartialOrd for Method {
224 #[inline]
225 fn partial_cmp(&self, other: &Method) -> Option<std::cmp::Ordering> {
226 Some(self.cmp(other))
227 }
228}
229
230impl PartialEq<&Method> for Method {
231 #[inline]
232 fn eq(&self, other: &&Method) -> bool {
233 self == *other
234 }
235}
236
237impl PartialEq<Method> for &Method {
238 #[inline]
239 fn eq(&self, other: &Method) -> bool {
240 *self == other
241 }
242}
243
244impl PartialEq<str> for Method {
245 #[inline]
246 fn eq(&self, other: &str) -> bool {
247 self.as_ref() == other
248 }
249}
250
251impl PartialEq<Method> for str {
252 #[inline]
253 fn eq(&self, other: &Method) -> bool {
254 self == other.as_ref()
255 }
256}
257
258impl PartialEq<&str> for Method {
259 #[inline]
260 fn eq(&self, other: &&str) -> bool {
261 self.as_ref() == *other
262 }
263}
264
265impl PartialEq<Method> for &str {
266 #[inline]
267 fn eq(&self, other: &Method) -> bool {
268 *self == other.as_ref()
269 }
270}
271
272impl fmt::Debug for Method {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 f.write_str(self.as_ref())
275 }
276}
277
278impl fmt::Display for Method {
279 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
280 fmt.write_str(self.as_ref())
281 }
282}
283
284impl Default for Method {
285 #[inline]
286 fn default() -> Method {
287 Method::GET
288 }
289}
290
291impl From<&Method> for Method {
292 #[inline]
293 fn from(t: &Method) -> Self {
294 t.clone()
295 }
296}
297
298impl TryFrom<&[u8]> for Method {
299 type Error = InvalidMethod;
300
301 #[inline]
302 fn try_from(t: &[u8]) -> Result<Self, Self::Error> {
303 Method::from_bytes(t)
304 }
305}
306
307impl TryFrom<&str> for Method {
308 type Error = InvalidMethod;
309
310 #[inline]
311 fn try_from(t: &str) -> Result<Self, Self::Error> {
312 TryFrom::try_from(t.as_bytes())
313 }
314}
315
316impl FromStr for Method {
317 type Err = InvalidMethod;
318
319 #[inline]
320 fn from_str(t: &str) -> Result<Self, Self::Err> {
321 TryFrom::try_from(t)
322 }
323}
324
325impl InvalidMethod {
326 fn new() -> InvalidMethod {
327 InvalidMethod { _priv: () }
328 }
329}
330
331impl fmt::Debug for InvalidMethod {
332 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
333 f.debug_struct("InvalidMethod")
334 .finish()
336 }
337}
338
339impl fmt::Display for InvalidMethod {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 f.write_str("invalid HTTP method")
342 }
343}
344
345impl Error for InvalidMethod {}
346
347mod extension {
348 use super::InvalidMethod;
349 use std::str;
350
351 #[derive(Clone, PartialEq, Eq, Hash)]
352 pub struct InlineExtension([u8; InlineExtension::MAX], u8);
354
355 #[derive(Clone, PartialEq, Eq, Hash)]
356 pub struct AllocatedExtension(Box<[u8]>);
358
359 impl InlineExtension {
360 pub const MAX: usize = 15;
362
363 pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
364 let mut data: [u8; InlineExtension::MAX] = Default::default();
365
366 write_checked(src, &mut data)?;
367
368 Ok(InlineExtension(data, src.len() as u8))
371 }
372
373 pub fn as_str(&self) -> &str {
374 let InlineExtension(data, len) = self;
375 unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
378 }
379 }
380
381 impl AllocatedExtension {
382 pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
383 let mut data: Vec<u8> = vec![0; src.len()];
384
385 write_checked(src, &mut data)?;
386
387 Ok(AllocatedExtension(data.into_boxed_slice()))
390 }
391
392 pub fn as_str(&self) -> &str {
393 unsafe { str::from_utf8_unchecked(&self.0) }
396 }
397 }
398
399 #[rustfmt::skip]
415 const METHOD_CHARS: [u8; 256] = [
416 b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'!', b'\0', b'#', b'$', b'%', b'&', b'\'', b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'\0', b'\0', b'\0', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'\0', b'|', b'\0', b'~', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0' ];
444
445 fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
448 for (i, &b) in src.iter().enumerate() {
449 let b = METHOD_CHARS[b as usize];
450
451 if b == 0 {
452 return Err(InvalidMethod::new());
453 }
454
455 dst[i] = b;
456 }
457
458 Ok(())
459 }
460}
461
462#[cfg(test)]
463mod test {
464 use super::*;
465
466 #[test]
467 fn test_method_eq() {
468 assert_eq!(Method::GET, Method::GET);
469 assert_eq!(Method::GET, "GET");
470 assert_eq!(&Method::GET, "GET");
471
472 assert_eq!("GET", Method::GET);
473 assert_eq!("GET", &Method::GET);
474
475 assert_eq!(&Method::GET, Method::GET);
476 assert_eq!(Method::GET, &Method::GET);
477 }
478
479 #[test]
480 fn test_invalid_method() {
481 assert!(Method::from_str("").is_err());
482 assert!(Method::from_bytes(b"").is_err());
483 assert!(Method::from_bytes(&[0xC0]).is_err()); assert!(Method::from_bytes(&[0x10]).is_err()); }
486
487 #[test]
488 fn test_is_idempotent() {
489 assert!(Method::OPTIONS.is_idempotent());
490 assert!(Method::GET.is_idempotent());
491 assert!(Method::PUT.is_idempotent());
492 assert!(Method::DELETE.is_idempotent());
493 assert!(Method::HEAD.is_idempotent());
494 assert!(Method::TRACE.is_idempotent());
495 assert!(Method::QUERY.is_idempotent());
496
497 assert!(!Method::POST.is_idempotent());
498 assert!(!Method::CONNECT.is_idempotent());
499 assert!(!Method::PATCH.is_idempotent());
500 }
501
502 #[test]
503 fn test_is_safe() {
504 assert!(Method::QUERY.is_safe());
506
507 assert!(Method::GET.is_safe());
508 assert!(Method::HEAD.is_safe());
509 assert!(Method::OPTIONS.is_safe());
510 assert!(Method::TRACE.is_safe());
511
512 assert!(!Method::POST.is_safe());
513 assert!(!Method::PUT.is_safe());
514 assert!(!Method::DELETE.is_safe());
515 assert!(!Method::CONNECT.is_safe());
516 assert!(!Method::PATCH.is_safe());
517 }
518
519 #[test]
520 fn test_query_method() {
521 assert_eq!(Method::from_bytes(b"QUERY").unwrap(), Method::QUERY);
523 assert_eq!(Method::from_str("QUERY").unwrap(), Method::QUERY);
524 assert_eq!(Method::QUERY.as_str(), "QUERY");
525 assert_eq!(Method::QUERY, "QUERY");
526 }
527
528 #[test]
529 fn test_extension_method() {
530 assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
531 assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
532
533 let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
534 assert_eq!(Method::from_str(long_method).unwrap(), long_method);
535
536 let longest_inline_method = [b'A'; InlineExtension::MAX];
537 assert_eq!(
538 Method::from_bytes(&longest_inline_method).unwrap(),
539 Method(ExtensionInline(
540 InlineExtension::new(&longest_inline_method).unwrap()
541 ))
542 );
543 let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1];
544 assert_eq!(
545 Method::from_bytes(&shortest_allocated_method).unwrap(),
546 Method(ExtensionAllocated(
547 AllocatedExtension::new(&shortest_allocated_method).unwrap()
548 ))
549 );
550 }
551
552 #[test]
553 fn test_extension_method_chars() {
554 const VALID_METHOD_CHARS: &str =
555 "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
556
557 for c in VALID_METHOD_CHARS.chars() {
558 let c = c.to_string();
559
560 assert_eq!(
561 Method::from_str(&c).unwrap(),
562 c.as_str(),
563 "testing {c} is a valid method character"
564 );
565 }
566 }
567}