axol_http/status.rs
1use std::{convert::Infallible, fmt};
2
3use strum::EnumProperty;
4use thiserror::Error;
5
6/// An error from creating a status code from a u16.
7#[derive(Error, Debug)]
8pub enum StatusCodeError {
9 #[error("infallible")]
10 Infallible(#[from] Infallible),
11 #[error("status code '{0}' not between 100 and 999 inclusive")]
12 OutOfRange(u16),
13}
14
15/// An HTTP status code (`status-code` in RFC 7230 et al.).
16///
17/// Constants are provided for known status codes, including those in the IANA
18/// [HTTP Status Code Registry](
19/// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml).
20///
21/// Status code values in the range 100-999 (inclusive) are supported by this
22/// type. Values in the range 100-599 are semantically classified by the most
23/// significant digit. See [`StatusCode::is_success`], etc. Values above 599
24/// are unclassified but allowed for legacy compatibility, though their use is
25/// discouraged. Applications may interpret such values as protocol errors.
26///
27/// # Examples
28///
29/// ```
30/// use axol_http::StatusCode;
31///
32/// assert_eq!(StatusCode::from_u16(200).unwrap(), StatusCode::Ok);
33/// assert_eq!(StatusCode::NotFound.as_u16(), 404);
34/// assert!(StatusCode::Ok.is_success());
35/// ```
36#[repr(u16)]
37#[derive(
38 Debug,
39 Clone,
40 Copy,
41 Default,
42 PartialEq,
43 Eq,
44 PartialOrd,
45 Ord,
46 Hash,
47 strum::EnumProperty,
48 strum::FromRepr,
49)]
50pub enum StatusCode {
51 /// 100 Continue
52 /// [[RFC7231, Section 6.2.1](https://tools.ietf.org/html/rfc7231#section-6.2.1)]
53 #[strum(props(canonical_reason = "Continue"))]
54 Continue = 100,
55 /// 101 Switching Protocols
56 /// [[RFC7231, Section 6.2.2](https://tools.ietf.org/html/rfc7231#section-6.2.2)]
57 #[strum(props(canonical_reason = "Switching Protocols"))]
58 SwitchingProtocols = 101,
59 /// 102 Processing
60 /// [[RFC2518](https://tools.ietf.org/html/rfc2518)]
61 #[strum(props(canonical_reason = "Processing"))]
62 Processing = 102,
63 /// 200 OK
64 /// [[RFC7231, Section 6.3.1](https://tools.ietf.org/html/rfc7231#section-6.3.1)]
65 #[strum(props(canonical_reason = "OK"))]
66 #[default]
67 Ok = 200,
68 /// 201 Created
69 /// [[RFC7231, Section 6.3.2](https://tools.ietf.org/html/rfc7231#section-6.3.2)]
70 #[strum(props(canonical_reason = "Created"))]
71 Created = 201,
72 /// 202 Accepted
73 /// [[RFC7231, Section 6.3.3](https://tools.ietf.org/html/rfc7231#section-6.3.3)]
74 #[strum(props(canonical_reason = "Accepted"))]
75 Accepted = 202,
76 /// 203 Non-Authoritative Information
77 /// [[RFC7231, Section 6.3.4](https://tools.ietf.org/html/rfc7231#section-6.3.4)]
78 #[strum(props(canonical_reason = "Non Authoritative Information"))]
79 NonAuthoritativeInformation = 203,
80 /// 204 No Content
81 /// [[RFC7231, Section 6.3.5](https://tools.ietf.org/html/rfc7231#section-6.3.5)]
82 #[strum(props(canonical_reason = "No Content"))]
83 NoContent = 204,
84 /// 205 Reset Content
85 /// [[RFC7231, Section 6.3.6](https://tools.ietf.org/html/rfc7231#section-6.3.6)]
86 #[strum(props(canonical_reason = "Reset Content"))]
87 ResetContent = 205,
88 /// 206 Partial Content
89 /// [[RFC7233, Section 4.1](https://tools.ietf.org/html/rfc7233#section-4.1)]
90 #[strum(props(canonical_reason = "Partial Content"))]
91 PartialContent = 206,
92 /// 207 Multi-Status
93 /// [[RFC4918](https://tools.ietf.org/html/rfc4918)]
94 #[strum(props(canonical_reason = "Multi-Status"))]
95 MultiStatus = 207,
96 /// 208 Already Reported
97 /// [[RFC5842](https://tools.ietf.org/html/rfc5842)]
98 #[strum(props(canonical_reason = "Already Reported"))]
99 AlreadyReported = 208,
100
101 /// 226 IM Used
102 /// [[RFC3229](https://tools.ietf.org/html/rfc3229)]
103 #[strum(props(canonical_reason = "IM Used"))]
104 ImUsed = 226,
105
106 /// 300 Multiple Choices
107 /// [[RFC7231, Section 6.4.1](https://tools.ietf.org/html/rfc7231#section-6.4.1)]
108 #[strum(props(canonical_reason = "Multiple Choices"))]
109 MultipleChoices = 300,
110 /// 301 Moved Permanently
111 /// [[RFC7231, Section 6.4.2](https://tools.ietf.org/html/rfc7231#section-6.4.2)]
112 #[strum(props(canonical_reason = "Moved Permanently"))]
113 MovedPermanently = 301,
114 /// 302 Found
115 /// [[RFC7231, Section 6.4.3](https://tools.ietf.org/html/rfc7231#section-6.4.3)]
116 #[strum(props(canonical_reason = "Found"))]
117 Found = 302,
118 /// 303 See Other
119 /// [[RFC7231, Section 6.4.4](https://tools.ietf.org/html/rfc7231#section-6.4.4)]
120 #[strum(props(canonical_reason = "See Other"))]
121 SeeOther = 303,
122 /// 304 Not Modified
123 /// [[RFC7232, Section 4.1](https://tools.ietf.org/html/rfc7232#section-4.1)]
124 #[strum(props(canonical_reason = "Not Modified"))]
125 NotModified = 304,
126 /// 305 Use Proxy
127 /// [[RFC7231, Section 6.4.5](https://tools.ietf.org/html/rfc7231#section-6.4.5)]
128 #[strum(props(canonical_reason = "Use Proxy"))]
129 UseProxy = 305,
130 /// 307 Temporary Redirect
131 /// [[RFC7231, Section 6.4.7](https://tools.ietf.org/html/rfc7231#section-6.4.7)]
132 #[strum(props(canonical_reason = "Temporary Redirect"))]
133 TemporaryRedirect = 307,
134 /// 308 Permanent Redirect
135 /// [[RFC7238](https://tools.ietf.org/html/rfc7238)]
136 #[strum(props(canonical_reason = "Permanent Redirect"))]
137 PermanentRedirect = 308,
138
139 /// 400 Bad Request
140 /// [[RFC7231, Section 6.5.1](https://tools.ietf.org/html/rfc7231#section-6.5.1)]
141 #[strum(props(canonical_reason = "Bad Request"))]
142 BadRequest = 400,
143 /// 401 Unauthorized
144 /// [[RFC7235, Section 3.1](https://tools.ietf.org/html/rfc7235#section-3.1)]
145 #[strum(props(canonical_reason = "Unauthorized"))]
146 Unauthorized = 401,
147 /// 402 Payment Required
148 /// [[RFC7231, Section 6.5.2](https://tools.ietf.org/html/rfc7231#section-6.5.2)]
149 #[strum(props(canonical_reason = "Payment Required"))]
150 PaymentRequired = 402,
151 /// 403 Forbidden
152 /// [[RFC7231, Section 6.5.3](https://tools.ietf.org/html/rfc7231#section-6.5.3)]
153 #[strum(props(canonical_reason = "Forbidden"))]
154 Forbidden = 403,
155 /// 404 Not Found
156 /// [[RFC7231, Section 6.5.4](https://tools.ietf.org/html/rfc7231#section-6.5.4)]
157 #[strum(props(canonical_reason = "Not Found"))]
158 NotFound = 404,
159 /// 405 Method Not Allowed
160 /// [[RFC7231, Section 6.5.5](https://tools.ietf.org/html/rfc7231#section-6.5.5)]
161 #[strum(props(canonical_reason = "Method Not Allowed"))]
162 MethodNotAllowed = 405,
163 /// 406 Not Acceptable
164 /// [[RFC7231, Section 6.5.6](https://tools.ietf.org/html/rfc7231#section-6.5.6)]
165 #[strum(props(canonical_reason = "Not Acceptable"))]
166 NotAcceptable = 406,
167 /// 407 Proxy Authentication Required
168 /// [[RFC7235, Section 3.2](https://tools.ietf.org/html/rfc7235#section-3.2)]
169 #[strum(props(canonical_reason = "Proxy Authentication Required"))]
170 ProxyAuthenticationRequired = 407,
171 /// 408 Request Timeout
172 /// [[RFC7231, Section 6.5.7](https://tools.ietf.org/html/rfc7231#section-6.5.7)]
173 #[strum(props(canonical_reason = "Request Timeout"))]
174 RequestTimeout = 408,
175 /// 409 Conflict
176 /// [[RFC7231, Section 6.5.8](https://tools.ietf.org/html/rfc7231#section-6.5.8)]
177 #[strum(props(canonical_reason = "Conflict"))]
178 Conflict = 409,
179 /// 410 Gone
180 /// [[RFC7231, Section 6.5.9](https://tools.ietf.org/html/rfc7231#section-6.5.9)]
181 #[strum(props(canonical_reason = "Gone"))]
182 Gone = 410,
183 /// 411 Length Required
184 /// [[RFC7231, Section 6.5.10](https://tools.ietf.org/html/rfc7231#section-6.5.10)]
185 #[strum(props(canonical_reason = "Length Required"))]
186 LengthRequired = 411,
187 /// 412 Precondition Failed
188 /// [[RFC7232, Section 4.2](https://tools.ietf.org/html/rfc7232#section-4.2)]
189 #[strum(props(canonical_reason = "Precondition Failed"))]
190 PreconditionFailed = 412,
191 /// 413 Payload Too Large
192 /// [[RFC7231, Section 6.5.11](https://tools.ietf.org/html/rfc7231#section-6.5.11)]
193 #[strum(props(canonical_reason = "Payload Too Large"))]
194 PayloadTooLarge = 413,
195 /// 414 URI Too Long
196 /// [[RFC7231, Section 6.5.12](https://tools.ietf.org/html/rfc7231#section-6.5.12)]
197 #[strum(props(canonical_reason = "URI Too Long"))]
198 UriTooLong = 414,
199 /// 415 Unsupported Media Type
200 /// [[RFC7231, Section 6.5.13](https://tools.ietf.org/html/rfc7231#section-6.5.13)]
201 #[strum(props(canonical_reason = "Unsupported Media Type"))]
202 UnsupportedMediaType = 415,
203 /// 416 Range Not Satisfiable
204 /// [[RFC7233, Section 4.4](https://tools.ietf.org/html/rfc7233#section-4.4)]
205 #[strum(props(canonical_reason = "Range Not Satisfiable"))]
206 RangeNotSatisfiable = 416,
207 /// 417 Expectation Failed
208 /// [[RFC7231, Section 6.5.14](https://tools.ietf.org/html/rfc7231#section-6.5.14)]
209 #[strum(props(canonical_reason = "Expectation Failed"))]
210 ExpectationFailed = 417,
211 /// 418 I'm a teapot
212 /// [curiously not registered by IANA but [RFC2324](https://tools.ietf.org/html/rfc2324)]
213 #[strum(props(canonical_reason = "I'm a teapot"))]
214 ImATeapot = 418,
215
216 /// 421 Misdirected Request
217 /// [RFC7540, Section 9.1.2](http://tools.ietf.org/html/rfc7540#section-9.1.2)
218 #[strum(props(canonical_reason = "Misdirected Request"))]
219 MisdirectedRequest = 421,
220 /// 422 Unprocessable Entity
221 /// [[RFC4918](https://tools.ietf.org/html/rfc4918)]
222 #[strum(props(canonical_reason = "Unprocessable Entity"))]
223 UnprocessableEntity = 422,
224 /// 423 Locked
225 /// [[RFC4918](https://tools.ietf.org/html/rfc4918)]
226 #[strum(props(canonical_reason = "Locked"))]
227 Locked = 423,
228 /// 424 Failed Dependency
229 /// [[RFC4918](https://tools.ietf.org/html/rfc4918)]
230 #[strum(props(canonical_reason = "Failed Dependency"))]
231 FailedDependency = 424,
232
233 /// 426 Upgrade Required
234 /// [[RFC7231, Section 6.5.15](https://tools.ietf.org/html/rfc7231#section-6.5.15)]
235 #[strum(props(canonical_reason = "Upgrade Required"))]
236 UpgradeRequired = 426,
237
238 /// 428 Precondition Required
239 /// [[RFC6585](https://tools.ietf.org/html/rfc6585)]
240 #[strum(props(canonical_reason = "Precondition Required"))]
241 PreconditionRequired = 428,
242 /// 429 Too Many Requests
243 /// [[RFC6585](https://tools.ietf.org/html/rfc6585)]
244 #[strum(props(canonical_reason = "Too Many Requests"))]
245 TooManyRequests = 429,
246
247 /// 431 Request Header Fields Too Large
248 /// [[RFC6585](https://tools.ietf.org/html/rfc6585)]
249 #[strum(props(canonical_reason = "Request Header Fields Too Large"))]
250 RequestHeaderFieldsTooLarge = 431,
251
252 /// 451 Unavailable For Legal Reasons
253 /// [[RFC7725](http://tools.ietf.org/html/rfc7725)]
254 #[strum(props(canonical_reason = "Unavailable For Legal Reasons"))]
255 UnavailableForLegalReasons = 451,
256
257 /// 500 Internal Server Error
258 /// [[RFC7231, Section 6.6.1](https://tools.ietf.org/html/rfc7231#section-6.6.1)]
259 #[strum(props(canonical_reason = "Internal Server Error"))]
260 InternalServerError = 500,
261 /// 501 Not Implemented
262 /// [[RFC7231, Section 6.6.2](https://tools.ietf.org/html/rfc7231#section-6.6.2)]
263 #[strum(props(canonical_reason = "Not Implemented"))]
264 NotImplemented = 501,
265 /// 502 Bad Gateway
266 /// [[RFC7231, Section 6.6.3](https://tools.ietf.org/html/rfc7231#section-6.6.3)]
267 #[strum(props(canonical_reason = "Bad Gateway"))]
268 BadGateway = 502,
269 /// 503 Service Unavailable
270 /// [[RFC7231, Section 6.6.4](https://tools.ietf.org/html/rfc7231#section-6.6.4)]
271 #[strum(props(canonical_reason = "Service Unavailable"))]
272 ServiceUnavailable = 503,
273 /// 504 Gateway Timeout
274 /// [[RFC7231, Section 6.6.5](https://tools.ietf.org/html/rfc7231#section-6.6.5)]
275 #[strum(props(canonical_reason = "Gateway Timeout"))]
276 GatewayTimeout = 504,
277 /// 505 HTTP Version Not Supported
278 /// [[RFC7231, Section 6.6.6](https://tools.ietf.org/html/rfc7231#section-6.6.6)]
279 #[strum(props(canonical_reason = "HTTP Version Not Supported"))]
280 HttpVersionNotSupported = 505,
281 /// 506 Variant Also Negotiates
282 /// [[RFC2295](https://tools.ietf.org/html/rfc2295)]
283 #[strum(props(canonical_reason = "Variant Also Negotiates"))]
284 VariantAlsoNegotiates = 506,
285 /// 507 Insufficient Storage
286 /// [[RFC4918](https://tools.ietf.org/html/rfc4918)]
287 #[strum(props(canonical_reason = "Insufficient Storage"))]
288 InsufficientStorage = 507,
289 /// 508 Loop Detected
290 /// [[RFC5842](https://tools.ietf.org/html/rfc5842)]
291 #[strum(props(canonical_reason = "Loop Detected"))]
292 LoopDetected = 508,
293
294 /// 510 Not Extended
295 /// [[RFC2774](https://tools.ietf.org/html/rfc2774)]
296 #[strum(props(canonical_reason = "Not Extended"))]
297 NotExtended = 510,
298 /// 511 Network Authentication Required
299 /// [[RFC6585](https://tools.ietf.org/html/rfc6585)]
300 #[strum(props(canonical_reason = "Network Authentication Required"))]
301 NetworkAuthenticationRequired = 511,
302
303 /// An unknown (invalid) status code. It is a logic error that will do unexpected things if this is initialized to a valid status code.
304 Other(u16) = u16::MAX,
305}
306
307impl Into<http::StatusCode> for StatusCode {
308 fn into(self) -> http::StatusCode {
309 http::StatusCode::from_u16(self.as_u16())
310 .expect("out of range status code where not expected")
311 }
312}
313
314impl From<http::StatusCode> for StatusCode {
315 fn from(value: http::StatusCode) -> Self {
316 Self::from_u16(value.as_u16()).expect("out of range status code where not expected")
317 }
318}
319
320impl TryFrom<u16> for StatusCode {
321 type Error = StatusCodeError;
322
323 fn try_from(value: u16) -> Result<Self, Self::Error> {
324 Self::from_u16(value)
325 }
326}
327
328impl Into<u16> for StatusCode {
329 fn into(self) -> u16 {
330 self.as_u16()
331 }
332}
333
334impl fmt::Display for StatusCode {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 write!(f, "{} {}", self.as_str(), self.canonical_reason())
337 }
338}
339
340impl StatusCode {
341 /// Converts a u16 to a status code.
342 ///
343 /// The function validates the correctness of the supplied u16. It must be
344 /// greater or equal to 100 and less than 1000.
345 ///
346 /// # Example
347 ///
348 /// ```
349 /// use axol_http::StatusCode;
350 ///
351 /// let ok = StatusCode::from_u16(200).unwrap();
352 /// assert!(matches!(ok, StatusCode::Ok);
353 ///
354 /// let err = StatusCode::from_u16(99);
355 /// assert!(err.is_err());
356 /// ```
357 pub fn from_u16(value: u16) -> Result<Self, StatusCodeError> {
358 if value >= 1000 || value < 100 {
359 return Err(StatusCodeError::OutOfRange(value));
360 }
361 Ok(match Self::from_repr(value) {
362 Some(StatusCode::Other(_)) => StatusCode::Other(u16::MAX),
363 Some(x) => x,
364 None => StatusCode::Other(value),
365 })
366 }
367
368 /// Returns the `u16` corresponding to this `StatusCode`.
369 ///
370 /// # Note
371 ///
372 /// This is the same as the `From<StatusCode>` implementation, but
373 /// included as an inherent method because that implementation doesn't
374 /// appear in rustdocs, as well as a way to force the type instead of
375 /// relying on inference.
376 ///
377 /// # Example
378 ///
379 /// ```
380 /// let status = axol_http::StatusCode::Ok;
381 /// assert_eq!(status.as_u16(), 200);
382 /// ```
383 pub fn as_u16(&self) -> u16 {
384 match self {
385 Self::Other(x) => *x,
386 x => x.discriminant(),
387 }
388 }
389
390 /// Returns a &str representation of the `StatusCode`
391 ///
392 /// The return value only includes a numerical representation of the
393 /// status code. The canonical reason is not included.
394 ///
395 /// # Example
396 ///
397 /// ```
398 /// let status = axol_http::StatusCode::Ok;
399 /// assert_eq!(status.as_str(), "200");
400 /// ```
401 pub fn as_str(&self) -> &'static str {
402 let inner: http::StatusCode = (*self).into();
403 // SAFETY: http::StatusCode::as_str is returning a reference to a const str.
404 // to maintain safety, we must pin http crate version
405 unsafe { std::mem::transmute(inner.as_str()) }
406 }
407
408 /// Get the standardised `reason-phrase` for this status code.
409 ///
410 /// This is mostly here for servers writing responses, but could potentially have application
411 /// at other times.
412 ///
413 /// The reason phrase is defined as being exclusively for human readers. You should avoid
414 /// deriving any meaning from it at all costs.
415 ///
416 /// Bear in mind also that in HTTP/2.0 and HTTP/3.0 the reason phrase is abolished from
417 /// transmission, and so this canonical reason phrase really is the only reason phrase you’ll
418 /// find.
419 ///
420 /// This is empty for `Other` or invalid status codes.
421 ///
422 /// # Example
423 ///
424 /// ```
425 /// let status = axol_http::StatusCode::OK;
426 /// assert_eq!(status.canonical_reason(), "OK");
427 /// ```
428 pub fn canonical_reason(&self) -> &'static str {
429 self.get_str("canonical_reason").unwrap_or_default()
430 }
431
432 /// Check if status is within 100-199.
433 pub fn is_informational(&self) -> bool {
434 200 > self.discriminant() && self.discriminant() >= 100
435 }
436
437 /// Check if status is within 200-299.
438 pub fn is_success(&self) -> bool {
439 300 > self.discriminant() && self.discriminant() >= 200
440 }
441
442 /// Check if status is within 300-399.
443 pub fn is_redirection(&self) -> bool {
444 400 > self.discriminant() && self.discriminant() >= 300
445 }
446
447 /// Check if status is within 400-499.
448 pub fn is_client_error(&self) -> bool {
449 500 > self.discriminant() && self.discriminant() >= 400
450 }
451
452 /// Check if status is within 500-599.
453 pub fn is_server_error(&self) -> bool {
454 600 > self.discriminant() && self.discriminant() >= 500
455 }
456
457 // https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting
458 fn discriminant(&self) -> u16 {
459 unsafe { *(self as *const Self as *const u16) }
460 }
461}