use std::{
borrow::Cow,
fmt::{self, Display},
ops::Deref,
};
#[repr(u16)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum StatusCode {
Continue = 100,
SwitchingProtocols = 101,
EarlyHints = 103,
Ok = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultiStatus = 207,
ImUsed = 226,
MultipleChoice = 300,
MovedPermanently = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
TemporaryRedirect = 307,
PermanentRedirect = 308,
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,
PayloadTooLarge = 413,
UriTooLong = 414,
UnsupportedMediaType = 415,
RequestedRangeNotSatisfiable = 416,
ExpectationFailed = 417,
ImATeapot = 418,
MisdirectedRequest = 421,
UnprocessableEntity = 422,
Locked = 423,
FailedDependency = 424,
TooEarly = 425,
UpgradeRequired = 426,
PreconditionRequired = 428,
TooManyRequests = 429,
RequestHeaderFieldsTooLarge = 431,
UnavailableForLegalReasons = 451,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HttpVersionNotSupported = 505,
VariantAlsoNegotiates = 506,
InsufficientStorage = 507,
LoopDetected = 508,
NotExtended = 510,
NetworkAuthenticationRequired = 511,
UnknownValue(u16),
}
impl StatusCode {
pub fn is_informational(&self) -> bool {
let num: u16 = (*self).into();
(100..200).contains(&num)
}
pub fn is_success(&self) -> bool {
let num: u16 = (*self).into();
(200..300).contains(&num)
}
pub fn is_redirection(&self) -> bool {
let num: u16 = (*self).into();
(300..400).contains(&num)
}
pub fn is_client_error(&self) -> bool {
let num: u16 = (*self).into();
(400..500).contains(&num)
}
pub fn is_server_error(&self) -> bool {
let num: u16 = (*self).into();
(500..600).contains(&num)
}
pub fn canonical_reason(&self) -> Cow<'static, str> {
match self {
StatusCode::Continue => Cow::Borrowed("Continue"),
StatusCode::SwitchingProtocols => Cow::Borrowed("Switching Protocols"),
StatusCode::EarlyHints => Cow::Borrowed("Early Hints"),
StatusCode::Ok => Cow::Borrowed("OK"),
StatusCode::Created => Cow::Borrowed("Created"),
StatusCode::Accepted => Cow::Borrowed("Accepted"),
StatusCode::NonAuthoritativeInformation => {
Cow::Borrowed("Non Authoritative Information")
}
StatusCode::NoContent => Cow::Borrowed("No Content"),
StatusCode::ResetContent => Cow::Borrowed("Reset Content"),
StatusCode::PartialContent => Cow::Borrowed("Partial Content"),
StatusCode::MultiStatus => Cow::Borrowed("Multi-Status"),
StatusCode::ImUsed => Cow::Borrowed("Im Used"),
StatusCode::MultipleChoice => Cow::Borrowed("Multiple Choice"),
StatusCode::MovedPermanently => Cow::Borrowed("Moved Permanently"),
StatusCode::Found => Cow::Borrowed("Found"),
StatusCode::SeeOther => Cow::Borrowed("See Other"),
StatusCode::NotModified => Cow::Borrowed("Modified"),
StatusCode::TemporaryRedirect => Cow::Borrowed("Temporary Redirect"),
StatusCode::PermanentRedirect => Cow::Borrowed("Permanent Redirect"),
StatusCode::BadRequest => Cow::Borrowed("Bad Request"),
StatusCode::Unauthorized => Cow::Borrowed("Unauthorized"),
StatusCode::PaymentRequired => Cow::Borrowed("Payment Required"),
StatusCode::Forbidden => Cow::Borrowed("Forbidden"),
StatusCode::NotFound => Cow::Borrowed("Not Found"),
StatusCode::MethodNotAllowed => Cow::Borrowed("Method Not Allowed"),
StatusCode::NotAcceptable => Cow::Borrowed("Not Acceptable"),
StatusCode::ProxyAuthenticationRequired => {
Cow::Borrowed("Proxy Authentication Required")
}
StatusCode::RequestTimeout => Cow::Borrowed("Request Timeout"),
StatusCode::Conflict => Cow::Borrowed("Conflict"),
StatusCode::Gone => Cow::Borrowed("Gone"),
StatusCode::LengthRequired => Cow::Borrowed("Length Required"),
StatusCode::PreconditionFailed => Cow::Borrowed("Precondition Failed"),
StatusCode::PayloadTooLarge => Cow::Borrowed("Payload Too Large"),
StatusCode::UriTooLong => Cow::Borrowed("URI Too Long"),
StatusCode::UnsupportedMediaType => Cow::Borrowed("Unsupported Media Type"),
StatusCode::RequestedRangeNotSatisfiable => {
Cow::Borrowed("Requested Range Not Satisfiable")
}
StatusCode::ExpectationFailed => Cow::Borrowed("Expectation Failed"),
StatusCode::ImATeapot => Cow::Borrowed("I'm a teapot"),
StatusCode::MisdirectedRequest => Cow::Borrowed("Misdirected Request"),
StatusCode::UnprocessableEntity => Cow::Borrowed("Unprocessable Entity"),
StatusCode::Locked => Cow::Borrowed("Locked"),
StatusCode::FailedDependency => Cow::Borrowed("Failed Dependency"),
StatusCode::TooEarly => Cow::Borrowed("Too Early"),
StatusCode::UpgradeRequired => Cow::Borrowed("Upgrade Required"),
StatusCode::PreconditionRequired => Cow::Borrowed("Precondition Required"),
StatusCode::TooManyRequests => Cow::Borrowed("Too Many Requests"),
StatusCode::RequestHeaderFieldsTooLarge => {
Cow::Borrowed("Request Header Fields Too Large")
}
StatusCode::UnavailableForLegalReasons => {
Cow::Borrowed("Unavailable For Legal Reasons")
}
StatusCode::InternalServerError => Cow::Borrowed("Internal Server Error"),
StatusCode::NotImplemented => Cow::Borrowed("Not Implemented"),
StatusCode::BadGateway => Cow::Borrowed("Bad Gateway"),
StatusCode::ServiceUnavailable => Cow::Borrowed("Service Unavailable"),
StatusCode::GatewayTimeout => Cow::Borrowed("Gateway Timeout"),
StatusCode::HttpVersionNotSupported => Cow::Borrowed("HTTP Version Not Supported"),
StatusCode::VariantAlsoNegotiates => Cow::Borrowed("Variant Also Negotiates"),
StatusCode::InsufficientStorage => Cow::Borrowed("Insufficient Storage"),
StatusCode::LoopDetected => Cow::Borrowed("Loop Detected"),
StatusCode::NotExtended => Cow::Borrowed("Not Extended"),
StatusCode::NetworkAuthenticationRequired => {
Cow::Borrowed("Network Authentication Required")
}
StatusCode::UnknownValue(code) => Cow::Owned(format!("HTTP {code}")),
}
}
}
#[cfg(feature = "json")]
mod serde {
use super::StatusCode;
use ::serde::{
de::{Error as DeError, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::fmt;
impl Serialize for StatusCode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let value: u16 = **self;
serializer.serialize_u16(value)
}
}
struct StatusCodeU16Visitor;
impl Visitor<'_> for StatusCodeU16Visitor {
type Value = StatusCode;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a u16 representing the status code")
}
fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
where
E: DeError,
{
self.visit_u16(v as u16)
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: DeError,
{
self.visit_u16(v as u16)
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: DeError,
{
self.visit_u16(v as u16)
}
fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E>
where
E: DeError,
{
Ok(StatusCode::from(v))
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: DeError,
{
self.visit_u16(v as u16)
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: DeError,
{
self.visit_u16(v as u16)
}
}
impl<'de> Deserialize<'de> for StatusCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(StatusCodeU16Visitor)
}
}
}
impl Deref for StatusCode {
type Target = u16;
fn deref(&self) -> &Self::Target {
if let Self::UnknownValue(code) = self {
return code;
}
unsafe { std::mem::transmute_copy(&self) }
}
}
impl From<StatusCode> for u16 {
fn from(code: StatusCode) -> u16 {
match code {
StatusCode::UnknownValue(code) => code,
_ => *code,
}
}
}
impl From<u16> for StatusCode {
fn from(num: u16) -> Self {
match num {
100 => StatusCode::Continue,
101 => StatusCode::SwitchingProtocols,
103 => StatusCode::EarlyHints,
200 => StatusCode::Ok,
201 => StatusCode::Created,
202 => StatusCode::Accepted,
203 => StatusCode::NonAuthoritativeInformation,
204 => StatusCode::NoContent,
205 => StatusCode::ResetContent,
206 => StatusCode::PartialContent,
207 => StatusCode::MultiStatus,
226 => StatusCode::ImUsed,
300 => StatusCode::MultipleChoice,
301 => StatusCode::MovedPermanently,
302 => StatusCode::Found,
303 => StatusCode::SeeOther,
304 => StatusCode::NotModified,
307 => StatusCode::TemporaryRedirect,
308 => StatusCode::PermanentRedirect,
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::PayloadTooLarge,
414 => StatusCode::UriTooLong,
415 => StatusCode::UnsupportedMediaType,
416 => StatusCode::RequestedRangeNotSatisfiable,
417 => StatusCode::ExpectationFailed,
418 => StatusCode::ImATeapot,
421 => StatusCode::MisdirectedRequest,
422 => StatusCode::UnprocessableEntity,
423 => StatusCode::Locked,
424 => StatusCode::FailedDependency,
425 => StatusCode::TooEarly,
426 => StatusCode::UpgradeRequired,
428 => StatusCode::PreconditionRequired,
429 => StatusCode::TooManyRequests,
431 => StatusCode::RequestHeaderFieldsTooLarge,
451 => StatusCode::UnavailableForLegalReasons,
500 => StatusCode::InternalServerError,
501 => StatusCode::NotImplemented,
502 => StatusCode::BadGateway,
503 => StatusCode::ServiceUnavailable,
504 => StatusCode::GatewayTimeout,
505 => StatusCode::HttpVersionNotSupported,
506 => StatusCode::VariantAlsoNegotiates,
507 => StatusCode::InsufficientStorage,
508 => StatusCode::LoopDetected,
510 => StatusCode::NotExtended,
511 => StatusCode::NetworkAuthenticationRequired,
_ => StatusCode::UnknownValue(num),
}
}
}
impl PartialEq<StatusCode> for u16 {
fn eq(&self, other: &StatusCode) -> bool {
self == other as &u16
}
}
impl PartialEq<u16> for StatusCode {
fn eq(&self, other: &u16) -> bool {
self as &u16 == other
}
}
impl Display for StatusCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", *(self as &u16))
}
}
#[cfg(test)]
mod test {
use super::StatusCode;
#[test]
fn status_code_from() {
assert_eq!(StatusCode::from(204), 204);
assert_eq!(StatusCode::from(700), 700);
}
#[test]
fn display() {
assert_eq!(StatusCode::from(204).to_string(), "204");
assert_eq!(StatusCode::from(700).to_string(), "700");
}
#[test]
fn canonical_reason() {
assert_eq!(StatusCode::from(204).canonical_reason(), "No Content");
assert_eq!(StatusCode::from(700).canonical_reason(), "HTTP 700");
}
#[cfg(feature = "json")]
#[test]
fn serde_as_u16() -> Result<(), serde_json::Error> {
let status_code: StatusCode = serde_json::from_str("202")?;
assert_eq!(StatusCode::Accepted, status_code);
assert_eq!(
Some(202),
serde_json::to_value(StatusCode::Accepted)?.as_u64()
);
Ok(())
}
}