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