Skip to main content

disco_types/
status.rs

1use serde::{Deserialize, Serialize};
2use std::fmt::{self, Display, Formatter};
3
4/// Serializable HTTP status code.
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
6#[serde(try_from = "u16", into = "u16")]
7pub struct StatusCode(http::StatusCode);
8
9impl TryFrom<u16> for StatusCode {
10    type Error = <http::StatusCode as TryFrom<u16>>::Error;
11
12    fn try_from(code: u16) -> Result<Self, Self::Error> {
13        Ok(http::StatusCode::try_from(code)?.into())
14    }
15}
16
17impl From<StatusCode> for u16 {
18    fn from(code: StatusCode) -> Self {
19        code.0.as_u16()
20    }
21}
22
23#[cfg(feature = "http-types")]
24impl TryFrom<StatusCode> for http_types::StatusCode {
25    type Error = <http_types::StatusCode as TryFrom<u16>>::Error;
26
27    fn try_from(code: StatusCode) -> Result<Self, Self::Error> {
28        // Tide's status code enum does not represent all possible HTTP status codes, while the
29        // source type (`http::StatusCode`) does, so this conversion may fail.
30        u16::from(code).try_into()
31    }
32}
33
34#[cfg(feature = "http-types")]
35impl From<http_types::StatusCode> for StatusCode {
36    fn from(code: http_types::StatusCode) -> Self {
37        // The source type, `http_types::StatusCode`, only represents valid HTTP status codes, and the
38        // destination type, `http::StatusCode`, can represent all valid HTTP status codes, so
39        // this conversion will never panic.
40        u16::from(code).try_into().unwrap()
41    }
42}
43
44#[cfg(feature = "http-types")]
45impl PartialEq<http_types::StatusCode> for StatusCode {
46    fn eq(&self, other: &http_types::StatusCode) -> bool {
47        *self == Self::from(*other)
48    }
49}
50
51#[cfg(feature = "http-types")]
52impl PartialEq<StatusCode> for http_types::StatusCode {
53    fn eq(&self, other: &StatusCode) -> bool {
54        StatusCode::from(*self) == *other
55    }
56}
57
58impl From<StatusCode> for http::StatusCode {
59    fn from(code: StatusCode) -> Self {
60        code.0
61    }
62}
63
64impl From<http::StatusCode> for StatusCode {
65    fn from(code: http::StatusCode) -> Self {
66        Self(code)
67    }
68}
69
70impl PartialEq<http::StatusCode> for StatusCode {
71    fn eq(&self, other: &http::StatusCode) -> bool {
72        *self == Self::from(*other)
73    }
74}
75
76impl PartialEq<StatusCode> for http::StatusCode {
77    fn eq(&self, other: &StatusCode) -> bool {
78        *self == Self::from(*other)
79    }
80}
81
82impl Display for StatusCode {
83    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
84        write!(f, "{}", u16::from(*self))
85    }
86}
87
88impl StatusCode {
89    /// Returns `true` if the status code is `1xx` range.
90    ///
91    /// If this returns `true` it indicates that the request was received,
92    /// continuing process.
93    pub fn is_informational(self) -> bool {
94        self.0.is_informational()
95    }
96
97    /// Returns `true` if the status code is the `2xx` range.
98    ///
99    /// If this returns `true` it indicates that the request was successfully
100    /// received, understood, and accepted.
101    pub fn is_success(self) -> bool {
102        self.0.is_success()
103    }
104
105    /// Returns `true` if the status code is the `3xx` range.
106    ///
107    /// If this returns `true` it indicates that further action needs to be
108    /// taken in order to complete the request.
109    pub fn is_redirection(self) -> bool {
110        self.0.is_redirection()
111    }
112
113    /// Returns `true` if the status code is the `4xx` range.
114    ///
115    /// If this returns `true` it indicates that the request contains bad syntax
116    /// or cannot be fulfilled.
117    pub fn is_client_error(self) -> bool {
118        self.0.is_client_error()
119    }
120
121    /// Returns `true` if the status code is the `5xx` range.
122    ///
123    /// If this returns `true` it indicates that the server failed to fulfill an
124    /// apparently valid request.
125    pub fn is_server_error(self) -> bool {
126        self.0.is_server_error()
127    }
128
129    /// The canonical reason for a given status code
130    pub fn canonical_reason(self) -> Option<&'static str> {
131        self.0.canonical_reason()
132    }
133
134    pub const CONTINUE: Self = Self(http::StatusCode::CONTINUE);
135    pub const SWITCHING_PROTOCOLS: Self = Self(http::StatusCode::SWITCHING_PROTOCOLS);
136    pub const PROCESSING: Self = Self(http::StatusCode::PROCESSING);
137    pub const OK: Self = Self(http::StatusCode::OK);
138    pub const CREATED: Self = Self(http::StatusCode::CREATED);
139    pub const ACCEPTED: Self = Self(http::StatusCode::ACCEPTED);
140    pub const NON_AUTHORITATIVE_INFORMATION: Self =
141        Self(http::StatusCode::NON_AUTHORITATIVE_INFORMATION);
142    pub const NO_CONTENT: Self = Self(http::StatusCode::NO_CONTENT);
143    pub const RESET_CONTENT: Self = Self(http::StatusCode::RESET_CONTENT);
144    pub const PARTIAL_CONTENT: Self = Self(http::StatusCode::PARTIAL_CONTENT);
145    pub const MULTI_STATUS: Self = Self(http::StatusCode::MULTI_STATUS);
146    pub const ALREADY_REPORTED: Self = Self(http::StatusCode::ALREADY_REPORTED);
147    pub const IM_USED: Self = Self(http::StatusCode::IM_USED);
148    pub const MULTIPLE_CHOICES: Self = Self(http::StatusCode::MULTIPLE_CHOICES);
149    pub const MOVED_PERMANENTLY: Self = Self(http::StatusCode::MOVED_PERMANENTLY);
150    pub const FOUND: Self = Self(http::StatusCode::FOUND);
151    pub const SEE_OTHER: Self = Self(http::StatusCode::SEE_OTHER);
152    pub const NOT_MODIFIED: Self = Self(http::StatusCode::NOT_MODIFIED);
153    pub const USE_PROXY: Self = Self(http::StatusCode::USE_PROXY);
154    pub const TEMPORARY_REDIRECT: Self = Self(http::StatusCode::TEMPORARY_REDIRECT);
155    pub const PERMANENT_REDIRECT: Self = Self(http::StatusCode::PERMANENT_REDIRECT);
156    pub const BAD_REQUEST: Self = Self(http::StatusCode::BAD_REQUEST);
157    pub const UNAUTHORIZED: Self = Self(http::StatusCode::UNAUTHORIZED);
158    pub const PAYMENT_REQUIRED: Self = Self(http::StatusCode::PAYMENT_REQUIRED);
159    pub const FORBIDDEN: Self = Self(http::StatusCode::FORBIDDEN);
160    pub const NOT_FOUND: Self = Self(http::StatusCode::NOT_FOUND);
161    pub const METHOD_NOT_ALLOWED: Self = Self(http::StatusCode::METHOD_NOT_ALLOWED);
162    pub const NOT_ACCEPTABLE: Self = Self(http::StatusCode::NOT_ACCEPTABLE);
163    pub const PROXY_AUTHENTICATION_REQUIRED: Self =
164        Self(http::StatusCode::PROXY_AUTHENTICATION_REQUIRED);
165    pub const REQUEST_TIMEOUT: Self = Self(http::StatusCode::REQUEST_TIMEOUT);
166    pub const CONFLICT: Self = Self(http::StatusCode::CONFLICT);
167    pub const GONE: Self = Self(http::StatusCode::GONE);
168    pub const LENGTH_REQUIRED: Self = Self(http::StatusCode::LENGTH_REQUIRED);
169    pub const PRECONDITION_FAILED: Self = Self(http::StatusCode::PRECONDITION_FAILED);
170    pub const PAYLOAD_TOO_LARGE: Self = Self(http::StatusCode::PAYLOAD_TOO_LARGE);
171    pub const URI_TOO_LONG: Self = Self(http::StatusCode::URI_TOO_LONG);
172    pub const UNSUPPORTED_MEDIA_TYPE: Self = Self(http::StatusCode::UNSUPPORTED_MEDIA_TYPE);
173    pub const RANGE_NOT_SATISFIABLE: Self = Self(http::StatusCode::RANGE_NOT_SATISFIABLE);
174    pub const EXPECTATION_FAILED: Self = Self(http::StatusCode::EXPECTATION_FAILED);
175    pub const IM_A_TEAPOT: Self = Self(http::StatusCode::IM_A_TEAPOT);
176    pub const MISDIRECTED_REQUEST: Self = Self(http::StatusCode::MISDIRECTED_REQUEST);
177    pub const UNPROCESSABLE_ENTITY: Self = Self(http::StatusCode::UNPROCESSABLE_ENTITY);
178    pub const LOCKED: Self = Self(http::StatusCode::LOCKED);
179    pub const FAILED_DEPENDENCY: Self = Self(http::StatusCode::FAILED_DEPENDENCY);
180    pub const UPGRADE_REQUIRED: Self = Self(http::StatusCode::UPGRADE_REQUIRED);
181    pub const PRECONDITION_REQUIRED: Self = Self(http::StatusCode::PRECONDITION_REQUIRED);
182    pub const TOO_MANY_REQUESTS: Self = Self(http::StatusCode::TOO_MANY_REQUESTS);
183    pub const REQUEST_HEADER_FIELDS_TOO_LARGE: Self =
184        Self(http::StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
185    pub const UNAVAILABLE_FOR_LEGAL_REASONS: Self =
186        Self(http::StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
187    pub const INTERNAL_SERVER_ERROR: Self = Self(http::StatusCode::INTERNAL_SERVER_ERROR);
188    pub const NOT_IMPLEMENTED: Self = Self(http::StatusCode::NOT_IMPLEMENTED);
189    pub const BAD_GATEWAY: Self = Self(http::StatusCode::BAD_GATEWAY);
190    pub const SERVICE_UNAVAILABLE: Self = Self(http::StatusCode::SERVICE_UNAVAILABLE);
191    pub const GATEWAY_TIMEOUT: Self = Self(http::StatusCode::GATEWAY_TIMEOUT);
192    pub const HTTP_VERSION_NOT_SUPPORTED: Self = Self(http::StatusCode::HTTP_VERSION_NOT_SUPPORTED);
193    pub const VARIANT_ALSO_NEGOTIATES: Self = Self(http::StatusCode::VARIANT_ALSO_NEGOTIATES);
194    pub const INSUFFICIENT_STORAGE: Self = Self(http::StatusCode::INSUFFICIENT_STORAGE);
195    pub const LOOP_DETECTED: Self = Self(http::StatusCode::LOOP_DETECTED);
196    pub const NOT_EXTENDED: Self = Self(http::StatusCode::NOT_EXTENDED);
197    pub const NETWORK_AUTHENTICATION_REQUIRED: Self =
198        Self(http::StatusCode::NETWORK_AUTHENTICATION_REQUIRED);
199}
200
201#[cfg(test)]
202mod test {
203    use super::*;
204    use vbs::{BinarySerializer, Serializer, version::StaticVersion};
205
206    type SerializerV01 = Serializer<StaticVersion<0, 1>>;
207    #[test]
208    fn test_status_code() {
209        for code in 100u16.. {
210            // Iterate over all valid status codes, then break.
211            let Ok(status) = StatusCode::try_from(code) else {
212                break;
213            };
214            // Test type conversions.
215            #[cfg(feature = "http-types")]
216            if let Ok(tide_status) = http_types::StatusCode::try_from(code) {
217                assert_eq!(
218                    tide_status,
219                    http_types::StatusCode::try_from(status).unwrap()
220                );
221            }
222            assert_eq!(
223                http::StatusCode::from_u16(code).unwrap(),
224                http::StatusCode::from(status)
225            );
226            assert_eq!(code, u16::from(status));
227
228            // Test binary round trip.
229            assert_eq!(
230                status,
231                SerializerV01::deserialize::<StatusCode>(
232                    &SerializerV01::serialize(&status).unwrap()
233                )
234                .unwrap()
235            );
236
237            // Test JSON round trip, readability, and backwards compatibility.
238            let json = serde_json::to_string(&status).unwrap();
239            assert_eq!(status, serde_json::from_str::<StatusCode>(&json).unwrap());
240            assert_eq!(json, code.to_string());
241            #[cfg(feature = "http-types")]
242            if let Ok(tide_status) = http_types::StatusCode::try_from(status) {
243                assert_eq!(json, serde_json::to_string(&tide_status).unwrap());
244            }
245
246            // Test display.
247            assert_eq!(status.to_string(), code.to_string());
248            #[cfg(feature = "http-types")]
249            if let Ok(tide_status) = http_types::StatusCode::try_from(status) {
250                assert_eq!(status.to_string(), tide_status.to_string());
251            }
252
253            // Test equality.
254            #[cfg(feature = "http-types")]
255            if let Ok(tide_status) = http_types::StatusCode::try_from(status) {
256                assert_eq!(status, tide_status);
257            }
258            assert_eq!(status, http::StatusCode::from(status));
259        }
260
261        // Now iterate over all valid _Tide_ status codes, and ensure the ycan be converted to our
262        // `StatusCode`.
263        #[cfg(feature = "http-types")]
264        for code in 100u16.. {
265            let Ok(status) = http_types::StatusCode::try_from(code) else {
266                break;
267            };
268            assert_eq!(
269                StatusCode::try_from(code).unwrap(),
270                StatusCode::from(status)
271            );
272        }
273
274        // Now iterate over all valid _reqwest_ status codes, and ensure the ycan be converted to
275        // our `StatusCode`.
276        for code in 100u16.. {
277            let Ok(status) = http::StatusCode::from_u16(code) else {
278                break;
279            };
280            assert_eq!(
281                StatusCode::try_from(code).unwrap(),
282                StatusCode::from(status)
283            );
284        }
285    }
286}