use self::extension::{AllocatedExtension, InlineExtension};
use self::Inner::*;
use std::convert::TryFrom;
use std::error::Error;
use std::str::FromStr;
use std::{fmt, str};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Method(Inner);
pub struct InvalidMethod {
_priv: (),
}
#[derive(Clone, PartialEq, Eq, Hash)]
enum Inner {
Options,
Get,
Post,
Put,
Delete,
Head,
Trace,
Connect,
Patch,
ExtensionInline(InlineExtension),
ExtensionAllocated(AllocatedExtension),
}
impl Method {
pub const GET: Method = Method(Get);
pub const POST: Method = Method(Post);
pub const PUT: Method = Method(Put);
pub const DELETE: Method = Method(Delete);
pub const HEAD: Method = Method(Head);
pub const OPTIONS: Method = Method(Options);
pub const CONNECT: Method = Method(Connect);
pub const PATCH: Method = Method(Patch);
pub const TRACE: Method = Method(Trace);
pub fn from_bytes(src: &[u8]) -> Result<Method, InvalidMethod> {
match src.len() {
0 => Err(InvalidMethod::new()),
3 => match src {
b"GET" => Ok(Method(Get)),
b"PUT" => Ok(Method(Put)),
_ => Method::extension_inline(src),
},
4 => match src {
b"POST" => Ok(Method(Post)),
b"HEAD" => Ok(Method(Head)),
_ => Method::extension_inline(src),
},
5 => match src {
b"PATCH" => Ok(Method(Patch)),
b"TRACE" => Ok(Method(Trace)),
_ => Method::extension_inline(src),
},
6 => match src {
b"DELETE" => Ok(Method(Delete)),
_ => Method::extension_inline(src),
},
7 => match src {
b"OPTIONS" => Ok(Method(Options)),
b"CONNECT" => Ok(Method(Connect)),
_ => Method::extension_inline(src),
},
_ => {
if src.len() <= InlineExtension::MAX {
Method::extension_inline(src)
} else {
let allocated = AllocatedExtension::new(src)?;
Ok(Method(ExtensionAllocated(allocated)))
}
}
}
}
fn extension_inline(src: &[u8]) -> Result<Method, InvalidMethod> {
let inline = InlineExtension::new(src)?;
Ok(Method(ExtensionInline(inline)))
}
pub fn is_safe(&self) -> bool {
matches!(self.0, Get | Head | Options | Trace)
}
pub fn is_idempotent(&self) -> bool {
match self.0 {
Put | Delete => true,
_ => self.is_safe(),
}
}
#[inline]
pub fn as_str(&self) -> &str {
match self.0 {
Options => "OPTIONS",
Get => "GET",
Post => "POST",
Put => "PUT",
Delete => "DELETE",
Head => "HEAD",
Trace => "TRACE",
Connect => "CONNECT",
Patch => "PATCH",
ExtensionInline(ref inline) => inline.as_str(),
ExtensionAllocated(ref allocated) => allocated.as_str(),
}
}
}
impl AsRef<str> for Method {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> PartialEq<&'a Method> for Method {
#[inline]
fn eq(&self, other: &&'a Method) -> bool {
self == *other
}
}
impl<'a> PartialEq<Method> for &'a Method {
#[inline]
fn eq(&self, other: &Method) -> bool {
*self == other
}
}
impl PartialEq<str> for Method {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_ref() == other
}
}
impl PartialEq<Method> for str {
#[inline]
fn eq(&self, other: &Method) -> bool {
self == other.as_ref()
}
}
impl<'a> PartialEq<&'a str> for Method {
#[inline]
fn eq(&self, other: &&'a str) -> bool {
self.as_ref() == *other
}
}
impl<'a> PartialEq<Method> for &'a str {
#[inline]
fn eq(&self, other: &Method) -> bool {
*self == other.as_ref()
}
}
impl fmt::Debug for Method {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_ref())
}
}
impl fmt::Display for Method {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(self.as_ref())
}
}
impl Default for Method {
#[inline]
fn default() -> Method {
Method::GET
}
}
impl<'a> From<&'a Method> for Method {
#[inline]
fn from(t: &'a Method) -> Self {
t.clone()
}
}
impl<'a> TryFrom<&'a [u8]> for Method {
type Error = InvalidMethod;
#[inline]
fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
Method::from_bytes(t)
}
}
impl<'a> TryFrom<&'a str> for Method {
type Error = InvalidMethod;
#[inline]
fn try_from(t: &'a str) -> Result<Self, Self::Error> {
TryFrom::try_from(t.as_bytes())
}
}
impl FromStr for Method {
type Err = InvalidMethod;
#[inline]
fn from_str(t: &str) -> Result<Self, Self::Err> {
TryFrom::try_from(t)
}
}
impl InvalidMethod {
fn new() -> InvalidMethod {
InvalidMethod { _priv: () }
}
}
impl fmt::Debug for InvalidMethod {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("InvalidMethod")
.finish()
}
}
impl fmt::Display for InvalidMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid HTTP method")
}
}
impl Error for InvalidMethod {}
mod extension {
use super::InvalidMethod;
use std::str;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct InlineExtension([u8; InlineExtension::MAX], u8);
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct AllocatedExtension(Box<[u8]>);
impl InlineExtension {
pub const MAX: usize = 15;
pub fn new(src: &[u8]) -> Result<InlineExtension, InvalidMethod> {
let mut data: [u8; InlineExtension::MAX] = Default::default();
write_checked(src, &mut data)?;
Ok(InlineExtension(data, src.len() as u8))
}
pub fn as_str(&self) -> &str {
let InlineExtension(ref data, len) = self;
unsafe { str::from_utf8_unchecked(&data[..*len as usize]) }
}
}
impl AllocatedExtension {
pub fn new(src: &[u8]) -> Result<AllocatedExtension, InvalidMethod> {
let mut data: Vec<u8> = vec![0; src.len()];
write_checked(src, &mut data)?;
Ok(AllocatedExtension(data.into_boxed_slice()))
}
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.0) }
}
}
#[rustfmt::skip]
const METHOD_CHARS: [u8; 256] = [
b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'!', b'\0', b'#', b'$', b'%', b'&', b'\'', b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'\0', b'\0', b'\0', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'\0', b'|', b'\0', b'~', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0' ];
fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> {
for (i, &b) in src.iter().enumerate() {
let b = METHOD_CHARS[b as usize];
if b == 0 {
return Err(InvalidMethod::new());
}
dst[i] = b;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_method_eq() {
assert_eq!(Method::GET, Method::GET);
assert_eq!(Method::GET, "GET");
assert_eq!(&Method::GET, "GET");
assert_eq!("GET", Method::GET);
assert_eq!("GET", &Method::GET);
assert_eq!(&Method::GET, Method::GET);
assert_eq!(Method::GET, &Method::GET);
}
#[test]
fn test_invalid_method() {
assert!(Method::from_str("").is_err());
assert!(Method::from_bytes(b"").is_err());
assert!(Method::from_bytes(&[0xC0]).is_err()); assert!(Method::from_bytes(&[0x10]).is_err()); }
#[test]
fn test_is_idempotent() {
assert!(Method::OPTIONS.is_idempotent());
assert!(Method::GET.is_idempotent());
assert!(Method::PUT.is_idempotent());
assert!(Method::DELETE.is_idempotent());
assert!(Method::HEAD.is_idempotent());
assert!(Method::TRACE.is_idempotent());
assert!(!Method::POST.is_idempotent());
assert!(!Method::CONNECT.is_idempotent());
assert!(!Method::PATCH.is_idempotent());
}
#[test]
fn test_extension_method() {
assert_eq!(Method::from_str("WOW").unwrap(), "WOW");
assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!");
let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely.";
assert_eq!(Method::from_str(long_method).unwrap(), long_method);
let longest_inline_method = [b'A'; InlineExtension::MAX];
assert_eq!(
Method::from_bytes(&longest_inline_method).unwrap(),
Method(ExtensionInline(
InlineExtension::new(&longest_inline_method).unwrap()
))
);
let shortest_allocated_method = [b'A'; InlineExtension::MAX + 1];
assert_eq!(
Method::from_bytes(&shortest_allocated_method).unwrap(),
Method(ExtensionAllocated(
AllocatedExtension::new(&shortest_allocated_method).unwrap()
))
);
}
#[test]
fn test_extension_method_chars() {
const VALID_METHOD_CHARS: &str =
"!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for c in VALID_METHOD_CHARS.chars() {
let c = c.to_string();
assert_eq!(
Method::from_str(&c).unwrap(),
c.as_str(),
"testing {c} is a valid method character"
);
}
}
}