#![feature(field_init_shorthand)]
#![feature(static_in_const)]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub struct HttpVersion {
pub major: u8,
pub minor: u8,
}
impl HttpVersion {
pub fn from_parts(major: u8, minor: u8) -> Self {
debug_assert!(major < 10 && minor < 10);
HttpVersion { major, minor }
}
pub fn from_bytes(s: &[u8]) -> Result<Self, ()> {
const NAME: &[u8] = b"HTTP/";
if !s.starts_with(NAME) {
return Err(());
}
let ver = &s[NAME.len()..];
if ver.len() != 3 || ver[1] != b'.' {
return Err(());
}
match (to_digit(ver[0]), to_digit(ver[2])) {
(Some(major), Some(minor)) => Ok(HttpVersion::from_parts(major, minor)),
_ => return Err(()),
}
}
}
fn to_digit(b: u8) -> Option<u8> {
if b >= b'0' && b <= b'9' {
Some(b - b'0')
} else {
None
}
}
impl std::fmt::Display for HttpVersion {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "HTTP/{}.{}", self.major, self.minor)
}
}
impl std::str::FromStr for HttpVersion {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
HttpVersion::from_bytes(s.as_bytes())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Write;
#[test]
fn test_http_ver() {
assert_eq!(HttpVersion::from_bytes(b"HTTP/1.0"), Ok(HttpVersion {
major: 1,
minor: 0,
}));
assert_eq!(HttpVersion::from_bytes(b"HTTP/1.1"), Ok(HttpVersion {
major: 1,
minor: 1,
}));
assert_eq!(HttpVersion::from_bytes(b"HTTP/0.0"), Ok(HttpVersion {
major: 0,
minor: 0,
}));
assert_eq!(HttpVersion::from_bytes(b"HTTP/0.1"), Ok(HttpVersion {
major: 0,
minor: 1,
}));
assert_eq!(HttpVersion::from_bytes(b"HTTP/9.9"), Ok(HttpVersion {
major: 9,
minor: 9,
}));
assert_eq!("HTTP/1.1".parse(), Ok(HttpVersion {
major: 1,
minor: 1,
}));
assert_eq!("http/1.1".parse::<HttpVersion>(), Err(()));
assert_eq!(HttpVersion::from_bytes(b"http/1.1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"Http/1.1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTp/1.1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"PTTH/1.1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/1.1 "), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/1. "), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/@.@"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/1.10"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/10.1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP@1.1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/1@1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"HTTP/1 1"), Err(()));
assert_eq!(HttpVersion::from_bytes(b"PTTHPTTHPTTH"), Err(()));
assert_eq!(HttpVersion::from_bytes(b""), Err(()));
let mut buf = [b'|'; 8];
write!(&mut buf[..], "{}", HttpVersion::from_parts(1, 1)).unwrap();
assert_eq!(&buf[..], b"HTTP/1.1");
assert_eq!(HttpVersion::from_bytes(&buf[..]), Ok(HttpVersion::from_parts(1, 1)));
}
}