Skip to main content

base_http/
method.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::syntax::is_token;
5use std::{borrow::Cow, error::Error, fmt, str::FromStr};
6
7/// A case-sensitive HTTP method token. Unknown methods remain representable.
8#[derive(Clone, Debug, Eq, Hash, PartialEq)]
9pub struct Method(Cow<'static, str>);
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct InvalidMethod;
13
14impl fmt::Display for InvalidMethod {
15    fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
16        output.write_str("invalid HTTP method token")
17    }
18}
19impl Error for InvalidMethod {}
20
21impl Method {
22    pub const GET: Self = Self(Cow::Borrowed("GET"));
23    pub const HEAD: Self = Self(Cow::Borrowed("HEAD"));
24    pub const POST: Self = Self(Cow::Borrowed("POST"));
25    pub const PUT: Self = Self(Cow::Borrowed("PUT"));
26    pub const DELETE: Self = Self(Cow::Borrowed("DELETE"));
27    pub const CONNECT: Self = Self(Cow::Borrowed("CONNECT"));
28    pub const OPTIONS: Self = Self(Cow::Borrowed("OPTIONS"));
29    pub const TRACE: Self = Self(Cow::Borrowed("TRACE"));
30    pub const PATCH: Self = Self(Cow::Borrowed("PATCH"));
31    pub const QUERY: Self = Self(Cow::Borrowed("QUERY"));
32
33    pub fn new(value: impl Into<String>) -> Result<Self, InvalidMethod> {
34        let value = value.into();
35        if !is_token(value.as_bytes()) {
36            return Err(InvalidMethod);
37        }
38        Ok(Self(Cow::Owned(value)))
39    }
40
41    pub fn as_str(&self) -> &str {
42        &self.0
43    }
44
45    /// Semantic safety does not imply that an attempted exchange can be retried.
46    pub fn is_safe(&self) -> bool {
47        matches!(
48            self.as_str(),
49            "GET" | "HEAD" | "OPTIONS" | "TRACE" | "QUERY"
50        )
51    }
52
53    /// Method semantics only; processing state and body replay are separate.
54    /// Unknown extension methods conservatively return false.
55    pub fn is_idempotent(&self) -> bool {
56        self.is_safe() || matches!(self.as_str(), "PUT" | "DELETE")
57    }
58}
59
60impl FromStr for Method {
61    type Err = InvalidMethod;
62    fn from_str(value: &str) -> Result<Self, Self::Err> {
63        Self::new(value)
64    }
65}
66impl AsRef<str> for Method {
67    fn as_ref(&self) -> &str {
68        self.as_str()
69    }
70}
71impl fmt::Display for Method {
72    fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
73        output.write_str(self.as_str())
74    }
75}