use std::ops::Deref;
use std::ops::{BitOr, BitOrAssign};
use axum::routing::MethodFilter;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Methods(MethodFilter);
impl serde::Serialize for Methods {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let methods = self.to_vec();
let mut seq = serializer.serialize_seq(Some(methods.len()))?;
for method in methods {
seq.serialize_element(method)?;
}
seq.end()
}
}
impl<'de> serde::Deserialize<'de> for Methods {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct MethodsVisitor;
impl<'de> serde::de::Visitor<'de> for MethodsVisitor {
type Value = Methods;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string or list of HTTP method names")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Methods::from_str(v).ok_or_else(|| E::custom(format!("unknown HTTP method: {}", v)))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut methods: Option<Methods> = None;
while let Some(method_str) = seq.next_element::<String>()? {
let method = Methods::from_str(&method_str).ok_or_else(|| {
serde::de::Error::custom(format!("unknown HTTP method: {}", method_str))
})?;
methods = Some(methods.map_or(method, |m| m | method));
}
methods.ok_or_else(|| serde::de::Error::custom("empty method list"))
}
}
deserializer.deserialize_any(MethodsVisitor)
}
}
impl Methods {
pub const GET: Self = Self(MethodFilter::GET);
pub const POST: Self = Self(MethodFilter::POST);
pub const PUT: Self = Self(MethodFilter::PUT);
pub const PATCH: Self = Self(MethodFilter::PATCH);
pub const DELETE: Self = Self(MethodFilter::DELETE);
pub const HEAD: Self = Self(MethodFilter::HEAD);
pub const OPTIONS: Self = Self(MethodFilter::OPTIONS);
pub const TRACE: Self = Self(MethodFilter::TRACE);
pub const CONNECT: Self = Self(MethodFilter::CONNECT);
pub fn iter(&self) -> MethodIter {
MethodIter {
filter: self.0,
position: 0,
}
}
pub fn or(self, other: Self) -> Self {
Self(self.0.or(other.0))
}
pub fn to_str(&self) -> Option<&'static str> {
KNOWN_METHODS
.iter()
.find(|(_, f)| *f == self.0)
.map(|(name, _)| *name)
}
pub fn to_vec(&self) -> Vec<&'static str> {
self.iter().map(|(name, _)| name).collect()
}
pub fn contains(&self, method: Self) -> bool {
self.0.or(method.0) == self.0
}
pub fn intersects(&self, other: Self) -> bool {
other.iter().any(|(_, method)| self.contains(method))
}
pub fn from_str(s: &str) -> Option<Self> {
KNOWN_METHODS
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case(s))
.map(|(_, filter)| Self(*filter))
}
}
impl BitOr for Methods {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0.or(rhs.0))
}
}
impl BitOrAssign for Methods {
fn bitor_assign(&mut self, rhs: Self) {
self.0 = self.0.or(rhs.0);
}
}
impl From<Methods> for MethodFilter {
fn from(methods: Methods) -> Self {
methods.0
}
}
impl From<MethodFilter> for Methods {
fn from(filter: MethodFilter) -> Self {
Self(filter)
}
}
impl Deref for Methods {
type Target = MethodFilter;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct MethodIter {
filter: MethodFilter,
position: usize,
}
impl Iterator for MethodIter {
type Item = (&'static str, Methods);
fn next(&mut self) -> Option<Self::Item> {
while self.position < KNOWN_METHODS.len() {
let (name, method) = KNOWN_METHODS[self.position];
self.position += 1;
if Methods(self.filter).contains(Methods(method)) {
return Some((name, Methods(method)));
}
}
None
}
}
const KNOWN_METHODS: &[(&str, MethodFilter)] = &[
("GET", MethodFilter::GET),
("POST", MethodFilter::POST),
("PUT", MethodFilter::PUT),
("PATCH", MethodFilter::PATCH),
("DELETE", MethodFilter::DELETE),
("HEAD", MethodFilter::HEAD),
("OPTIONS", MethodFilter::OPTIONS),
("TRACE", MethodFilter::TRACE),
("CONNECT", MethodFilter::CONNECT),
];
#[cfg(test)]
mod tests {
use super::Methods;
#[test]
fn parses_methods_case_insensitively() {
assert_eq!(Methods::from_str("get"), Some(Methods::GET));
assert_eq!(Methods::from_str("Trace"), Some(Methods::TRACE));
assert_eq!(Methods::from_str("connect"), Some(Methods::CONNECT));
assert_eq!(Methods::from_str("BREW"), None);
}
#[test]
fn iterates_combined_methods_in_stable_order() {
let methods = Methods::POST | Methods::GET | Methods::OPTIONS;
assert_eq!(methods.to_vec(), vec!["GET", "POST", "OPTIONS"]);
}
#[test]
fn detects_intersections() {
let read_methods = Methods::GET | Methods::HEAD;
assert!(read_methods.intersects(Methods::HEAD | Methods::OPTIONS));
assert!(!read_methods.intersects(Methods::POST | Methods::DELETE));
}
#[test]
fn serializes_methods_as_sequence() {
let value = serde_json::to_value(Methods::GET | Methods::POST).unwrap();
assert_eq!(value, serde_json::json!(["GET", "POST"]));
}
#[test]
fn deserializes_string_or_sequence() {
let method: Methods = serde_json::from_value(serde_json::json!("patch")).unwrap();
assert_eq!(method, Methods::PATCH);
let methods: Methods = serde_json::from_value(serde_json::json!(["GET", "HEAD"])).unwrap();
assert_eq!(methods.to_vec(), vec!["GET", "HEAD"]);
}
}