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<'a> PartialEq<&'a Method> for Method {
191 #[inline]
192 fn eq(&self, other: &&'a Method) -> bool {
193 self == *other
194 }
195}
196
197impl<'a> PartialEq<Method> for &'a Method {
198 #[inline]
199 fn eq(&self, other: &Method) -> bool {
200 *self == other
201 }
202}
203
204impl PartialEq<str> for Method {
205 #[inline]
206 fn eq(&self, other: &str) -> bool {
207 self.as_ref() == other
208 }
209}
210
211impl PartialEq<Method> for str {
212 #[inline]
213 fn eq(&self, other: &Method) -> bool {
214 self == other.as_ref()
215 }
216}
217
218impl<'a> PartialEq<&'a str> for Method {
219 #[inline]
220 fn eq(&self, other: &&'a str) -> bool {
221 self.as_ref() == *other
222 }
223}
224
225impl<'a> PartialEq<Method> for &'a str {
226 #[inline]
227 fn eq(&self, other: &Method) -> bool {
228 *self == other.as_ref()
229 }
230}
231
232impl fmt::Debug for Method {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 f.write_str(self.as_ref())
235 }
236}
237
238impl fmt::Display for Method {
239 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
240 fmt.write_str(self.as_ref())
241 }
242}
243
244impl Default for Method {
245 #[inline]
246 fn default() -> Method {
247 Method::GET
248 }
249}
250
251impl<'a> From<&'a Method> for Method {
252 #[inline]
253 fn from(t: &'a Method) -> Self {
254 t.clone()
255 }
256}
257
258impl<'a> TryFrom<&'a [u8]> for Method {
259 type Error = InvalidMethod;
260
261 #[inline]
262 fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
263 Method::from_bytes(t)
264 }
265}
266
267impl<'a> TryFrom<&'a str> for Method {
268 type Error = InvalidMethod;
269
270 #[inline]
271 fn try_from(t: &'a str) -> Result<Self, Self::Error> {
272 TryFrom::try_from(t.as_bytes())
273 }
274}
275
276impl FromStr for Method {
277 type Err = InvalidMethod;
278
279 #[inline]
280 fn from_str(t: &str) -> Result<Self, Self::Err> {
281 TryFrom::try_from(t)
282 }
283}
284
285impl InvalidMethod {
286 fn new() -> InvalidMethod {
287 InvalidMethod { _priv: () }
288 }
289}
290
291impl fmt::Debug for InvalidMethod {
292 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293 f.debug_struct("InvalidMethod")
294 .finish()
296 }
297}
298
299impl fmt::Display for InvalidMethod {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 f.write_str("invalid HTTP method")
302 }
303}
304
305impl Error for InvalidMethod {}
306
307mod extension {
308 use super::InvalidMethod;
309 use std::str;
310
311 #[derive(Clone, PartialEq, Eq, Hash)]
312 pub struct InlineExtension([u8; InlineExtension::MAX], u8);
314
315 #[derive(Clone, PartialEq, Eq, Hash)]
316 pub struct AllocatedExtension(Box<[u8]>);
318
319 impl InlineExtension {
320 pub const MAX: usize = 15;
322
323 pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
324 let mut data: [u8; InlineExtension::MAX] = Default::default();
325
326 write_checked(src, &mut data)?;
327
328 Ok(InlineExtension(data, src.len() as u8))
331 }
332
333 pub fn as_str(&self) -> &str {
334 let InlineExtension(ref data, len) = self;
335 unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
338 }
339 }
340
341 impl AllocatedExtension {
342 pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
343 let mut data: Vec<u8> = vec![0; src.len()];
344
345 write_checked(src, &mut data)?;
346
347 Ok(AllocatedExtension(data.into_boxed_slice()))
350 }
351
352 pub fn as_str(&self) -> &str {
353 unsafe { str::from_utf8_unchecked(&self.0) }
356 }
357 }
358
359 #[rustfmt::skip]
375 const METHOD_CHARS: [u8; 256] = [
376 b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\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' ];
404
405 fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
408 for (i, &b) in src.iter().enumerate() {
409 let b = METHOD_CHARS[b as usize];
410
411 if b == 0 {
412 return Err(InvalidMethod::new());
413 }
414
415 dst[i] = b;
416 }
417
418 Ok(())
419 }
420}
421
422#[cfg(test)]
423mod test {
424 use super::*;
425
426 #[test]
427 fn test_method_eq() {
428 assert_eq!(Method::GET, Method::GET);
429 assert_eq!(Method::GET, "GET");
430 assert_eq!(&Method::GET, "GET");
431
432 assert_eq!("GET", Method::GET);
433 assert_eq!("GET", &Method::GET);
434
435 assert_eq!(&Method::GET, Method::GET);
436 assert_eq!(Method::GET, &Method::GET);
437 }
438
439 #[test]
440 fn test_invalid_method() {
441 assert!(Method::from_str("").is_err());
442 assert!(Method::from_bytes(b"").is_err());
443 assert!(Method::from_bytes(&[0xC0]).is_err()); assert!(Method::from_bytes(&[0x10]).is_err()); }
446
447 #[test]
448 fn test_is_idempotent() {
449 assert!(Method::OPTIONS.is_idempotent());
450 assert!(Method::GET.is_idempotent());
451 assert!(Method::PUT.is_idempotent());
452 assert!(Method::DELETE.is_idempotent());
453 assert!(Method::HEAD.is_idempotent());
454 assert!(Method::TRACE.is_idempotent());
455
456 assert!(!Method::POST.is_idempotent());
457 assert!(!Method::CONNECT.is_idempotent());
458 assert!(!Method::PATCH.is_idempotent());
459 }
460
461 #[test]
462 fn test_extension_method() {
463 assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
464 assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
465
466 let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
467 assert_eq!(Method::from_str(long_method).unwrap(), long_method);
468
469 let longest_inline_method = [b'A'; InlineExtension::MAX];
470 assert_eq!(
471 Method::from_bytes(&longest_inline_method).unwrap(),
472 Method(ExtensionInline(
473 InlineExtension::new(&longest_inline_method).unwrap()
474 ))
475 );
476 let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1];
477 assert_eq!(
478 Method::from_bytes(&shortest_allocated_method).unwrap(),
479 Method(ExtensionAllocated(
480 AllocatedExtension::new(&shortest_allocated_method).unwrap()
481 ))
482 );
483 }
484
485 #[test]
486 fn test_extension_method_chars() {
487 const VALID_METHOD_CHARS: &str =
488 "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
489
490 for c in VALID_METHOD_CHARS.chars() {
491 let c = c.to_string();
492
493 assert_eq!(
494 Method::from_str(&c).unwrap(),
495 c.as_str(),
496 "testing {c} is a valid method character"
497 );
498 }
499 }
500}