Skip to main content

h11r/
status.rs

1use std::error::Error;
2use std::fmt;
3
4/// An HTTP status code in the protocol-valid range `100..=599`.
5///
6/// See [RFC 9110 Section 15](https://www.rfc-editor.org/rfc/rfc9110.html#section-15).
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct StatusCode(u16);
9
10impl StatusCode {
11    /// Creates an HTTP status code from its numeric representation.
12    ///
13    /// # Errors
14    ///
15    /// Returns [`InvalidStatusCode`] when `value` is outside `100..=599`.
16    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    /// Returns the numeric status code.
25    #[must_use]
26    pub const fn as_u16(self) -> u16 {
27        self.0
28    }
29
30    /// Returns whether this is an informational (`1xx`) status.
31    #[must_use]
32    pub const fn is_informational(self) -> bool {
33        self.0 < 200
34    }
35
36    /// Returns whether this is a successful (`2xx`) status.
37    #[must_use]
38    pub const fn is_success(self) -> bool {
39        self.0 >= 200 && self.0 < 300
40    }
41
42    /// Returns whether this is a redirection (`3xx`) status.
43    #[must_use]
44    pub const fn is_redirection(self) -> bool {
45        self.0 >= 300 && self.0 < 400
46    }
47
48    /// Returns whether this is a client-error (`4xx`) status.
49    #[must_use]
50    pub const fn is_client_error(self) -> bool {
51        self.0 >= 400 && self.0 < 500
52    }
53
54    /// Returns whether this is a server-error (`5xx`) status.
55    #[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/// An error returned when an integer is outside the valid HTTP status range.
82#[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}