use alloc::vec::Vec;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
Other,
}
const ALL: [Method; 10] = [
Method::Get,
Method::Head,
Method::Post,
Method::Put,
Method::Delete,
Method::Connect,
Method::Options,
Method::Trace,
Method::Patch,
Method::Other,
];
impl Method {
#[must_use]
pub fn from_bytes(token: &[u8]) -> Self {
match token {
b"GET" => Self::Get,
b"HEAD" => Self::Head,
b"POST" => Self::Post,
b"PUT" => Self::Put,
b"DELETE" => Self::Delete,
b"CONNECT" => Self::Connect,
b"OPTIONS" => Self::Options,
b"TRACE" => Self::Trace,
b"PATCH" => Self::Patch,
_ => Self::Other,
}
}
#[must_use]
pub const fn as_str(&self) -> Option<&'static str> {
Some(match self {
Self::Get => "GET",
Self::Head => "HEAD",
Self::Post => "POST",
Self::Put => "PUT",
Self::Delete => "DELETE",
Self::Connect => "CONNECT",
Self::Options => "OPTIONS",
Self::Trace => "TRACE",
Self::Patch => "PATCH",
Self::Other => return None,
})
}
#[must_use]
pub const fn is_safe(&self) -> bool {
matches!(self, Self::Get | Self::Head | Self::Options | Self::Trace)
}
#[must_use]
pub const fn is_idempotent(&self) -> bool {
self.is_safe() || matches!(self, Self::Put | Self::Delete)
}
#[must_use]
pub const fn index(&self) -> u8 {
match self {
Self::Get => 0,
Self::Head => 1,
Self::Post => 2,
Self::Put => 3,
Self::Delete => 4,
Self::Connect => 5,
Self::Options => 6,
Self::Trace => 7,
Self::Patch => 8,
Self::Other => 9,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct MethodSet {
bits: u16,
}
impl MethodSet {
pub const EMPTY: Self = Self { bits: 0 };
#[must_use]
pub const fn of(methods: &[Method]) -> Self {
let mut set = Self::EMPTY;
let mut i = 0;
while i < methods.len() {
set = set.with(methods[i]);
i += 1;
}
set
}
#[must_use]
pub const fn with(self, method: Method) -> Self {
Self {
bits: self.bits | (1 << method.index()),
}
}
#[must_use]
pub const fn contains(&self, method: Method) -> bool {
self.bits & (1 << method.index()) != 0
}
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.bits == 0
}
pub fn iter(&self) -> impl Iterator<Item = Method> + use<> {
let set = *self;
ALL.into_iter().filter(move |m| set.contains(*m))
}
pub fn write_allow(&self, out: &mut Vec<u8>) {
let mut first = true;
for name in self.iter().filter_map(|m| m.as_str()) {
if !first {
out.extend_from_slice(b", ");
}
first = false;
out.extend_from_slice(name.as_bytes());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_method_properties_match_rfc_9110() {
let safe = [Method::Get, Method::Head, Method::Options, Method::Trace];
let idempotent = [
Method::Get,
Method::Head,
Method::Options,
Method::Trace,
Method::Put,
Method::Delete,
];
for m in ALL {
assert_eq!(m.is_safe(), safe.contains(&m), "{m:?}");
assert_eq!(m.is_idempotent(), idempotent.contains(&m), "{m:?}");
}
}
#[test]
fn test_method_index_dense_round_trips() {
for (i, m) in ALL.iter().enumerate() {
assert_eq!(usize::from(m.index()), i);
if let Some(text) = m.as_str() {
assert_eq!(Method::from_bytes(text.as_bytes()), *m);
}
}
}
#[test]
fn test_method_set_operations_cover_every_method() {
let all = MethodSet::of(&ALL);
assert_eq!(all.iter().count(), 10);
for m in ALL {
assert!(all.contains(m));
assert!(!MethodSet::EMPTY.contains(m));
assert_eq!(MethodSet::EMPTY.with(m).iter().collect::<Vec<_>>(), [m]);
}
assert!(MethodSet::default().is_empty());
let mut out = Vec::new();
all.write_allow(&mut out);
assert_eq!(
out,
b"GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH"
);
let mut out = Vec::new();
MethodSet::of(&[Method::Other]).write_allow(&mut out);
assert!(out.is_empty());
}
}