Coverage Report

Created: 2026-09-03 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/http-1.5.0/src/method.rs
Line
Count
Source
1
//! The HTTP request method
2
//!
3
//! This module contains HTTP-method related structs and errors and such. The
4
//! main type of this module, `Method`, is also reexported at the root of the
5
//! crate as `http::Method` and is intended for import through that location
6
//! primarily.
7
//!
8
//! # Examples
9
//!
10
//! ```
11
//! use http::Method;
12
//!
13
//! assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap());
14
//! assert!(Method::GET.is_idempotent());
15
//! assert_eq!(Method::POST.as_str(), "POST");
16
//! ```
17
18
use self::extension::{AllocatedExtension, InlineExtension};
19
use self::Inner::*;
20
21
use std::convert::TryFrom;
22
use std::error::Error;
23
use std::str::FromStr;
24
use std::{fmt, str};
25
26
/// The Request Method (VERB)
27
///
28
/// This type also contains constants for a number of common HTTP methods such
29
/// as GET, POST, etc.
30
///
31
/// Currently includes 8 variants representing the 8 methods defined in
32
/// [RFC 7230](https://tools.ietf.org/html/rfc7231#section-4.1), plus PATCH,
33
/// and an Extension variant for all extensions.
34
///
35
/// # Examples
36
///
37
/// ```
38
/// use http::Method;
39
///
40
/// assert_eq!(Method::GET, Method::from_bytes(b"GET").unwrap());
41
/// assert!(Method::GET.is_idempotent());
42
/// assert_eq!(Method::POST.as_str(), "POST");
43
/// ```
44
#[derive(Clone, PartialEq, Eq, Hash)]
45
pub struct Method(Inner);
46
47
/// A possible error value when converting `Method` from bytes.
48
pub struct InvalidMethod {
49
    _priv: (),
50
}
51
52
#[derive(Clone, PartialEq, Eq, Hash)]
53
enum Inner {
54
    Options,
55
    Get,
56
    Post,
57
    Put,
58
    Delete,
59
    Head,
60
    Trace,
61
    Connect,
62
    Patch,
63
    Query,
64
    // If the extension is short enough, store it inline
65
    ExtensionInline(InlineExtension),
66
    // Otherwise, allocate it
67
    ExtensionAllocated(AllocatedExtension),
68
}
69
70
impl Method {
71
    /// GET
72
    pub const GET: Method = Method(Get);
73
74
    /// POST
75
    pub const POST: Method = Method(Post);
76
77
    /// PUT
78
    pub const PUT: Method = Method(Put);
79
80
    /// DELETE
81
    pub const DELETE: Method = Method(Delete);
82
83
    /// HEAD
84
    pub const HEAD: Method = Method(Head);
85
86
    /// OPTIONS
87
    pub const OPTIONS: Method = Method(Options);
88
89
    /// CONNECT
90
    pub const CONNECT: Method = Method(Connect);
91
92
    /// PATCH
93
    pub const PATCH: Method = Method(Patch);
94
95
    /// TRACE
96
    pub const TRACE: Method = Method(Trace);
97
98
    /// QUERY
99
    pub const QUERY: Method = Method(Query);
100
101
    /// Converts a slice of bytes to an HTTP method.
102
0
    pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
103
0
        match src.len() {
104
0
            0 => Err(InvalidMethod::new()),
105
0
            3 => match src {
106
0
                b"GET" => Ok(Method(Get)),
107
0
                b"PUT" => Ok(Method(Put)),
108
0
                _ => Method::extension_inline(src),
109
            },
110
0
            4 => match src {
111
0
                b"POST" => Ok(Method(Post)),
112
0
                b"HEAD" => Ok(Method(Head)),
113
0
                _ => Method::extension_inline(src),
114
            },
115
0
            5 => match src {
116
0
                b"PATCH" => Ok(Method(Patch)),
117
0
                b"TRACE" => Ok(Method(Trace)),
118
0
                b"QUERY" => Ok(Method(Query)),
119
0
                _ => Method::extension_inline(src),
120
            },
121
0
            6 => match src {
122
0
                b"DELETE" => Ok(Method(Delete)),
123
0
                _ => Method::extension_inline(src),
124
            },
125
0
            7 => match src {
126
0
                b"OPTIONS" => Ok(Method(Options)),
127
0
                b"CONNECT" => Ok(Method(Connect)),
128
0
                _ => Method::extension_inline(src),
129
            },
130
            _ => {
131
0
                if src.len() <= InlineExtension::MAX {
132
0
                    Method::extension_inline(src)
133
                } else {
134
0
                    let allocated = AllocatedExtension::new(src)?;
135
136
0
                    Ok(Method(ExtensionAllocated(allocated)))
137
                }
138
            }
139
        }
140
0
    }
