#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum HttpMethod {
Get,
Head,
Post,
Put,
Patch,
Delete,
Options,
Trace,
Connect,
Other(String),
}
#[must_use]
pub fn parse_method(input: &str) -> Option<HttpMethod> {
let normalized = normalize_method(input)?;
Some(match normalized.as_str() {
"GET" => HttpMethod::Get,
"HEAD" => HttpMethod::Head,
"POST" => HttpMethod::Post,
"PUT" => HttpMethod::Put,
"PATCH" => HttpMethod::Patch,
"DELETE" => HttpMethod::Delete,
"OPTIONS" => HttpMethod::Options,
"TRACE" => HttpMethod::Trace,
"CONNECT" => HttpMethod::Connect,
_ => HttpMethod::Other(normalized),
})
}
#[must_use]
pub fn normalize_method(input: &str) -> Option<String> {
let trimmed = input.trim();
if trimmed.is_empty() || trimmed.chars().any(char::is_whitespace) {
return None;
}
if trimmed.bytes().all(is_token_byte) {
Some(trimmed.to_ascii_uppercase())
} else {
None
}
}
#[must_use]
pub fn is_standard_method(input: &str) -> bool {
matches!(
normalize_method(input).as_deref(),
Some(
"GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "TRACE" | "CONNECT"
)
)
}
#[must_use]
pub fn is_safe_method(input: &str) -> bool {
matches!(
normalize_method(input).as_deref(),
Some("GET" | "HEAD" | "OPTIONS" | "TRACE")
)
}
#[must_use]
pub fn is_idempotent_method(input: &str) -> bool {
matches!(
normalize_method(input).as_deref(),
Some("GET" | "HEAD" | "OPTIONS" | "TRACE" | "PUT" | "DELETE")
)
}
#[must_use]
pub fn method_allows_body(input: &str) -> bool {
match parse_method(input) {
Some(HttpMethod::Get | HttpMethod::Head | HttpMethod::Trace) | None => false,
Some(_) => true,
}
}
#[must_use]
pub fn method_expects_body(input: &str) -> bool {
matches!(
normalize_method(input).as_deref(),
Some("POST" | "PUT" | "PATCH")
)
}
fn is_token_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric()
|| matches!(
byte,
b'!' | b'#'
| b'$'
| b'%'
| b'&'
| b'\''
| b'*'
| b'+'
| b'-'
| b'.'
| b'^'
| b'_'
| b'`'
| b'|'
| b'~'
)
}