1use std::error::Error;
2use std::fmt;
3
4#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct StatusCode(u16);
9
10impl StatusCode {
11 pub const fn from_u16(value: u16) -> Result<Self, InvalidStatusCode> {
17 if value >= 100 && value <= 599 {
18 Ok(Self(value))
19 } else {
20 Err(InvalidStatusCode)
21 }
22 }
23
24 #[must_use]
26 pub const fn as_u16(self) -> u16 {
27 self.0
28 }
29
30 #[must_use]
32 pub const fn is_informational(self) -> bool {
33 self.0 < 200
34 }
35
36 #[must_use]
38 pub const fn is_success(self) -> bool {
39 self.0 >= 200 && self.0 < 300
40 }
41
42 #[must_use]
44 pub const fn is_redirection(self) -> bool {
45 self.0 >= 300 && self.0 < 400
46 }
47
48 #[must_use]
50 pub const fn is_client_error(self) -> bool {
51 self.0 >= 400 && self.0 < 500
52 }
53
54 #[must_use]
56 pub const fn is_server_error(self) -> bool {
57 self.0 >= 500
58 }
59}
60
61impl TryFrom<u16> for StatusCode {
62 type Error = InvalidStatusCode;
63
64 fn try_from(value: u16) -> Result<Self, Self::Error> {
65 Self::from_u16(value)
66 }
67}
68
69impl From<StatusCode> for u16 {
70 fn from(value: StatusCode) -> Self {
71 value.as_u16()
72 }
73}
74
75impl fmt::Display for StatusCode {
76 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
77 self.0.fmt(formatter)
78 }
79}
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub struct InvalidStatusCode;
84
85impl fmt::Display for InvalidStatusCode {
86 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
87 formatter.write_str("HTTP status code must be between 100 and 599")
88 }
89}
90
91impl Error for InvalidStatusCode {}
92
93#[cfg(test)]
94mod tests {
95 use super::StatusCode;
96
97 #[test]
98 fn accepts_exactly_three_digit_protocol_status_codes() {
99 assert!(StatusCode::from_u16(99).is_err());
100 assert!(StatusCode::from_u16(600).is_err());
101
102 for value in 100..=599 {
103 assert_eq!(
104 StatusCode::from_u16(value).expect("valid status").as_u16(),
105 value
106 );
107 }
108 }
109
110 #[test]
111 fn classifies_each_status_by_its_first_digit() {
112 for value in 100..=599 {
113 let status = StatusCode::from_u16(value).expect("valid status");
114 assert_eq!(status.is_informational(), value / 100 == 1);
115 assert_eq!(status.is_success(), value / 100 == 2);
116 assert_eq!(status.is_redirection(), value / 100 == 3);
117 assert_eq!(status.is_client_error(), value / 100 == 4);
118 assert_eq!(status.is_server_error(), value / 100 == 5);
119 }
120 }
121}