141
142
0
    fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
143
0
        let inline = InlineExtension::new(src)?;
144
145
0
        Ok(Method(ExtensionInline(inline)))
146
0
    }
147
148
    /// Whether a method is considered "safe", meaning the request is
149
    /// essentially read-only.
150
    ///
151
    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.1)
152
    /// for more words.
153
0
    pub fn is_safe(&self) -> bool {
154
0
        matches!(self.0, Get | Head | Options | Trace | Query)
155
0
    }
156
157
    /// Whether a method is considered "idempotent", meaning the request has
158
    /// the same result if executed multiple times.
159
    ///
160
    /// See [the spec](https://tools.ietf.org/html/rfc7231#section-4.2.2) for
161
    /// more words.
162
0
    pub fn is_idempotent(&self) -> bool {
163
0
        match self.0 {
164
0
            Put | Delete => true,
165
0
            _ => self.is_safe(),
166
        }
167
0
    }
168
169
    /// Return a &str representation of the HTTP method
170
    #[inline]
171
0
    pub fn as_str(&self) -> &str {
172
0
        match self.0 {
173
0
            Options => "OPTIONS",
174
0
            Get => "GET",
175
0
            Post => "POST",
176
0
            Put => "PUT",
177
0
            Delete => "DELETE",
178
0
            Head => "HEAD",
179
0
            Trace => "TRACE",
180
0
            Connect => "CONNECT",
181
0
            Patch => "PATCH",
182
0
            Query => "QUERY",
183
0
            ExtensionInline(ref inline) => inline.as_str(),
184
0
            ExtensionAllocated(ref allocated) => allocated.as_str(),
185
        }
186
0
    }
187
}
188
189
impl AsRef<str> for Method {
190
    #[inline]
191
0
    fn as_ref(&self) -> &str {
192
0
        self.as_str()
193
0
    }
194
}
195
196
impl Ord for Method {
197
    #[inline]
198
0
    fn cmp(&self, other: &Method) -> std::cmp::Ordering {
199
0
        self.as_ref().cmp(other.as_ref())
200
0
    }
201
}
202
203
impl PartialOrd for Method {
204
    #[inline]
205
0
    fn partial_cmp(&self, other: &Method) -> Option<std::cmp::Ordering> {
206
0
        Some(self.cmp(other))
207
0
    }
208
}
209
210
impl PartialEq<&Method> for Method {
211
    #[inline]
212
0
    fn eq(&self, other: &&Method) -> bool {
213
0
        self == *other
214
0
    }
215
}
216
217
impl PartialEq<Method> for &Method {
218
    #[inline]
219
0
    fn eq(&self, other: &Method) -> bool {
220
0
        *self == other
221
0
    }
Unexecuted instantiation: <&http::method::Method as core::cmp::PartialEq<http::method::Method>>::eq
Unexecuted instantiation: <&http::method::Method as core::cmp::PartialEq<http::method::Method>>::eq
222
}
223
224
impl PartialEq<str> for Method {
225
    #[inline]
226
0
    fn eq(&self, other: &str) -> bool {
227
0
        self.as_ref() == other
228
0
    }
229
}
230
231
impl PartialEq<Method> for str {
232
    #[inline]
233
0
    fn eq(&self, other: &Method) -> bool {
234
0
        self == other.as_ref()
235
0
    }
236
}
237
238
impl PartialEq<&str> for Method {
239
    #[inline]
240
0
    fn eq(&self, other: &&str) -> bool {
241
0
        self.as_ref() == *other
242
0
    }
243
}
244
245
impl PartialEq<Method> for &str {
246
    #[inline]
247
0
    fn eq(&self, other: &Method) -> bool {
248
0
        *self == other.as_ref()
249
0
    }
250
}
251
252
impl fmt::Debug for Method {
253
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254
0
        f.write_str(self.as_ref())
255
0
    }
256
}
257
258
impl fmt::Display for Method {
259
0
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
260
0
        fmt.write_str(self.as_ref())
261
0
    }
