#[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),
}
#[allow(dead_code)]
impl StatusCode {
#[must_use]
pub fn as_u16(self) -> u16 {
match self {
StatusCode::Continue => 100,
StatusCode::SwitchingProtocols => 101,
StatusCode::Ok => 200,
StatusCode::Created => 201,
StatusCode::Accepted => 202,
StatusCode::NonAuthoritativeInformation => 203,
StatusCode::NoContent => 204,
StatusCode::ResetContent => 205,
StatusCode::PartialContent => 206,
StatusCode::MultipleChoices => 300,
StatusCode::MovedPermanently => 301,
StatusCode::Found => 302,
StatusCode::SeeOther => 303,
StatusCode::NotModified => 304,
StatusCode::UseProxy => 305,
StatusCode::TemporaryRedirect => 307,
StatusCode::BadRequest => 400,
StatusCode::Unauthorized => 401,
StatusCode::PaymentRequired => 402,
StatusCode::Forbidden => 403,
StatusCode::NotFound => 404,
StatusCode::MethodNotAllowed => 405,
StatusCode::NotAcceptable => 406,
StatusCode::ProxyAuthenticationRequired => 407,
StatusCode::RequestTimeout => 408,
StatusCode::Conflict => 409,
StatusCode::Gone => 410,
StatusCode::LengthRequired => 411,
StatusCode::PreconditionFailed => 412,
StatusCode::RequestEntityTooLarge => 413,
StatusCode::RequestUriTooLong => 414,
StatusCode::UnsupportedMediaType => 415,
StatusCode::RequestedRangeNotSatisfiable => 416,
StatusCode::ExpectationFailed => 417,
StatusCode::InternalServerError => 500,
StatusCode::NotImplemented => 501,
StatusCode::BadGateway => 502,
StatusCode::ServiceUnavailable => 503,
StatusCode::GatewayTimeout => 504,
StatusCode::HttpVersionNotSupported => 505,
StatusCode::Other(code) => code,
}
}
#[must_use]
pub fn text(self) -> &'static str {
match self {
StatusCode::Continue => "Continue",
StatusCode::SwitchingProtocols => "Switching Protocols",
StatusCode::Ok => "OK",
StatusCode::Created => "Created",
StatusCode::Accepted => "Accepted",
StatusCode::NonAuthoritativeInformation => "Non-Authoritative Information",
StatusCode::NoContent => "No Content",
StatusCode::ResetContent => "Reset Content",
StatusCode::PartialContent => "Partial Content",
StatusCode::MultipleChoices => "Multiple Choices",
StatusCode::MovedPermanently => "Moved Permanently",
StatusCode::Found => "Found",
StatusCode::SeeOther => "See Other",
StatusCode::NotModified => "Not Modified",
StatusCode::UseProxy => "Use Proxy",
StatusCode::TemporaryRedirect => "Temporary Redirect",
StatusCode::BadRequest => "Bad Request",
StatusCode::Unauthorized => "Unauthorized",
StatusCode::PaymentRequired => "Payment Required",
StatusCode::Forbidden => "Forbidden",
StatusCode::NotFound => "Not Found",
StatusCode::MethodNotAllowed => "Method Not Allowed",
StatusCode::NotAcceptable => "Not Acceptable",
StatusCode::ProxyAuthenticationRequired => "Proxy Authentication Required",
StatusCode::RequestTimeout => "Request Timeout",
StatusCode::Conflict => "Conflict",
StatusCode::Gone => "Gone",
StatusCode::LengthRequired => "Length Required",
StatusCode::PreconditionFailed => "Precondition Failed",
StatusCode::RequestEntityTooLarge => "Request Entity Too Large",
StatusCode::RequestUriTooLong => "Request-URI Too Long",
StatusCode::UnsupportedMediaType => "Unsupported Media Type",
StatusCode::RequestedRangeNotSatisfiable => "Requested Range Not Satisfiable",
StatusCode::ExpectationFailed => "Expectation Failed",
StatusCode::InternalServerError => "Internal Server Error",
StatusCode::NotImplemented => "Not Implemented",
StatusCode::BadGateway => "Bad Gateway",
StatusCode::ServiceUnavailable => "Service Unavailable",
StatusCode::GatewayTimeout => "Gateway Timeout",
StatusCode::HttpVersionNotSupported => "HTTP Version Not Supported",
StatusCode::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 => StatusCode::Continue,
101 => StatusCode::SwitchingProtocols,
200 => StatusCode::Ok,
201 => StatusCode::Created,
202 => StatusCode::Accepted,
203 => StatusCode::NonAuthoritativeInformation,
204 => StatusCode::NoContent,
205 => StatusCode::ResetContent,
206 => StatusCode::PartialContent,
300 => StatusCode::MultipleChoices,
301 => StatusCode::MovedPermanently,
302 => StatusCode::Found,
303 => StatusCode::SeeOther,
304 => StatusCode::NotModified,
305 => StatusCode::UseProxy,
307 => StatusCode::TemporaryRedirect,
400 => StatusCode::BadRequest,
401 => StatusCode::Unauthorized,
402 => StatusCode::PaymentRequired,
403 => StatusCode::Forbidden,
404 => StatusCode::NotFound,
405 => StatusCode::MethodNotAllowed,
406 => StatusCode::NotAcceptable,
407 => StatusCode::ProxyAuthenticationRequired,
408 => StatusCode::RequestTimeout,
409 => StatusCode::Conflict,
410 => StatusCode::Gone,
411 => StatusCode::LengthRequired,
412 => StatusCode::PreconditionFailed,
413 => StatusCode::RequestEntityTooLarge,
414 => StatusCode::RequestUriTooLong,
415 => StatusCode::UnsupportedMediaType,
416 => StatusCode::RequestedRangeNotSatisfiable,
417 => StatusCode::ExpectationFailed,
500 => StatusCode::InternalServerError,
501 => StatusCode::NotImplemented,
502 => StatusCode::BadGateway,
503 => StatusCode::ServiceUnavailable,
504 => StatusCode::GatewayTimeout,
505 => StatusCode::HttpVersionNotSupported,
other => StatusCode::Other(other),
}
}
}
impl TryFrom<&str> for StatusCode {
type Error = crate::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if let Ok(code) = value.parse::<u16>() {
Ok(StatusCode::from(code))
} else {
Err(crate::Error::InvalidStatusCode)
}
}
}
#[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);
}
}