1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3pub struct Status(pub u16);
4
5impl Status {
6 pub const SWITCHING_PROTOCOLS: Status = Status(101);
8 pub const OK: Status = Status(200);
9 pub const CREATED: Status = Status(201);
10 pub const NO_CONTENT: Status = Status(204);
11 pub const FOUND: Status = Status(302);
12 pub const SEE_OTHER: Status = Status(303);
13 pub const NOT_MODIFIED: Status = Status(304);
14 pub const BAD_REQUEST: Status = Status(400);
15 pub const UNAUTHORIZED: Status = Status(401);
16 pub const FORBIDDEN: Status = Status(403);
17 pub const NOT_FOUND: Status = Status(404);
18 pub const METHOD_NOT_ALLOWED: Status = Status(405);
19 pub const PAYLOAD_TOO_LARGE: Status = Status(413);
20 pub const PAGE_EXPIRED: Status = Status(419);
23 pub const UNPROCESSABLE: Status = Status(422);
24 pub const TOO_MANY_REQUESTS: Status = Status(429);
25 pub const INTERNAL_ERROR: Status = Status(500);
26 pub const SERVICE_UNAVAILABLE: Status = Status(503);
27
28 pub fn code(self) -> u16 {
29 self.0
30 }
31
32 pub fn is_success(self) -> bool {
33 (200..300).contains(&self.0)
34 }
35
36 pub fn is_redirect(self) -> bool {
37 (300..400).contains(&self.0)
38 }
39
40 pub fn is_error(self) -> bool {
41 self.0 >= 400
42 }
43
44 pub fn is_bodyless(self) -> bool {
46 matches!(self.0, 204 | 304) || (100..200).contains(&self.0)
47 }
48
49 pub fn reason(self) -> &'static str {
50 match self.0 {
51 100 => "Continue",
52 101 => "Switching Protocols",
53 200 => "OK",
54 201 => "Created",
55 202 => "Accepted",
56 204 => "No Content",
57 301 => "Moved Permanently",
58 302 => "Found",
59 303 => "See Other",
60 304 => "Not Modified",
61 307 => "Temporary Redirect",
62 308 => "Permanent Redirect",
63 400 => "Bad Request",
64 401 => "Unauthorized",
65 403 => "Forbidden",
66 404 => "Not Found",
67 405 => "Method Not Allowed",
68 408 => "Request Timeout",
69 409 => "Conflict",
70 413 => "Payload Too Large",
71 415 => "Unsupported Media Type",
72 419 => "Page Expired",
73 422 => "Unprocessable Content",
74 429 => "Too Many Requests",
75 500 => "Internal Server Error",
76 501 => "Not Implemented",
77 502 => "Bad Gateway",
78 503 => "Service Unavailable",
79 504 => "Gateway Timeout",
80 _ => "Unknown",
81 }
82 }
83}
84
85impl From<u16> for Status {
86 fn from(code: u16) -> Self {
87 Status(code)
88 }
89}
90
91impl std::fmt::Display for Status {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 write!(f, "{} {}", self.0, self.reason())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn every_status_the_framework_sends_has_a_reason_phrase() {
103 for status in [
106 Status::SWITCHING_PROTOCOLS,
107 Status::OK,
108 Status::CREATED,
109 Status::NO_CONTENT,
110 Status::FOUND,
111 Status::SEE_OTHER,
112 Status::NOT_MODIFIED,
113 Status::BAD_REQUEST,
114 Status::UNAUTHORIZED,
115 Status::FORBIDDEN,
116 Status::NOT_FOUND,
117 Status::METHOD_NOT_ALLOWED,
118 Status::PAYLOAD_TOO_LARGE,
119 Status::PAGE_EXPIRED,
120 Status::UNPROCESSABLE,
121 Status::TOO_MANY_REQUESTS,
122 Status::INTERNAL_ERROR,
123 Status::SERVICE_UNAVAILABLE,
124 ] {
125 assert_ne!(status.reason(), "Unknown", "{} has no reason phrase", status.code());
126 }
127 }
128
129 #[test]
130 fn an_informational_status_carries_no_body() {
131 assert!(Status::SWITCHING_PROTOCOLS.is_bodyless());
132 assert!(Status::NO_CONTENT.is_bodyless());
133 assert!(!Status::OK.is_bodyless());
134 }
135}