262
}
263
264
impl Default for Method {
265
    #[inline]
266
0
    fn default() -> Method {
267
0
        Method::GET
268
0
    }
269
}
270
271
impl From<&Method> for Method {
272
    #[inline]
273
0
    fn from(t: &Method) -> Self {
274
0
        t.clone()
275
0
    }
276
}
277
278
impl TryFrom<&[u8]> for Method {
279
    type Error = InvalidMethod;
280
281
    #[inline]
282
0
    fn try_from(t: &[u8]) -> Result<Self, Self::Error> {
283
0
        Method::from_bytes(t)
284
0
    }
Unexecuted instantiation: <http::method::Method as core::convert::TryFrom<&[u8]>>::try_from
Unexecuted instantiation: <http::method::Method as core::convert::TryFrom<&[u8]>>::try_from
285
}
286
287
impl TryFrom<&str> for Method {
288
    type Error = InvalidMethod;
289
290
    #[inline]
291
0
    fn try_from(t: &str) -> Result<Self, Self::Error> {
292
0
        TryFrom::try_from(t.as_bytes())
293
0
    }
Unexecuted instantiation: <http::method::Method as core::convert::TryFrom<&str>>::try_from
Unexecuted instantiation: <http::method::Method as core::convert::TryFrom<&str>>::try_from
294
}
295
296
impl FromStr for Method {
297
    type Err = InvalidMethod;
298
299
    #[inline]
300
0
    fn from_str(t: &str) -> Result<Self, Self::Err> {
301
0
        TryFrom::try_from(t)
302
0
    }
303
}
304
305
impl InvalidMethod {
306
0
    fn new() -> InvalidMethod {
307
0
        InvalidMethod { _priv: () }
308
0
    }
309
}
310
311
impl fmt::Debug for InvalidMethod {
312
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
313
0
        f.debug_struct("InvalidMethod")
314
            // skip _priv noise
315
0
            .finish()
316
0
    }
317
}
318
319
impl fmt::Display for InvalidMethod {
320
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321
0
        f.write_str("invalid HTTP method")
322
0
    }
323
}
324
325
impl Error for InvalidMethod {}
326
327
mod extension {
328
    use super::InvalidMethod;
329
    use std::str;
330
331
    #[derive(Clone, PartialEq, Eq, Hash)]
332
    // Invariant: the first self.1 bytes of self.0 are valid UTF-8.
333
    pub struct InlineExtension([u8; InlineExtension::MAX], u8);
334
335
    #[derive(Clone, PartialEq, Eq, Hash)]
336
    // Invariant: self.0 contains valid UTF-8.
337
    pub struct AllocatedExtension(Box<[u8]>);
338
339
    impl InlineExtension {
340
        // Method::from_bytes() assumes this is at least 7
341
        pub const MAX: usize = 15;
342
343
0
        pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
344
0
            let mut data: [u8; InlineExtension::MAX] = Default::default();
345
346
0
            write_checked(src, &mut data)?;
347
348
            // Invariant: write_checked ensures that the first src.len() bytes
349
            // of data are valid UTF-8.
350
0
            Ok(InlineExtension(data, src.len() as u8))
351
0
        }
352
353
0
        pub fn as_str(&self) -> &str {
354
0
            let InlineExtension(ref data, len) = self;
355
            // Safety: the invariant of InlineExtension ensures that the first
356
            // len bytes of data contain valid UTF-8.
357
0
            unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
358
0
        }
359
    }
360
361
    impl AllocatedExtension {
362
0
        pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
363
0
            let mut data: Vec<u8> = vec![0; src.len()];
364
365
0
            write_checked(src, &mut data)?;
366
367
            // Invariant: data is exactly src.len() long and write_checked
368
            // ensures that the first src.len() bytes of data are valid UTF-8.
369
0
            Ok(AllocatedExtension(data.into_boxed_slice()))
370
0
        }
371
372
0
        pub fn as_str(&self) -> &str {
373
            // Safety: the invariant of AllocatedExtension ensures that self.0
374
            // contains valid UTF-8.
375
0
            unsafe { str::from_utf8_unchecked(&self.0) }
376
0
        }
377
    }
378
379
    // From the RFC 9110 HTTP Semantics, section 9.1, the HTTP method is case-sensitive and can
380
    // contain the following characters:
381
    //
382
    // ```
383
    // method = token
384
    // token = 1*tchar
385
    // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
386
    //     "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
387
    // ```
388
    //
389
    // https://datatracker.ietf.org/doc/html/rfc9110#section-9.1
390
    //
391
    // Note that this definition means that any &[u8] that consists solely of valid
392
    // characters is also valid UTF-8 because the valid method characters are a
393
    // subset of the valid 1 byte UTF-8 encoding.
394
    #[rustfmt::skip]
