1use self::extension::{AllocatedExtension, InlineExtension};
19use self::Inner::*;
20
21use std::convert::TryFrom;
22use std::error::Error;
23use std::str::FromStr;
24use std::{fmt, str};
25
26#[derive(Clone, PartialEq, Eq, Hash)]
45pub struct Method(Inner);
46
47pub struct InvalidMethod {
49 _priv: (),
50}
51
52#[derive(Clone, PartialEq, Eq, Hash)]
53enum Inner {
54 Options,
55 Get,
56 Post,
57 Put,
58 Delete,
59 Head,
60 Trace,
61 Connect,
62 Patch,
63 ExtensionInline(InlineExtension),
65 ExtensionAllocated(AllocatedExtension),
67}
68
69impl Method {
70 pub const GET: Method = Method(Get);
72
73 pub const POST: Method = Method(Post);
75
76 pub const PUT: Method = Method(Put);
78
79 pub const DELETE: Method = Method(Delete);
81
82 pub const HEAD: Method = Method(Head);
84
85 pub const OPTIONS: Method = Method(Options);
87
88 pub const CONNECT: Method = Method(Connect);
90
91 pub const PATCH: Method = Method(Patch);
93
94 pub const TRACE: Method = Method(Trace);
96
97 pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
99 match src.len() {
100 0 => Err(InvalidMethod::new()),
101 3 => match src {
102 b"GET" => Ok(Method(Get)),
103 b"PUT" => Ok(Method(Put)),
104 _ => Method::extension_inline(src),
105 },
106 4 => match src {
107 b"POST" => Ok(Method(Post)),
108 b"HEAD" => Ok(Method(Head)),
109 _ => Method::extension_inline(src),
110 },
111 5 => match src {
112 b"PATCH" => Ok(Method(Patch)),
113 b"TRACE" => Ok(Method(Trace)),
114 _ => Method::extension_inline(src),
115 },
116 6 => match src {
117 b"DELETE" => Ok(Method(Delete)),
118 _ => Method::extension_inline(src),
119 },
120 7 => match src {
121 b"OPTIONS" => Ok(Method(Options)),
122 b"CONNECT" => Ok(Method(Connect)),
123 _ => Method::extension_inline(src),
124 },
125 _ => {
126 if src.len() <= InlineExtension::MAX {
127 Method::extension_inline(src)
128 } else {
129 let allocated = AllocatedExtension::new(src)?;
130
131 Ok(Method(ExtensionAllocated(allocated)))
132 }
133 }
134 }
135 }
136
137 fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
138 let inline = InlineExtension::new(src)?;
139
140 Ok(Method(ExtensionInline(inline)))
141 }
142
143 pub fn is_safe(&self) -> bool {
149 matches!(self.0, Get | Head | Options | Trace)
150 }
151
152 pub fn is_idempotent(&self) -> bool {
158 match self.0 {
159 Put | Delete => true,
160 _ => self.is_safe(),
161 }
162 }
163
164 #[inline]
166 pub fn as_str(&self) -> &str {
167 match self.0 {
168 Options => "OPTIONS",
169 Get => "GET",
170 Post => "POST",
171 Put => "PUT",
172 Delete => "DELETE",
173 Head => "HEAD",
174 Trace => "TRACE",
175 Connect => "CONNECT",
176 Patch => "PATCH",
177 ExtensionInline(ref inline) => inline.as_str(),
178 ExtensionAllocated(ref allocated) => allocated.as_str(),
179 }
180 }
181}
182
183impl AsRef<str> for Method {
184 #[inline]
185 fn as_ref(&self) -> &str {
186 self.as_str()
187 }
188}
189
190impl Ord for Method {
191 #[inline]
192 fn cmp(&self, other: &Method) -> std::cmp::Ordering {
193 self.as_ref().cmp(other.as_ref())
194 }
195}
196
197impl PartialOrd for Method {
198 #[inline]
199 fn partial_cmp(&self, other: &Method) -> Option<std::cmp::Ordering> {
200 Some(self.cmp(other))
201 }
202}
203
204impl PartialEq<&Method> for Method {
205 #[inline]
206 fn eq(&self, other: &&Method) -> bool {
207 self == *other
208 }
209}
210
211impl PartialEq<Method> for &Method {
212 #[inline]
213 fn eq(&self, other: &Method) -> bool {
214 *self == other
215 }
216}
217
218impl PartialEq<str> for Method {
219 #[inline]
220 fn eq(&self, other: &str) -> bool {
221 self.as_ref() == other
222 }
223}
224
225impl PartialEq<Method> for str {
226 #[inline]
227 fn eq(&self, other: &Method) -> bool {
228 self == other.as_ref()
229 }
230}
231
232impl PartialEq<&str> for Method {
233 #[inline]
234 fn eq(&self, other: &&str) -> bool {
235 self.as_ref() == *other
236 }
237}
238
239impl PartialEq<Method> for &str {
240 #[inline]
241 fn eq(&self, other: &Method) -> bool {
242 *self == other.as_ref()
243 }
244}
245
246impl fmt::Debug for Method {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.write_str(self.as_ref())
249 }
250}
251
252impl fmt::Display for Method {
253 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
254 fmt.write_str(self.as_ref())
255 }
256}
257
258impl Default for Method {
259 #[inline]
260 fn default() -> Method {
261 Method::GET
262 }
263}
264
265impl From<&Method> for Method {
266 #[inline]
267 fn from(t: &Method) -> Self {
268 t.clone()
269 }
270}
271
272impl TryFrom<&[u8]> for Method {
273 type Error = InvalidMethod;
274
275 #[inline]
276 fn try_from(t: &[u8]) -> Result<Self, Self::Error> {
277 Method::from_bytes(t)
278 }
279}
280
281impl TryFrom<&str> for Method {
282 type Error = InvalidMethod;
283
284 #[inline]
285 fn try_from(t: &str) -> Result<Self, Self::Error> {
286 TryFrom::try_from(t.as_bytes())
287 }
288}
289
290impl FromStr for Method {
291 type Err = InvalidMethod;
292
293 #[inline]
294 fn from_str(t: &str) -> Result<Self, Self::Err> {
295 TryFrom::try_from(t)
296 }
297}
298
299impl InvalidMethod {
300 fn new() -> InvalidMethod {
301 InvalidMethod { _priv: () }
302 }
303}
304
305impl fmt::Debug for InvalidMethod {
306 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307 f.debug_struct("InvalidMethod")
308 .finish()
310 }
311}
312
313impl fmt::Display for InvalidMethod {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 f.write_str("invalid HTTP method")
316 }
317}
318
319impl Error for InvalidMethod {}
320
321mod extension {
322 use super::InvalidMethod;
323 use std::str;
324
325 #[derive(Clone, PartialEq, Eq, Hash)]
326 pub struct InlineExtension([u8; InlineExtension::MAX], u8);
328
329 #[derive(Clone, PartialEq, Eq, Hash)]
330 pub struct AllocatedExtension(Box<[u8]>);
332
333 impl InlineExtension {
334 pub const MAX: usize = 15;
336
337 pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
338 let mut data: [u8; InlineExtension::MAX] = Default::default();
339
340 write_checked(src, &mut data)?;
341
342 Ok(InlineExtension(data, src.len() as u8))
345 }
346
347 pub fn as_str(&self) -> &str {
348 let InlineExtension(ref data, len) = self;
349 unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
352 }
353 }
354
355 impl AllocatedExtension {
356 pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
357 let mut data: Vec<u8> = vec![0; src.len()];
358
359 write_checked(src, &mut data)?;
360
361 Ok(AllocatedExtension(data.into_boxed_slice()))
364 }
365
366 pub fn as_str(&self) -> &str {
367 unsafe { str::from_utf8_unchecked(&self.0) }
370 }
371 }
372
373 #[rustfmt::skip]
389 const METHOD_CHARS: [u8; 256] = [
390 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' ];
418
419 fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
422 for (i, &b) in src.iter().enumerate() {
423 let b = METHOD_CHARS[b as usize];
424
425 if b == 0 {
426 return Err(InvalidMethod::new());
427 }
428
429 dst[i] = b;
430 }
431
432 Ok(())
433 }
434}
435
436#[cfg(test)]
437mod test {
438 use super::*;
439
440 #[test]
441 fn test_method_eq() {
442 assert_eq!(Method::GET, Method::GET);
443 assert_eq!(Method::GET, "GET");
444 assert_eq!(&Method::GET, "GET");
445
446 assert_eq!("GET", Method::GET);
447 assert_eq!("GET", &Method::GET);
448
449 assert_eq!(&Method::GET, Method::GET);
450 assert_eq!(Method::GET, &Method::GET);
451 }
452
453 #[test]
454 fn test_invalid_method() {
455 assert!(Method::from_str("").is_err());
456 assert!(Method::from_bytes(b"").is_err());
457 assert!(Method::from_bytes(&[0xC0]).is_err()); assert!(Method::from_bytes(&[0x10]).is_err()); }
460
461 #[test]
462 fn test_is_idempotent() {
463 assert!(Method::OPTIONS.is_idempotent());
464 assert!(Method::GET.is_idempotent());
465 assert!(Method::PUT.is_idempotent());
466 assert!(Method::DELETE.is_idempotent());
467 assert!(Method::HEAD.is_idempotent());
468 assert!(Method::TRACE.is_idempotent());
469
470 assert!(!Method::POST.is_idempotent());
471 assert!(!Method::CONNECT.is_idempotent());
472 assert!(!Method::PATCH.is_idempotent());
473 }
474
475 #[test]
476 fn test_extension_method() {
477 assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
478 assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
479
480 let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
481 assert_eq!(Method::from_str(long_method).unwrap(), long_method);
482
483 let longest_inline_method = [b'A'; InlineExtension::MAX];
484 assert_eq!(
485 Method::from_bytes(&longest_inline_method).unwrap(),
486 Method(ExtensionInline(
487 InlineExtension::new(&longest_inline_method).unwrap()
488 ))
489 );
490 let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1];
491 assert_eq!(
492 Method::from_bytes(&shortest_allocated_method).unwrap(),
493 Method(ExtensionAllocated(
494 AllocatedExtension::new(&shortest_allocated_method).unwrap()
495 ))
496 );
497 }
498
499 #[test]
500 fn test_extension_method_chars() {
501 const VALID_METHOD_CHARS: &str =
502 "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
503
504 for c in VALID_METHOD_CHARS.chars() {
505 let c = c.to_string();
506
507 assert_eq!(
508 Method::from_str(&c).unwrap(),
509 c.as_str(),
510 "testing {c} is a valid method character"
511 );
512 }
513 }
514}