use crate::request::{CR, LF};
use core::{
num::NonZeroU8,
str::{FromStr, Utf8Error},
};
#[derive(Debug, Clone)]
pub struct Response {
status: StatusCode,
meta: Option<String>,
body: Option<Vec<u8>>,
}
const BYTE_ORDER_MARK: &[u8] = "\u{FEFF}".as_bytes();
impl Response {
pub(crate) fn new<B: AsRef<[u8]>>(response_bytes: B) -> Result<Self, ResponseParseError> {
let mut response_chunks = response_bytes.as_ref().splitn(2, |c| *c == CR || *c == LF);
let header_bytes = response_chunks.next().expect("always some response bytes");
let start = if header_bytes.starts_with(BYTE_ORDER_MARK) {
BYTE_ORDER_MARK.len()
} else {
0
};
let status_bytes: [u8; 2] = match header_bytes.get(start..(start + 2)) {
None => return Err(ResponseParseError::TooShort),
Some(b) => b.try_into().expect("slice came from a range of two"),
};
let status = match StatusCode::from_bytes(status_bytes) {
Err(err) => return Err(ResponseParseError::InvalidStatus(err)),
Ok(s) => s,
};
let status_end = start + 2;
let meta_bytes = header_bytes.get(status_end..).expect("status idx is valid");
let header = str::from_utf8(meta_bytes)?.trim_start(); let meta = if header.is_empty() {
None
} else {
Some(header.to_owned())
};
let body = match response_chunks.next() {
None => None,
Some(body_bytes) => {
let bytes = body_bytes.strip_prefix(&[LF]).unwrap_or(body_bytes);
if bytes.is_empty() {
None
} else {
Some(bytes.to_owned())
}
}
};
Ok(Self { status, meta, body })
}
#[inline]
pub const fn status(&self) -> StatusCode {
self.status
}
#[inline]
pub const fn meta(&self) -> Option<&str> {
match &self.meta {
None => None,
Some(meta) => Some(meta.as_str()),
}
}
#[inline]
pub const fn body(&self) -> Option<&[u8]> {
match &self.body {
None => None,
Some(b) => Some(b.as_slice()),
}
}
#[inline]
pub const fn is_success(&self) -> bool {
self.status().is_success()
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ResponseParseError {
InvalidStatus(InvalidStatusCode),
HeaderNotUtf8(Utf8Error),
TooShort,
}
#[cfg(not(tarpaulin_include))]
impl core::fmt::Display for ResponseParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidStatus(err) => write!(f, "{err}"),
Self::HeaderNotUtf8(err) => write!(f, "Could not parse header: {err}"),
Self::TooShort => write!(f, "Header too short to parse"),
}
}
}
#[cfg(not(tarpaulin_include))]
impl core::error::Error for ResponseParseError {}
impl From<Utf8Error> for ResponseParseError {
fn from(value: Utf8Error) -> Self {
Self::HeaderNotUtf8(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StatusCode(NonZeroU8);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatusCategory {
InputExpected,
Success,
Redirection,
TemporaryFailure,
PermanentFailure,
ClientCertificates,
}
impl From<StatusCode> for StatusCategory {
#[inline]
fn from(value: StatusCode) -> Self {
value.category()
}
}
impl StatusCode {
#[inline]
pub const fn as_u8(self) -> u8 {
self.0.get()
}
#[inline]
pub const fn category(self) -> StatusCategory {
match self.0.get() {
..10 => unreachable!(),
10..=19 => StatusCategory::InputExpected,
20..=29 => StatusCategory::Success,
30..=39 => StatusCategory::Redirection,
40..=49 => StatusCategory::TemporaryFailure,
50..=59 => StatusCategory::PermanentFailure,
60..=69 => StatusCategory::ClientCertificates,
70.. => unreachable!(),
}
}
#[inline]
pub const fn from_bytes(bytes: [u8; 2]) -> Result<Self, InvalidStatusCode> {
let raw_str = match str::from_utf8(&bytes) {
Err(_) => return Err(InvalidStatusCode::new(InvalidStatusCodeKind::Utf8)),
Ok(s) => s,
};
let raw = match u8::from_str_radix(raw_str, 10) {
Err(_) => return Err(InvalidStatusCode::new(InvalidStatusCodeKind::ParseInt)),
Ok(r) => r,
};
Self::from_u8(raw)
}
#[inline]
pub const fn from_u8(value: u8) -> Result<Self, InvalidStatusCode> {
if value < 10 || value > 69 {
Err(InvalidStatusCode::new(InvalidStatusCodeKind::Range))
} else {
Ok(Self(NonZeroU8::new(value).expect("non-zero")))
}
}
#[inline]
pub const fn is_input(self) -> bool {
matches!(self.category(), StatusCategory::InputExpected)
}
#[inline]
pub const fn is_success(self) -> bool {
matches!(self.category(), StatusCategory::Success)
}
#[inline]
pub const fn is_redirection(self) -> bool {
matches!(self.category(), StatusCategory::Redirection)
}
#[inline]
pub const fn is_temporary_failure(self) -> bool {
matches!(self.category(), StatusCategory::TemporaryFailure)
}
#[inline]
pub const fn is_permanent_failure(self) -> bool {
matches!(self.category(), StatusCategory::PermanentFailure)
}
#[inline]
pub const fn is_client_certificate_failure(self) -> bool {
matches!(self.category(), StatusCategory::ClientCertificates)
}
#[inline]
pub const fn is_failure(self) -> bool {
match self.category() {
StatusCategory::InputExpected
| StatusCategory::Success
| StatusCategory::Redirection => false,
StatusCategory::TemporaryFailure
| StatusCategory::PermanentFailure
| StatusCategory::ClientCertificates => true,
}
}
}
macro_rules! status_code {
(
$(
$(#[$docs:meta])*
($code:literal, $name:ident),
)+
) => {
impl StatusCode {
$(
$(#[$docs])*
pub const $name: Self = Self(NonZeroU8::new($code).expect("non-zero"));
)+
}
};
}
status_code! {
(10, INPUT_EXPECTED),
(11, SENSITIVE_INPUT_EXPECTED),
(20, SUCCESS),
(30, TEMPORARY_REDIRECTION),
(31, PERMANENT_REDIRECTION),
(40, TEMPORARY_FAILURE),
(41, SERVER_UNAVAILABLE),
(42, CGI_ERROR),
(43, PROXY_ERROR),
(44, SLOW_DOWN),
(50, PERMANENT_FAILURE),
(51, NOT_FOUND),
(52, GONE),
(53, PROXY_REQUEST_REFUSED),
(59, BAD_REQUEST),
(60, CERTIFICATE_REQUIRED),
(61, CERTIFICATE_NOT_AUTHORIZED),
(62, CERTIFICATE_NOT_VALID),
}
impl Default for StatusCode {
#[inline]
fn default() -> Self {
Self::SUCCESS
}
}
impl PartialEq<u8> for StatusCode {
#[inline]
fn eq(&self, other: &u8) -> bool {
self.as_u8() == *other
}
}
impl From<StatusCode> for u8 {
#[inline]
fn from(value: StatusCode) -> Self {
value.as_u8()
}
}
impl TryFrom<&[u8]> for StatusCode {
type Error = InvalidStatusCode;
#[inline]
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let bytes: [u8; 2] = match value.try_into() {
Ok(b) => b,
Err(_) => return Err(InvalidStatusCode::new(InvalidStatusCodeKind::Length)),
};
Self::try_from(bytes)
}
}
impl TryFrom<[u8; 2]> for StatusCode {
type Error = InvalidStatusCode;
#[inline]
fn try_from(value: [u8; 2]) -> Result<Self, Self::Error> {
Self::from_bytes(value)
}
}
impl TryFrom<&[u8; 2]> for StatusCode {
type Error = InvalidStatusCode;
#[inline]
fn try_from(value: &[u8; 2]) -> Result<Self, Self::Error> {
Self::from_bytes(*value)
}
}
impl TryFrom<u8> for StatusCode {
type Error = InvalidStatusCode;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
Self::from_u8(value)
}
}
impl FromStr for StatusCode {
type Err = InvalidStatusCode;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() != 2 || s.chars().count() != 2 {
return Err(InvalidStatusCode::new(InvalidStatusCodeKind::Length));
}
let raw = match s.parse::<u8>() {
Err(_) => return Err(InvalidStatusCode::new(InvalidStatusCodeKind::ParseInt)),
Ok(r) => r,
};
Self::from_u8(raw)
}
}
impl TryFrom<&str> for StatusCode {
type Error = InvalidStatusCode;
#[inline]
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::from_str(value)
}
}
impl TryFrom<String> for StatusCode {
type Error = InvalidStatusCode;
#[inline]
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::from_str(&value)
}
}
#[derive(Debug)]
pub struct InvalidStatusCode {
kind: InvalidStatusCodeKind,
__priv: (),
}
impl InvalidStatusCode {
#[inline]
pub const fn kind(&self) -> InvalidStatusCodeKind {
self.kind
}
#[inline]
const fn new(kind: InvalidStatusCodeKind) -> Self {
Self { kind, __priv: () }
}
}
impl std::fmt::Display for InvalidStatusCode {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid status code: {}", self.kind)
}
}
impl std::error::Error for InvalidStatusCode {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InvalidStatusCodeKind {
ParseInt,
Range,
Length,
Utf8,
}
impl core::fmt::Display for InvalidStatusCodeKind {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ParseInt => write!(f, "could not parse as u8"),
Self::Range => write!(f, "value is less than 10 or greater than 69"),
Self::Length => write!(f, "value is not exactly 2 characters"),
Self::Utf8 => write!(f, "value is not valid UTF-8"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_from_reasonable_bytes() {
let res_data = b"20 text/gemini\r\nHello, world!\r\n".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert_eq!(res.body().unwrap(), b"Hello, world!\r\n".as_ref());
}
#[test]
fn test_response_from_reasonable_bytes_with_non_unicode_body() {
let res_data = [b"20 text/plain\r\nHello, w".as_ref(), &[0xc0], b"rld!\r\n"].concat();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/plain");
assert_eq!(
res.body().unwrap(),
[b"Hello, w".as_ref(), &[0xc0], b"rld!\r\n"].concat()
);
}
#[test]
fn test_response_from_weird_status() {
let res_data = b"21 text/gemini\r\nHello, world!\r\n".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), 21);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert_eq!(res.body().unwrap(), b"Hello, world!\r\n".as_ref());
}
#[test]
fn test_response_from_bytes_without_trailing_newline() {
let res_data = b"20 text/gemini\r\nHello, world!".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert_eq!(res.body().unwrap(), b"Hello, world!".as_ref());
}
#[test]
fn test_response_from_bytes_with_extra_leading_body_space() {
let res_data = b"20 text/gemini\r\n Hello, world!\r\n".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert_eq!(res.body().unwrap(), b" Hello, world!\r\n".as_ref());
}
#[test]
fn test_response_from_bytes_with_extra_trailing_body_space() {
let res_data = b"20 text/gemini\r\nHello, world!\r\n ".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert_eq!(res.body().unwrap(), b"Hello, world!\r\n ".as_ref());
}
#[test]
fn test_response_from_bytes_with_no_body() {
let res_data = b"20 text/gemini\r\n".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_extra_meta_space() {
let res_data = b"20 text/gemini\r\n".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_nbsp() {
let res_data = "20 text/gemini\r\n".as_bytes();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.status(), 20);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_byte_order_at_start() {
let res_data = "\u{FEFF}20 text/gemini\r\n".as_bytes();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_no_delimiter() {
let res_data = b"20 text/gemini".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_no_separation_after_status() {
let res_data = "20text/gemini".as_bytes();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert_eq!(res.meta().unwrap(), "text/gemini");
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_no_mime() {
let res_data = b"20\r\n".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert!(res.meta().is_none());
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_no_mime_reversed_delimiter() {
let res_data = b"20\n\r".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert!(res.meta().is_none());
assert_eq!(res.body().unwrap(), b"\r".as_ref());
}
#[test]
fn test_response_from_malformed_bytes_with_no_mime_no_line_feed() {
let res_data = b"20\n".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert!(res.meta().is_none());
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_no_mime_no_delimiter() {
let res_data = b"20".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), StatusCode::SUCCESS);
assert!(res.meta().is_none());
assert!(res.body().is_none());
}
#[test]
fn test_response_from_malformed_bytes_with_no_mime_no_delimiter_and_weird_status() {
let res_data = b"22".to_vec();
let res = Response::new(res_data).unwrap();
assert_eq!(res.status(), 22);
assert!(res.meta().is_none());
assert!(res.body().is_none());
}
#[test]
fn test_fails_to_parse_single_byte() {
let res_data = b"2".to_vec();
let res = Response::new(res_data);
assert!(matches!(res, Err(ResponseParseError::TooShort)));
}
#[test]
fn test_fails_to_parse_empty_buffer() {
let res_data = Vec::new();
let res = Response::new(res_data);
assert!(matches!(res, Err(ResponseParseError::TooShort)));
}
#[test]
fn test_fails_to_parse_non_utf8_status() {
let res_data = [0x32, 0xc0, 0x20, 0x62, 0x61, 0x64]; let res = Response::new(res_data).err().unwrap();
assert!(
matches!(res, ResponseParseError::InvalidStatus(err) if err.kind() == InvalidStatusCodeKind::Utf8)
);
}
#[test]
fn test_fails_to_parse_non_utf8_separator() {
let res_data = [0x32, 0x30, 0xc0, 0x62, 0x61, 0x64]; let res = Response::new(res_data).err().unwrap();
assert!(
matches!(res, ResponseParseError::HeaderNotUtf8(err) if format!("{err}") == "invalid utf-8 sequence of 1 bytes from index 0"),
"{res}"
);
}
#[test]
fn test_fails_to_parse_non_utf8_header() {
let res_data = [0x32, 0x30, 0x20, 0x62, 0xc0, 0x61, 0x64]; let res = Response::new(res_data).err().unwrap();
assert!(
matches!(res, ResponseParseError::HeaderNotUtf8(err) if format!("{err}") == "invalid utf-8 sequence of 1 bytes from index 2"),
"{res}"
);
}
#[test]
fn test_invalid_statuses() {
#[rustfmt::skip]
let bad_statuses = [
b"--", b"--", b"--", b"--", b"--", b"--", b"--", b"--", b"--", b"--",
b"0-", b"1-", b"2-", b"3-", b"4-", b"5-", b"6-", b"7-", b"8-", b"9-",
b"-0", b"-1", b"-2", b"-3", b"-4", b"-5", b"-6", b"-7", b"-8", b"-9",
b"00", b"01", b"02", b"03", b"04", b"05", b"06", b"07", b"08", b"09",
b"70", b"71", b"72", b"73", b"74", b"75", b"76", b"77", b"78", b"79",
b"80", b"81", b"82", b"83", b"84", b"85", b"86", b"87", b"88", b"89",
b"90", b"91", b"92", b"93", b"94", b"95", b"96", b"97", b"98", b"99",
b"0A", b"0A", b"0A", b"0A", b"0A", b"0A", b"0A", b"0A", b"0A", b"0A",
b"A0", b"A0", b"A0", b"A0", b"A0", b"A0", b"A0", b"A0", b"A0", b"A0",
b"AA", b"AA", b"AA", b"AA", b"AA", b"AA", b"AA", b"AA", b"AA", b"AA",
b"1A", b"1A", b"1A", b"1A", b"1A", b"1A", b"1A", b"1A", b"1A", b"1A",
&[0xef, 0xbb], ];
for case in bad_statuses {
let res = StatusCode::from_bytes(*case);
assert!(res.is_err());
let err = Response::new(case).err().unwrap(); assert!(matches!(err, ResponseParseError::InvalidStatus(_)));
}
}
#[test]
#[rustfmt::skip]
fn test_valid_statuses() {
assert_eq!(StatusCode::try_from(b"10").unwrap(), StatusCode::INPUT_EXPECTED);
assert_eq!(StatusCode::try_from(b"11").unwrap(), StatusCode::SENSITIVE_INPUT_EXPECTED);
assert_eq!(StatusCode::try_from(b"20").unwrap(), StatusCode::SUCCESS);
assert_eq!(StatusCode::try_from(b"30").unwrap(), StatusCode::TEMPORARY_REDIRECTION);
assert_eq!(StatusCode::try_from(b"31").unwrap(), StatusCode::PERMANENT_REDIRECTION);
assert_eq!(StatusCode::try_from(b"40").unwrap(), StatusCode::TEMPORARY_FAILURE);
assert_eq!(StatusCode::try_from(b"41").unwrap(), StatusCode::SERVER_UNAVAILABLE);
assert_eq!(StatusCode::try_from(b"42").unwrap(), StatusCode::CGI_ERROR);
assert_eq!(StatusCode::try_from(b"43").unwrap(), StatusCode::PROXY_ERROR);
assert_eq!(StatusCode::try_from(b"44").unwrap(), StatusCode::SLOW_DOWN);
assert_eq!(StatusCode::try_from(b"50").unwrap(), StatusCode::PERMANENT_FAILURE);
assert_eq!(StatusCode::try_from(b"51").unwrap(), StatusCode::NOT_FOUND);
assert_eq!(StatusCode::try_from(b"52").unwrap(), StatusCode::GONE);
assert_eq!(StatusCode::try_from(b"53").unwrap(), StatusCode::PROXY_REQUEST_REFUSED);
assert_eq!(StatusCode::try_from(b"59").unwrap(), StatusCode::BAD_REQUEST);
assert_eq!(StatusCode::try_from(b"60").unwrap(), StatusCode::CERTIFICATE_REQUIRED);
assert_eq!(StatusCode::try_from(b"61").unwrap(), StatusCode::CERTIFICATE_NOT_AUTHORIZED);
assert_eq!(StatusCode::try_from(b"62").unwrap(), StatusCode::CERTIFICATE_NOT_VALID);
let cases = [
(*b"13", 13, StatusCategory::InputExpected),
(*b"14", 14, StatusCategory::InputExpected),
(*b"15", 15, StatusCategory::InputExpected),
(*b"16", 16, StatusCategory::InputExpected),
(*b"17", 17, StatusCategory::InputExpected),
(*b"18", 18, StatusCategory::InputExpected),
(*b"19", 19, StatusCategory::InputExpected),
(*b"21", 21, StatusCategory::Success),
(*b"22", 22, StatusCategory::Success),
(*b"23", 23, StatusCategory::Success),
(*b"24", 24, StatusCategory::Success),
(*b"25", 25, StatusCategory::Success),
(*b"26", 26, StatusCategory::Success),
(*b"27", 27, StatusCategory::Success),
(*b"28", 28, StatusCategory::Success),
(*b"29", 29, StatusCategory::Success),
(*b"32", 32, StatusCategory::Redirection),
(*b"33", 33, StatusCategory::Redirection),
(*b"34", 34, StatusCategory::Redirection),
(*b"35", 35, StatusCategory::Redirection),
(*b"36", 36, StatusCategory::Redirection),
(*b"37", 37, StatusCategory::Redirection),
(*b"38", 38, StatusCategory::Redirection),
(*b"39", 39, StatusCategory::Redirection),
(*b"45", 45, StatusCategory::TemporaryFailure),
(*b"46", 46, StatusCategory::TemporaryFailure),
(*b"47", 47, StatusCategory::TemporaryFailure),
(*b"48", 48, StatusCategory::TemporaryFailure),
(*b"49", 49, StatusCategory::TemporaryFailure),
(*b"54", 54, StatusCategory::PermanentFailure),
(*b"55", 55, StatusCategory::PermanentFailure),
(*b"56", 56, StatusCategory::PermanentFailure),
(*b"57", 57, StatusCategory::PermanentFailure),
(*b"58", 58, StatusCategory::PermanentFailure),
(*b"63", 63, StatusCategory::ClientCertificates),
(*b"64", 64, StatusCategory::ClientCertificates),
(*b"65", 65, StatusCategory::ClientCertificates),
(*b"66", 66, StatusCategory::ClientCertificates),
(*b"67", 67, StatusCategory::ClientCertificates),
(*b"68", 68, StatusCategory::ClientCertificates),
(*b"69", 69, StatusCategory::ClientCertificates),
];
for (bytes, value, category) in cases {
let status = StatusCode::try_from(bytes).unwrap();
assert_eq!(status, value);
assert_eq!(status, StatusCode::from_u8(value).unwrap());
assert_eq!(status.category(), category);
}
}
#[test]
fn test_raw_resolution() {
let statuses = [
(StatusCode::INPUT_EXPECTED, 10),
(StatusCode::SENSITIVE_INPUT_EXPECTED, 11),
(StatusCode::try_from(12).unwrap(), 12), (StatusCode::SUCCESS, 20),
(StatusCode::TEMPORARY_REDIRECTION, 30),
(StatusCode::PERMANENT_REDIRECTION, 31),
(StatusCode::TEMPORARY_FAILURE, 40),
(StatusCode::SERVER_UNAVAILABLE, 41),
(StatusCode::CGI_ERROR, 42),
(StatusCode::PROXY_ERROR, 43),
(StatusCode::SLOW_DOWN, 44),
(StatusCode::PERMANENT_FAILURE, 50),
(StatusCode::NOT_FOUND, 51),
(StatusCode::GONE, 52),
(StatusCode::PROXY_REQUEST_REFUSED, 53),
(StatusCode::BAD_REQUEST, 59),
(StatusCode::CERTIFICATE_REQUIRED, 60),
(StatusCode::CERTIFICATE_NOT_AUTHORIZED, 61),
(StatusCode::CERTIFICATE_NOT_VALID, 62),
];
for (status, raw) in statuses {
assert_eq!(status, raw);
let code: u8 = status.into();
assert_eq!(code, raw);
}
}
}