Skip to main content

use_method/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4/// Known HTTP methods plus a variant for valid extension methods.
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub enum HttpMethod {
7    Get,
8    Head,
9    Post,
10    Put,
11    Patch,
12    Delete,
13    Options,
14    Trace,
15    Connect,
16    Other(String),
17}
18
19/// Parses a method token into a standard variant or `Other`.
20#[must_use]
21pub fn parse_method(input: &str) -> Option<HttpMethod> {
22    let normalized = normalize_method(input)?;
23
24    Some(match normalized.as_str() {
25        "GET" => HttpMethod::Get,
26        "HEAD" => HttpMethod::Head,
27        "POST" => HttpMethod::Post,
28        "PUT" => HttpMethod::Put,
29        "PATCH" => HttpMethod::Patch,
30        "DELETE" => HttpMethod::Delete,
31        "OPTIONS" => HttpMethod::Options,
32        "TRACE" => HttpMethod::Trace,
33        "CONNECT" => HttpMethod::Connect,
34        _ => HttpMethod::Other(normalized),
35    })
36}
37
38/// Normalizes a valid HTTP method token to uppercase.
39#[must_use]
40pub fn normalize_method(input: &str) -> Option<String> {
41    let trimmed = input.trim();
42    if trimmed.is_empty() || trimmed.chars().any(char::is_whitespace) {
43        return None;
44    }
45
46    if trimmed.bytes().all(is_token_byte) {
47        Some(trimmed.to_ascii_uppercase())
48    } else {
49        None
50    }
51}
52
53/// Returns `true` when the token is one of the standard request methods.
54#[must_use]
55pub fn is_standard_method(input: &str) -> bool {
56    matches!(
57        normalize_method(input).as_deref(),
58        Some(
59            "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "TRACE" | "CONNECT"
60        )
61    )
62}
63
64/// Returns `true` when the method is defined as safe.
65#[must_use]
66pub fn is_safe_method(input: &str) -> bool {
67    matches!(
68        normalize_method(input).as_deref(),
69        Some("GET" | "HEAD" | "OPTIONS" | "TRACE")
70    )
71}
72
73/// Returns `true` when the method is defined as idempotent.
74#[must_use]
75pub fn is_idempotent_method(input: &str) -> bool {
76    matches!(
77        normalize_method(input).as_deref(),
78        Some("GET" | "HEAD" | "OPTIONS" | "TRACE" | "PUT" | "DELETE")
79    )
80}
81
82/// Returns `true` when the method can reasonably carry a body.
83#[must_use]
84pub fn method_allows_body(input: &str) -> bool {
85    match parse_method(input) {
86        Some(HttpMethod::Get | HttpMethod::Head | HttpMethod::Trace) | None => false,
87        Some(_) => true,
88    }
89}
90
91/// Returns `true` when the method usually implies a request body.
92#[must_use]
93pub fn method_expects_body(input: &str) -> bool {
94    matches!(
95        normalize_method(input).as_deref(),
96        Some("POST" | "PUT" | "PATCH")
97    )
98}
99
100fn is_token_byte(byte: u8) -> bool {
101    byte.is_ascii_alphanumeric()
102        || matches!(
103            byte,
104            b'!' | b'#'
105                | b'$'
106                | b'%'
107                | b'&'
108                | b'\''
109                | b'*'
110                | b'+'
111                | b'-'
112                | b'.'
113                | b'^'
114                | b'_'
115                | b'`'
116                | b'|'
117                | b'~'
118        )
119}