Skip to main content

rama_http_types/
method.rs

1//! Forked from the `http` crate — vendored so rama owns its HTTP leaf types.
2//! See `docs/thirdparty/fork/README.md`.
3//!
4//! The HTTP request method
5//!
6//! This module contains HTTP-method related structs and errors and such. The
7//! main type of this module, `Method`, is also reexported at the root of the
8//! crate as `rama_http_types::Method` and is intended for import through that location
9//! primarily.
10//!
11//! # Examples
12//!
13//! ```
14//! use rama_http_types::Method;
15//!
16//! assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap());
17//! assert!(Method::GET.is_idempotent());
18//! assert_eq!(Method::POST.as_str(), "POST");
19//! ```
20
21// Vendored verbatim from `http`; keep upstream's style rather than rama's.
22#![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/// The Request Method (VERB)
40///
41/// This type also contains constants for a number of common HTTP methods such
42/// as GET, POST, etc.
43///
44/// Currently includes 8 variants representing the 8 methods defined in
45/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH
46/// ([RFC 5789](https://tools.ietf.org/html/rfc5789)) and QUERY
47/// ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)), and an Extension
48/// variant for all extensions.
49///
50/// # Examples
51///
52/// ```
53/// use rama_http_types::Method;
54///
55/// assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap());
56/// assert!(Method::GET.is_idempotent());
57/// assert_eq!(Method::POST.as_str(), "POST");
58/// ```
59#[derive(Clone, PartialEq, Eq, Hash)]
60pub struct Method(Inner);
61
62/// A possible error value when converting `Method` from bytes.
63pub 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    // If the extension is short enough, store it inline
80    ExtensionInline(InlineExtension),
81    // Otherwise, allocate it
82    ExtensionAllocated(AllocatedExtension),
83}
84
85impl Method {
86    /// GET
87    pub const GET: Method = Method(Get);
88
89    /// POST
90    pub const POST: Method = Method(Post);
91
92    /// PUT
93    pub const PUT: Method = Method(Put);
94
95    /// DELETE
96    pub const DELETE: Method = Method(Delete);
97
98    /// HEAD
99    pub const HEAD: Method = Method(Head);
100
101    /// OPTIONS
102    pub const OPTIONS: Method = Method(Options);
103
104    /// CONNECT
105    pub const CONNECT: Method = Method(Connect);
106
107    /// PATCH
108    pub const PATCH: Method = Method(Patch);
109
110    /// TRACE
111    pub const TRACE: Method = Method(Trace);
112
113    /// QUERY
114    ///
115    /// A safe, idempotent, and cacheable method whose request content defines
116    /// the query, as specified in [RFC 10008](https://www.rfc-editor.org/rfc/rfc10008).
117    pub const QUERY: Method = Method(Query);
118
119    /// Converts a slice of bytes to an HTTP method.
120    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    /// Whether a method is considered "safe", meaning the request is
167    /// essentially read-only.
168    ///
169    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
170    /// for more words.
171    pub fn is_safe(&self) -> bool {
172        // QUERY is safe and idempotent per RFC 10008 §2.1: the request content
173        // defines a query the target processes without changing its state.
174        matches!(self.0, Get | Head | Options | Trace | Query)
175    }
176
177    /// Whether a method is considered "idempotent", meaning the request has
178    /// the same result if executed multiple times.
179    ///
180    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for
181    /// more words.
182    pub fn is_idempotent(&self) -> bool {
183        match self.0 {
184            Put | Delete => true,
185            _ => self.is_safe(),
186        }
187    }
188
189    /// Return a &str representation of the HTTP method
190    #[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
216// Methods are tokens, so their canonical Serde representation is the exact
217// token text. This also preserves extension methods without an allocation on
218// serialization.
219use 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            // skip _priv noise
342            .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    // Invariant: the first self.1 bytes of self.0 are valid UTF-8.
360    pub struct InlineExtension([u8; InlineExtension::MAX], u8);
361
362    #[derive(Clone, PartialEq, Eq, Hash)]
363    // Invariant: self.0 contains valid UTF-8.
364    pub struct AllocatedExtension(Box<[u8]>);
365
366    impl InlineExtension {
367        // Method::from_bytes() assumes this is at least 7
368        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            // Invariant: write_checked ensures that the first src.len() bytes
376            // of data are valid UTF-8.
377            Ok(InlineExtension(data, src.len() as u8))
378        }
379
380        pub fn as_str(&self) -> &str {
381            let InlineExtension(data, len) = self;
382            // Safety: the invariant of InlineExtension ensures that the first
383            // len bytes of data contain valid UTF-8.
384            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            // Invariant: data is exactly src.len() long and write_checked
395            // ensures that the first src.len() bytes of data are valid UTF-8.
396            Ok(AllocatedExtension(data.into_boxed_slice()))
397        }
398
399        pub fn as_str(&self) -> &str {
400            // Safety: the invariant of AllocatedExtension ensures that self.0
401            // contains valid UTF-8.
402            unsafe { str::from_utf8_unchecked(&self.0) }
403        }
404    }
405
406    // From the RFC 9110 HTTP Semantics, section 9.1, the HTTP method is case-sensitive and can
407    // contain the following characters:
408    //
409    // ```
410    // method = token
411    // token = 1*tchar
412    // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
413    //     "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
414    // ```
415    //
416    // https://datatracker.ietf.org/doc/html/rfc9110#section-9.1
417    //
418    // Note that this definition means that any &[u8] that consists solely of valid
419    // characters is also valid UTF-8 because the valid method characters are a
420    // subset of the valid 1 byte UTF-8 encoding.
421    #[rustfmt::skip]
422    const METHOD_CHARS: [u8; 256] = [
423        //  0      1      2      3      4      5      6      7      8      9
424        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //   x
425        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  1x
426        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  2x
427        b'\0', b'\0', b'\0',  b'!', b'\0',  b'#',  b'$',  b'%',  b'&', b'\'', //  3x
428        b'\0', b'\0',  b'*',  b'+', b'\0',  b'-',  b'.', b'\0',  b'0',  b'1', //  4x
429         b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9', b'\0', b'\0', //  5x
430        b'\0', b'\0', b'\0', b'\0', b'\0',  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
431         b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
432         b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
433         b'Z', b'\0', b'\0', b'\0',  b'^',  b'_',  b'`',  b'a',  b'b',  b'c', //  9x
434         b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
435         b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
436         b'x',  b'y',  b'z', b'\0',  b'|', b'\0',  b'~', b'\0', b'\0', b'\0', // 12x
437        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x
438        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x
439        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x
440        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x
441        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x
442        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x
443        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x
444        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x
445        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x
446        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x
447        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x
448        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x
449        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0'                              // 25x
450    ];
451
452    // write_checked ensures (among other things) that the first src.len() bytes
453    // of dst are valid UTF-8
454    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()); // invalid utf-8
491        assert!(Method::from_bytes(&[0x10]).is_err()); // invalid method characters
492    }
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        // RFC 10008 §2.1: QUERY is safe.
512        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        // QUERY must parse to the dedicated variant, not an extension method.
529        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}