1use std::{convert::TryFrom, fmt, str::FromStr};
2
3use crate::{Error, Version};
4
5#[derive(PartialEq, Eq, Debug, Clone)]
6pub struct Status {
7 version: Version,
8 code: StatusCode,
9 reason: String,
10}
11
12impl Status {
13 pub fn status_code(&self) -> StatusCode {
14 self.code
15 }
16
17 pub fn version(&self) -> Version {
18 self.version
19 }
20
21 pub fn reason(&self) -> &str {
22 &self.reason
23 }
24
25 pub fn as_u16(&self) -> u16 {
26 self.code.0
27 }
28}
29
30impl<T, U, V> TryFrom<(T, U, V)> for Status
31where
32 Version: TryFrom<T>,
33 V: ToString,
34 StatusCode: From<U>,
35{
36 type Error = Error;
37
38 fn try_from(status: (T, U, V)) -> Result<Status, Error> {
39 Ok(Status {
40 version: Version::try_from(status.0).map_err(|_| Error::StatusErr)?,
41 code: StatusCode::from(status.1),
42 reason: status.2.to_string(),
43 })
44 }
45}
46
47impl FromStr for Status {
48 type Err = Error;
49
50 fn from_str(status_line: &str) -> Result<Status, Error> {
51 let mut status_line = status_line.trim().splitn(3, ' ');
52
53 let version: Version = status_line.next().ok_or(Error::EmptyVersion)?.parse()?;
54 let code: StatusCode = status_line.next().ok_or(Error::EmptyStatus)?.parse()?;
55 let reason = status_line
56 .next()
57 .unwrap_or_else(|| code.reason().unwrap_or("Unknown"));
58
59 Status::try_from((version, u16::from(code), reason))
60 }
61}
62
63#[derive(Debug, PartialEq, Eq, Clone, Copy)]
64pub struct StatusCode(u16);
65
66impl StatusCode {
67 pub fn from_u16(code: u16) -> Result<StatusCode, Error> {
68 if !(100..600).contains(&code) {
69 return Err(Error::InvalidStatusCode(code));
70 }
71
72 Ok(StatusCode(code))
73 }
74
75 pub fn is_info(self) -> bool {
76 self.0 >= 100 && self.0 < 200
77 }
78
79 pub fn is_success(self) -> bool {
80 self.0 >= 200 && self.0 < 300
81 }
82
83 pub fn is_redirect(self) -> bool {
84 self.0 >= 300 && self.0 < 400
85 }
86
87 pub fn is_client_err(self) -> bool {
88 self.0 >= 400 && self.0 < 500
89 }
90
91 pub fn is_server_err(self) -> bool {
92 self.0 >= 500 && self.0 < 600
93 }
94
95 pub fn is_nobody(self) -> bool {
96 self.is_info() || self.0 == 204 || self.0 == 304
97 }
98
99 pub fn is<F: FnOnce(u16) -> bool>(self, f: F) -> bool {
100 f(self.0)
101 }
102
103 pub fn reason(self) -> Option<&'static str> {
104 match self.0 {
105 100 => Some("Continue"),
106 101 => Some("Switching Protocols"),
107 102 => Some("Processing"),
108 103 => Some("Early Hints"),
109 200 => Some("OK"),
110 201 => Some("Created"),
111 202 => Some("Accepted"),
112 203 => Some("Non Authoritative Information"),
113 204 => Some("No Content"),
114 205 => Some("Reset Content"),
115 206 => Some("Partial Content"),
116 207 => Some("Multi-Status"),
117 208 => Some("Already Reported"),
118 226 => Some("IM Used"),
119 300 => Some("Multiple Choices"),
120 301 => Some("Moved Permanently"),
121 302 => Some("Found"),
122 303 => Some("See Other"),
123 304 => Some("Not Modified"),
124 305 => Some("Use Proxy"),
125 306 => Some("Switch Proxy"),
126 307 => Some("Temporary Redirect"),
127 308 => Some("Permanent Redirect"),
128 400 => Some("Bad Request"),
129 401 => Some("Unauthorized"),
130 402 => Some("Payment Required"),
131 403 => Some("Forbidden"),
132 404 => Some("Not Found"),
133 405 => Some("Method Not Allowed"),
134 406 => Some("Not Acceptable"),
135 407 => Some("Proxy Authentication Required"),
136 408 => Some("Request Timeout"),
137 409 => Some("Conflict"),
138 410 => Some("Gone"),
139 411 => Some("Length Required"),
140 412 => Some("Precondition Failed"),
141 413 => Some("Payload Too Large"),
142 414 => Some("URI Too Long"),
143 415 => Some("Unsupported Media Type"),
144 416 => Some("Range Not Satisfiable"),
145 417 => Some("Expectation Failed"),
146 418 => Some("I'm a teapot"),
147 421 => Some("Misdirected Request"),
148 422 => Some("Unprocessable Entity"),
149 423 => Some("Locked"),
150 424 => Some("Failed Dependency"),
151 425 => Some("Too Early"),
152 426 => Some("Upgrade Required"),
153 428 => Some("Precondition Required"),
154 429 => Some("Too Many Requests"),
155 431 => Some("Request Header Fields Too Large"),
156 451 => Some("Unavailable For Legal Reasons"),
157 500 => Some("Internal Server Error"),
158 501 => Some("Not Implemented"),
159 502 => Some("Bad Gateway"),
160 503 => Some("Service Unavailable"),
161 504 => Some("Gateway Timeout"),
162 505 => Some("HTTP Version Not Supported"),
163 506 => Some("Variant Also Negotiates"),
164 507 => Some("Insufficient Storage"),
165 508 => Some("Loop Detected"),
166 510 => Some("Not Extended"),
167 511 => Some("Network Authentication Required"),
168 _ => None,
169 }
170 }
171
172 pub fn as_u16(self) -> u16 {
173 self.0
174 }
175}
176
177impl From<StatusCode> for u16 {
178 fn from(code: StatusCode) -> Self {
179 code.0
180 }
181}
182
183impl From<u16> for StatusCode {
184 fn from(code: u16) -> Self {
185 StatusCode(code)
186 }
187}
188
189impl fmt::Display for StatusCode {
190 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191 write!(f, "{}", self.0)
192 }
193}
194
195impl FromStr for StatusCode {
196 type Err = Error;
197
198 fn from_str(s: &str) -> Result<StatusCode, Error> {
199 StatusCode::from_u16(s.parse()?)
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 const STATUS_LINE: &str = "HTTP/1.1 200 OK";
208 const VERSION: Version = Version::Http11;
209 const CODE: u16 = 200;
210 const REASON: &str = "OK";
211 const CODE_S: StatusCode = StatusCode(200);
212
213 #[test]
214 fn status_code_new() {
215 assert_eq!(StatusCode::from_u16(200), StatusCode::from_u16(200));
216 assert_ne!(StatusCode::from_u16(400), Ok(StatusCode(404)));
217 }
218
219 #[test]
220 fn status_code_info() {
221 for i in 100..200 {
222 assert!(StatusCode(i).is_info())
223 }
224
225 for i in (0..1000).filter(|&i| i < 100 || i >= 200) {
226 assert!(!StatusCode(i).is_info())
227 }
228 }
229
230 #[test]
231 fn status_code_success() {
232 for i in 200..300 {
233 assert!(StatusCode(i).is_success())
234 }
235
236 for i in (0..1000).filter(|&i| i < 200 || i >= 300) {
237 assert!(!StatusCode(i).is_success())
238 }
239 }
240
241 #[test]
242 fn status_code_redirect() {
243 for i in 300..400 {
244 assert!(StatusCode(i).is_redirect())
245 }
246
247 for i in (0..1000).filter(|&i| i < 300 || i >= 400) {
248 assert!(!StatusCode(i).is_redirect())
249 }
250 }
251
252 #[test]
253 fn status_code_client_err() {
254 for i in 400..500 {
255 assert!(StatusCode(i).is_client_err())
256 }
257
258 for i in (0..1000).filter(|&i| i < 400 || i >= 500) {
259 assert!(!StatusCode(i).is_client_err())
260 }
261 }
262
263 #[test]
264 fn status_code_server_err() {
265 for i in 500..600 {
266 assert!(StatusCode(i).is_server_err())
267 }
268
269 for i in (0..1000).filter(|&i| i < 500 || i >= 600) {
270 assert!(!StatusCode(i).is_server_err())
271 }
272 }
273
274 #[test]
275 fn status_code_is() {
276 let check = |i| i % 3 == 0;
277
278 let code_1 = StatusCode(200);
279 let code_2 = StatusCode(300);
280
281 assert!(!code_1.is(check));
282 assert!(code_2.is(check));
283 }
284
285 #[test]
286 fn status_code_reason() {
287 assert_eq!(StatusCode(200).reason(), Some("OK"));
288 assert_eq!(StatusCode(400).reason(), Some("Bad Request"));
289 }
290
291 #[test]
292 fn status_code_from() {
293 assert_eq!(StatusCode::from(200), StatusCode(200));
294 }
295
296 #[test]
297 fn u16_from_status_code() {
298 assert_eq!(u16::from(CODE_S), 200);
299 }
300
301 #[test]
302 fn status_code_display() {
303 let code = format!("Status Code: {}", StatusCode(200));
304 const CODE_EXPECT: &str = "Status Code: 200";
305
306 assert_eq!(code, CODE_EXPECT);
307 }
308
309 #[test]
310 fn status_code_from_str() {
311 assert_eq!("200".parse::<StatusCode>(), Ok(StatusCode(200)));
312 assert_ne!("400".parse::<StatusCode>(), Ok(StatusCode(404)));
313 }
314
315 #[test]
316 fn status_from() {
317 let status = Status::try_from((VERSION, CODE, REASON)).unwrap();
318
319 assert_eq!(status.version(), VERSION);
320 assert_eq!(status.status_code(), CODE_S);
321 assert_eq!(status.reason(), REASON);
322 }
323
324 #[test]
325 fn status_from_str() {
326 let status = STATUS_LINE.parse::<Status>().unwrap();
327
328 assert_eq!(status.version(), VERSION);
329 assert_eq!(status.status_code(), CODE_S);
330 assert_eq!(status.reason(), REASON);
331 }
332}