#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum StatusCode {
Continue = 100,
SwitchingProtocols = 101,
Ok = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultipleChoices = 300,
MovedPermanently = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
UseProxy = 305,
TemporaryRedirect = 307,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
LengthRequired = 411,
PreconditionFailed = 412,
RequestEntityTooLarge = 413,
RequestUriTooLong = 414,
UnsupportedMediaType = 415,
RequestedRangeNotSatisfiable = 416,
ExpectationFailed = 417,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HttpVersionNotSupported = 505,
Other(u16),
}
impl StatusCode {
#[must_use]
pub const fn as_u16(self) -> u16 {
match self {
Self::Continue => 100,
Self::SwitchingProtocols => 101,
Self::Ok => 200,
Self::Created => 201,
Self::Accepted => 202,
Self::NonAuthoritativeInformation => 203,
Self::NoContent => 204,
Self::ResetContent => 205,
Self::PartialContent => 206,
Self::MultipleChoices => 300,
Self::MovedPermanently => 301,
Self::Found => 302,
Self::SeeOther => 303,
Self::NotModified => 304,
Self::UseProxy => 305,
Self::TemporaryRedirect => 307,
Self::BadRequest => 400,
Self::Unauthorized => 401,
Self::PaymentRequired => 402,
Self::Forbidden => 403,
Self::NotFound => 404,
Self::MethodNotAllowed => 405,
Self::NotAcceptable => 406,
Self::ProxyAuthenticationRequired => 407,
Self::RequestTimeout => 408,
Self::Conflict => 409,
Self::Gone => 410,
Self::LengthRequired => 411,
Self::PreconditionFailed => 412,
Self::RequestEntityTooLarge => 413,
Self::RequestUriTooLong => 414,
Self::UnsupportedMediaType => 415,
Self::RequestedRangeNotSatisfiable => 416,
Self::ExpectationFailed => 417,
Self::InternalServerError => 500,
Self::NotImplemented => 501,
Self::BadGateway => 502,
Self::ServiceUnavailable => 503,
Self::GatewayTimeout => 504,
Self::HttpVersionNotSupported => 505,
Self::Other(code) => code,
}
}
#[must_use]
pub const fn text(self) -> &'static str {
match self {
Self::Continue => "Continue",
Self::SwitchingProtocols => "Switching Protocols",
Self::Ok => "OK",
Self::Created => "Created",
Self::Accepted => "Accepted",
Self::NonAuthoritativeInformation => "Non-Authoritative Information",
Self::NoContent => "No Content",
Self::ResetContent => "Reset Content",
Self::PartialContent => "Partial Content",
Self::MultipleChoices => "Multiple Choices",
Self::MovedPermanently => "Moved Permanently",
Self::Found => "Found",
Self::SeeOther => "See Other",
Self::NotModified => "Not Modified",
Self::UseProxy => "Use Proxy",
Self::TemporaryRedirect => "Temporary Redirect",
Self::BadRequest => "Bad Request",
Self::Unauthorized => "Unauthorized",
Self::PaymentRequired => "Payment Required",
Self::Forbidden => "Forbidden",
Self::NotFound => "Not Found",
Self::MethodNotAllowed => "Method Not Allowed",
Self::NotAcceptable => "Not Acceptable",
Self::ProxyAuthenticationRequired => "Proxy Authentication Required",
Self::RequestTimeout => "Request Timeout",
Self::Conflict => "Conflict",
Self::Gone => "Gone",
Self::LengthRequired => "Length Required",
Self::PreconditionFailed => "Precondition Failed",
Self::RequestEntityTooLarge => "Request Entity Too Large",
Self::RequestUriTooLong => "Request-URI Too Long",
Self::UnsupportedMediaType => "Unsupported Media Type",
Self::RequestedRangeNotSatisfiable => "Requested Range Not Satisfiable",
Self::ExpectationFailed => "Expectation Failed",
Self::InternalServerError => "Internal Server Error",
Self::NotImplemented => "Not Implemented",
Self::BadGateway => "Bad Gateway",
Self::ServiceUnavailable => "Service Unavailable",
Self::GatewayTimeout => "Gateway Timeout",
Self::HttpVersionNotSupported => "HTTP Version Not Supported",
Self::Other(_) => "Other",
}
}
#[must_use]
pub fn is_success(self) -> bool {
let code = self.as_u16();
(200..300).contains(&code)
}
#[must_use]
pub fn is_client_error(self) -> bool {
let code = self.as_u16();
(400..500).contains(&code)
}
#[must_use]
pub fn is_server_error(self) -> bool {
let code = self.as_u16();
(500..600).contains(&code)
}
}
impl From<u16> for StatusCode {
fn from(code: u16) -> Self {
match code {
100 => Self::Continue,
101 => Self::SwitchingProtocols,
200 => Self::Ok,
201 => Self::Created,
202 => Self::Accepted,
203 => Self::NonAuthoritativeInformation,
204 => Self::NoContent,
205 => Self::ResetContent,
206 => Self::PartialContent,
300 => Self::MultipleChoices,
301 => Self::MovedPermanently,
302 => Self::Found,
303 => Self::SeeOther,
304 => Self::NotModified,
305 => Self::UseProxy,
307 => Self::TemporaryRedirect,
400 => Self::BadRequest,
401 => Self::Unauthorized,
402 => Self::PaymentRequired,
403 => Self::Forbidden,
404 => Self::NotFound,
405 => Self::MethodNotAllowed,
406 => Self::NotAcceptable,
407 => Self::ProxyAuthenticationRequired,
408 => Self::RequestTimeout,
409 => Self::Conflict,
410 => Self::Gone,
411 => Self::LengthRequired,
412 => Self::PreconditionFailed,
413 => Self::RequestEntityTooLarge,
414 => Self::RequestUriTooLong,
415 => Self::UnsupportedMediaType,
416 => Self::RequestedRangeNotSatisfiable,
417 => Self::ExpectationFailed,
500 => Self::InternalServerError,
501 => Self::NotImplemented,
502 => Self::BadGateway,
503 => Self::ServiceUnavailable,
504 => Self::GatewayTimeout,
505 => Self::HttpVersionNotSupported,
other => Self::Other(other),
}
}
}
impl TryFrom<&str> for StatusCode {
type Error = crate::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value
.parse::<u16>()
.map_or(Err(crate::Error::InvalidStatusCode), |code| {
Ok(Self::from(code))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_u16_known_codes() {
let code: StatusCode = 200_u16.into();
assert_eq!(code, StatusCode::Ok);
let code: StatusCode = 404_u16.into();
assert_eq!(code, StatusCode::NotFound);
let code: StatusCode = 500_u16.into();
assert_eq!(code, StatusCode::InternalServerError);
let code: StatusCode = 100_u16.into();
assert_eq!(code, StatusCode::Continue);
let code: StatusCode = 307_u16.into();
assert_eq!(code, StatusCode::TemporaryRedirect);
}
#[test]
fn test_from_u16_unknown_code() {
let code: StatusCode = 999_u16.into();
assert_eq!(code, StatusCode::Other(999));
let code: StatusCode = 150_u16.into();
assert_eq!(code, StatusCode::Other(150));
}
#[test]
fn test_text() {
assert_eq!(StatusCode::Ok.text(), "OK");
assert_eq!(StatusCode::NotFound.text(), "Not Found");
assert_eq!(
StatusCode::InternalServerError.text(),
"Internal Server Error"
);
assert_eq!(StatusCode::BadRequest.text(), "Bad Request");
assert_eq!(StatusCode::TemporaryRedirect.text(), "Temporary Redirect");
}
#[test]
fn test_enum_values_match_code() {
assert_eq!(StatusCode::Ok.as_u16(), 200);
assert_eq!(StatusCode::NotFound.as_u16(), 404);
assert_eq!(StatusCode::InternalServerError.as_u16(), 500);
assert_eq!(StatusCode::Continue.as_u16(), 100);
assert_eq!(StatusCode::TemporaryRedirect.as_u16(), 307);
}
#[test]
fn test_is_success() {
assert!(StatusCode::Ok.is_success());
assert!(StatusCode::Created.is_success());
assert!(StatusCode::Accepted.is_success());
assert!(StatusCode::NoContent.is_success());
assert!(!StatusCode::Continue.is_success());
assert!(!StatusCode::NotFound.is_success());
assert!(!StatusCode::InternalServerError.is_success());
assert!(!StatusCode::MovedPermanently.is_success());
}
#[test]
fn test_is_client_error() {
assert!(StatusCode::BadRequest.is_client_error());
assert!(StatusCode::Unauthorized.is_client_error());
assert!(StatusCode::Forbidden.is_client_error());
assert!(StatusCode::NotFound.is_client_error());
assert!(StatusCode::MethodNotAllowed.is_client_error());
assert!(!StatusCode::Ok.is_client_error());
assert!(!StatusCode::Continue.is_client_error());
assert!(!StatusCode::InternalServerError.is_client_error());
assert!(!StatusCode::MovedPermanently.is_client_error());
}
#[test]
fn test_is_server_error() {
assert!(StatusCode::InternalServerError.is_server_error());
assert!(StatusCode::NotImplemented.is_server_error());
assert!(StatusCode::BadGateway.is_server_error());
assert!(StatusCode::ServiceUnavailable.is_server_error());
assert!(StatusCode::GatewayTimeout.is_server_error());
assert!(!StatusCode::Ok.is_server_error());
assert!(!StatusCode::Continue.is_server_error());
assert!(!StatusCode::NotFound.is_server_error());
assert!(!StatusCode::MovedPermanently.is_server_error());
}
#[test]
fn test_try_from_str_valid() {
let code: StatusCode = "200".try_into().unwrap();
assert_eq!(code, StatusCode::Ok);
let code: StatusCode = "404".try_into().unwrap();
assert_eq!(code, StatusCode::NotFound);
let code: StatusCode = "500".try_into().unwrap();
assert_eq!(code, StatusCode::InternalServerError);
let code: StatusCode = "100".try_into().unwrap();
assert_eq!(code, StatusCode::Continue);
}
#[test]
fn test_try_from_str_invalid() {
let result: Result<StatusCode, _> = "abc".try_into();
assert!(result.is_err());
let result: Result<StatusCode, _> = "".try_into();
assert!(result.is_err());
let result: Result<StatusCode, _> = "12345".try_into();
assert_eq!(result.unwrap(), StatusCode::Other(12345));
let result: Result<StatusCode, _> = "999".try_into();
assert_eq!(result.unwrap(), StatusCode::Other(999));
let result: Result<StatusCode, _> = "150".try_into();
assert_eq!(result.unwrap(), StatusCode::Other(150));
}
#[test]
fn test_try_from_u16_valid() {
let code: StatusCode = 200_u16.into();
assert_eq!(code, StatusCode::Ok);
let code: StatusCode = 404_u16.into();
assert_eq!(code, StatusCode::NotFound);
let code: StatusCode = 500_u16.into();
assert_eq!(code, StatusCode::InternalServerError);
let code: StatusCode = 100_u16.into();
assert_eq!(code, StatusCode::Continue);
let code: StatusCode = 307_u16.into();
assert_eq!(code, StatusCode::TemporaryRedirect);
}
}