395
    const METHOD_CHARS: [u8; 256] = [
396
        //  0      1      2      3      4      5      6      7      8      9
397
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //   x
398
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  1x
399
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', //  2x
400
        b'\0', b'\0', b'\0',  b'!', b'\0',  b'#',  b'$',  b'%',  b'&', b'\'', //  3x
401
        b'\0', b'\0',  b'*',  b'+', b'\0',  b'-',  b'.', b'\0',  b'0',  b'1', //  4x
402
         b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9', b'\0', b'\0', //  5x
403
        b'\0', b'\0', b'\0', b'\0', b'\0',  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
404
         b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
405
         b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
406
         b'Z', b'\0', b'\0', b'\0',  b'^',  b'_',  b'`',  b'a',  b'b',  b'c', //  9x
407
         b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
408
         b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
409
         b'x',  b'y',  b'z', b'\0',  b'|', b'\0',  b'~', b'\0', b'\0', b'\0', // 12x
410
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x
411
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x
412
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x
413
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x
414
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x
415
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x
416
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x
417
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x
418
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x
419
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x
420
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x
421
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x
422
        b'\0', b'\0', b'\0', b'\0', b'\0', b'\0'                              // 25x
423
    ];
424
425
    // write_checked ensures (among other things) that the first src.len() bytes
426
    // of dst are valid UTF-8
427
0
    fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
428
0
        for (i, &b) in src.iter().enumerate() {
429
0
            let b = METHOD_CHARS[b as usize];
430
431
0
            if b == 0 {
432
0
                return Err(InvalidMethod::new());
433
0
            }
434
435
0
            dst[i] = b;
436
        }
437
438
0
        Ok(())
439
0
    }
440
}
441
442
#[cfg(test)]
443
mod test {
444
    use super::*;
445
446
    #[test]
447
    fn test_method_eq() {
448
        assert_eq!(Method::GET, Method::GET);
449
        assert_eq!(Method::GET, "GET");
450
        assert_eq!(&Method::GET, "GET");
451
452
        assert_eq!("GET", Method::GET);
453
        assert_eq!("GET", &Method::GET);
454
455
        assert_eq!(&Method::GET, Method::GET);
456
        assert_eq!(Method::GET, &Method::GET);
457
    }
458
459
    #[test]
460
    fn test_invalid_method() {
461
        assert!(Method::from_str("").is_err());
462
        assert!(Method::from_bytes(b"").is_err());
463
        assert!(Method::from_bytes(&[0xC0]).is_err()); // invalid utf-8
464
        assert!(Method::from_bytes(&[0x10]).is_err()); // invalid method characters
465
    }
466
467
    #[test]
468
    fn test_is_idempotent() {
469
        assert!(Method::OPTIONS.is_idempotent());
470
        assert!(Method::GET.is_idempotent());
471
        assert!(Method::PUT.is_idempotent());
472
        assert!(Method::DELETE.is_idempotent());
473
        assert!(Method::HEAD.is_idempotent());
474
        assert!(Method::TRACE.is_idempotent());
475
        assert!(Method::QUERY.is_idempotent());
476
477
        assert!(!Method::POST.is_idempotent());
478
        assert!(!Method::CONNECT.is_idempotent());
479
        assert!(!Method::PATCH.is_idempotent());
480
    }
481
482
    #[test]
483
    fn test_extension_method() {
484
        assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
485
        assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
486
487
        let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
488
        assert_eq!(Method::from_str(long_method).unwrap(), long_method);
489
490
        let longest_inline_method = [b'A'; InlineExtension::MAX];
491
        assert_eq!(
492
            Method::from_bytes(&longest_inline_method).unwrap(),
493
            Method(ExtensionInline(
494
                InlineExtension::new(&longest_inline_method).unwrap()
495
            ))
496
        );
497
        let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1];
498
        assert_eq!(
499
            Method::from_bytes(&shortest_allocated_method).unwrap(),
500
            Method(ExtensionAllocated(
501
                AllocatedExtension::new(&shortest_allocated_method).unwrap()
502
            ))
503
        );
504
    }
505
506
    #[test]
507
    fn test_extension_method_chars() {
508
        const VALID_METHOD_CHARS: &str =
509
            "!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
510
511
        for c in VALID_METHOD_CHARS.chars() {
512
            let c = c.to_string();
513
514
            assert_eq!(
515
                Method::from_str(&c).unwrap(),
516
                c.as_str(),
517
                "testing {c} is a valid method character"
518
            );
519
        }
520
    }
521
}