tide_disco/
status.rs

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