netc/
status.rs

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}