Skip to main content

sark_http/method/
mod.rs

1mod known;
2
3use std::{num::NonZero, str};
4
5pub use known::Known;
6use o3::alloc::{self, BoxSliceExt as _};
7
8use crate::field;
9
10/// An owned, case-sensitive HTTP token with inline storage for short extensions.
11#[derive(Debug, PartialEq, Eq, Hash)]
12pub struct Method(Kind);
13
14#[derive(Debug, PartialEq, Eq, Hash)]
15enum Kind {
16    Known(Known),
17    Trace,
18    Query,
19    Inline { bytes: [u8; 15], len: NonZero<u8> },
20    Owned(Box<[u8]>),
21}
22
23impl Method {
24    pub const GET: Self = Self(Kind::Known(Known::Get));
25    pub const POST: Self = Self(Kind::Known(Known::Post));
26    pub const PUT: Self = Self(Kind::Known(Known::Put));
27    pub const PATCH: Self = Self(Kind::Known(Known::Patch));
28    pub const DELETE: Self = Self(Kind::Known(Known::Delete));
29    pub const HEAD: Self = Self(Kind::Known(Known::Head));
30    pub const OPTIONS: Self = Self(Kind::Known(Known::Options));
31    pub const CONNECT: Self = Self(Kind::Known(Known::Connect));
32    pub const TRACE: Self = Self(Kind::Trace);
33    pub const QUERY: Self = Self(Kind::Query);
34
35    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
36        if let Some(known) = Known::from_bytes(bytes) {
37            return Ok(known.into());
38        }
39        if bytes == b"TRACE" {
40            return Ok(Self::TRACE);
41        }
42        if bytes == b"QUERY" {
43            return Ok(Self::QUERY);
44        }
45        field::Error::name(bytes).map_err(Error::Syntax)?;
46        if bytes.len() <= 15 {
47            let mut inline = [0; 15];
48            inline[..bytes.len()].copy_from_slice(bytes);
49            Ok(Self(Kind::Inline {
50                bytes: inline,
51                len: NonZero::<u8>::MIN.saturating_add(bytes.len() as u8 - 1),
52            }))
53        } else {
54            Ok(Self(Kind::Owned(
55                Box::try_copy(bytes).map_err(Error::Allocation)?,
56            )))
57        }
58    }
59
60    pub fn as_str(&self) -> &str {
61        // SAFETY: canonical variants are static ASCII and extension admission
62        // retains the exact immutable bytes covered by its token contract.
63        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
64    }
65
66    pub fn as_bytes(&self) -> &[u8] {
67        match &self.0 {
68            Kind::Known(known) => known.as_str().as_bytes(),
69            Kind::Trace => b"TRACE",
70            Kind::Query => b"QUERY",
71            Kind::Inline { bytes, len } => {
72                // SAFETY: the sole inline constructor admits 1..=15 immutable bytes.
73                unsafe { bytes.get_unchecked(..usize::from(len.get())) }
74            }
75            Kind::Owned(bytes) => bytes,
76        }
77    }
78}
79
80impl Default for Method {
81    fn default() -> Self {
82        Self::GET
83    }
84}
85
86impl AsRef<str> for Method {
87    fn as_ref(&self) -> &str {
88        self.as_str()
89    }
90}
91
92const _: () = assert!(size_of::<Method>() <= 3 * size_of::<usize>());
93
94impl From<Known> for Method {
95    fn from(method: Known) -> Self {
96        Self(Kind::Known(method))
97    }
98}
99
100/// Exact token syntax or extension storage failure.
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102pub enum Error {
103    Syntax(field::Error),
104    Allocation(alloc::Exhausted),
105}