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
216use rama_utils::macros::serde_str::impl_serde_str;
220
221impl_serde_str!(as_str Method);
222
223impl Ord for Method {
224 #[inline]
225 fn cmp(&self, other: &Method) -> std::cmp::Ordering {
226 self.as_ref().cmp(other.as_ref())
227 }
228}
229
230impl PartialOrd for Method {
231 #[inline]
232 fn partial_cmp(&self, other: &Method) -> Option<std::cmp::Ordering> {
233 Some(self.cmp(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<Method> for &Method {
245 #[inline]
246 fn eq(&self, other: &Method) -> bool {
247 *self == other
248 }
249}
250
251impl PartialEq<str> for Method {
252 #[inline]
253 fn eq(&self, other: &str) -> bool {
254 self.as_ref() == other
255 }
256}
257
258impl PartialEq<Method> for str {
259 #[inline]
260 fn eq(&self, other: &Method) -> bool {
261 self == other.as_ref()
262 }
263}
264
265impl PartialEq<&str> for Method {
266 #[inline]
267 fn eq(&self, other: &&str) -> bool {
268 self.as_ref() == *other
269 }
270}
271
272impl PartialEq<Method> for &str {
273 #[inline]
274 fn eq(&self, other: &Method) -> bool {
275 *self == other.as_ref()
276 }
277}
278
279impl fmt::Debug for Method {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 f.write_str(self.as_ref())
282 }
283}
284
285impl fmt::Display for Method {
286 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
287 fmt.write_str(self.as_ref())
288 }
289}
290
291impl Default for Method {
292 #[inline]
293 fn default() -> Method {
294 Method::GET
295 }
296}
297
298impl From<&Method> for Method {
299 #[inline]
300 fn from(t: &Method) -> Self {
301 t.clone()
302 }
303}
304
305impl TryFrom<&[u8]> for Method {
306 type Error = InvalidMethod;
307
308 #[inline]
309 fn try_from(t: &[u8]) -> Result<Self, Self::Error> {
310 Method::from_bytes(t)
311 }
312}
313
314impl TryFrom<&str> for Method {
315 type Error = InvalidMethod;
316
317 #[inline]
318 fn try_from(t: &str) -> Result<Self, Self::Error> {
319 TryFrom::try_from(t.as_bytes())
320 }
321}
322
323impl FromStr for Method {
324 type Err = InvalidMethod;
325
326 #[inline]
327 fn from_str(t: &str) -> Result<Self, Self::Err> {
328 TryFrom::try_from(t)
329 }
330}
331
332impl InvalidMethod {
333 fn new() -> InvalidMethod {
334 InvalidMethod { _priv: () }
335 }
336}
337
338impl fmt::Debug for InvalidMethod {
339 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
340 f.debug_struct("InvalidMethod")
341 .finish()
343 }
344}
345
346impl fmt::Display for InvalidMethod {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 f.write_str("invalid HTTP method")
349 }
350}
351
352impl Error for InvalidMethod {}
353
354mod extension {
355 use super::InvalidMethod;
356 use std::str;
357
358 #[derive(Clone, PartialEq, Eq, Hash)]
359 pub struct InlineExtension([u8; InlineExtension::MAX], u8);
361
362 #[derive(Clone, PartialEq, Eq, Hash)]
363 pub struct AllocatedExtension(Box<[u8]>);
365
366 impl InlineExtension {
367 pub const MAX: usize = 15;
369
370 pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
371 let mut data: [u8; InlineExtension::MAX] = Default::default();
372
373 write_checked(src, &mut data)?;
374
375 Ok(InlineExtension(data, src.len() as u8))
378 }
379
380 pub fn as_str(&self) -> &str {
381 let InlineExtension(data, len) = self;
382 unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
385 }
386 }
387
388 impl AllocatedExtension {
389 pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
390 let mut data: Vec<u8> = vec![0; src.len()];
391
392 write_checked(src, &mut data)?;
393
394 Ok(AllocatedExtension(data.into_boxed_slice()))
397 }
398
399 pub fn as_str(&self) -> &str {
400 unsafe { str::from_utf8_unchecked(&self.0) }
403 }
404 }
405
406 #[rustfmt::skip]
422 const METHOD_CHARS: [u8; 256] = [
423 b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\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' ];
451
452 fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
455 for (i, &b) in src.iter().enumerate() {
456 let b = METHOD_CHARS[b as usize];
457
458 if b == 0 {
459 return Err(InvalidMethod::new());
460 }
461
462 dst[i] = b;
463 }
464
465 Ok(())
466 }
467}
468
469#[cfg(test)]
470mod test {
471 use super::*;
472
473 #[test]
474 fn test_method_eq() {
475 assert_eq!(Method::GET, Method::GET);
476 assert_eq!(Method::GET, "GET");
477 assert_eq!(&Method::GET, "GET");
478
479 assert_eq!("GET", Method::GET);
480 assert_eq!("GET", &Method::GET);
481
482 assert_eq!(&Method::GET, Method::GET);
483 assert_eq!(Method::GET, &Method::GET);
484 }
485
486 #[test]
487 fn test_invalid_method() {
488 assert!(Method::from_str("").is_err());
489 assert!(Method::from_bytes(b"").is_err());
490 assert!(Method::from_bytes(&[0xC0]).is_err()); assert!(Method::from_bytes(&[0x10]).is_err()); }
493
494 #[test]
495 fn test_is_idempotent() {
496 assert!(Method::OPTIONS.is_idempotent());
497 assert!(Method::GET.is_idempotent());
498 assert!(Method::PUT.is_idempotent());
499 assert!(Method::DELETE.is_idempotent());
500 assert!(Method::HEAD.is_idempotent());
501 assert!(Method::TRACE.is_idempotent());
502 assert!(Method::QUERY.is_idempotent());
503
504 assert!(!Method::POST.is_idempotent());
505 assert!(!Method::CONNECT.is_idempotent());
506 assert!(!Method::PATCH.is_idempotent());
507 }
508
509 #[test]
510 fn test_is_safe() {
511 assert!(Method::QUERY.is_safe());
513
514 assert!(Method::GET.is_safe());
515 assert!(Method::HEAD.is_safe());
516 assert!(Method::OPTIONS.is_safe());
517 assert!(Method::TRACE.is_safe());
518
519 assert!(!Method::POST.is_safe());
520 assert!(!Method::PUT.is_safe());
521 assert!(!Method::DELETE.is_safe());
522 assert!(!Method::CONNECT.is_safe());
523 assert!(!Method::PATCH.is_safe());
524 }
525
526 #[test]
527 fn test_query_method() {
528 assert_eq!(Method::from_bytes(b"QUERY").unwrap(), Method::QUERY);
530 assert_eq!(Method::from_str("QUERY").unwrap(), Method::QUERY);
531 assert_eq!(Method::QUERY.as_str(), "QUERY");
532 assert_eq!(Method::QUERY, "QUERY");
533 }
534
535 #[test]
536 fn test_extension_method() {
537 assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
538 assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
539
540 let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
541 assert_eq!(Method::from_str(long_method).unwrap(), long_method);
542
543 let longest_inline_method = [b'A'; InlineExtension::MAX];
544 assert_eq!(
545 Method::from_bytes(&longest_inline_method).unwrap(),
546 Method(ExtensionInline(
547 InlineExtension::new(&longest_inline_method).unwrap()
548 ))
549 );
550 let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1];
551 assert_eq!(
552 Method::from_bytes(&shortest_allocated_method).unwrap(),
553 Method(ExtensionAllocated(
554 AllocatedExtension::new(&shortest_allocated_method).unwrap()
555 ))
556 );
557 }
558
559 #[test]
560 fn test_extension_method_chars() {
561 const VALID_METHOD_CHARS: &str =
562 "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
563
564 for c in VALID_METHOD_CHARS.chars() {
565 let c = c.to_string();
566
567 assert_eq!(
568 Method::from_str(&c).unwrap(),
569 c.as_str(),
570 "testing {c} is a valid method character"
571 );
572 }
573 }
574}