restless_web/
response.rs

1use once_cell::sync::Lazy;
2use std::collections::HashMap;
3use tokio::io::AsyncWriteExt;
4use tokio::net::tcp::WriteHalf;
5
6#[derive(Debug)]
7pub struct Res {
8    pub outcome: String,
9    status: usize,
10    headers: HashMap<String, String>,
11}
12
13// NOTE: Reference:
14// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status>
15static STATUS_TITLES: Lazy<HashMap<usize, &'static str>> = Lazy::new(|| {
16    HashMap::from([
17        // Information responses
18        (100, "Continue"),
19        (101, "Switching Protocol"),
20        (102, "Processing"),
21        (103, "Early Hints"), // WARN: Experimental
22        // Successful responses
23        (200, "OK"),
24        (201, "Created"),
25        (202, "Accepted"),
26        (203, "Non-Authoritative Information"),
27        (204, "No Content"),
28        (205, "Reset Content"),
29        (206, "Partial Content"),
30        (207, "Multi-Status Status"), // NOTE: WebDAV
31        (208, "Already Reported"),
32        (226, "IM Used"),
33        // Redirection messages
34        (300, "Multiple Choices"),
35        (301, "Moved Permanently"),
36        (302, "Found"),
37        (303, "See Other"),
38        (304, "Not Modified"),
39        (305, "Use Proxy"), // WARN: Deprecated
40        (306, "unused"),
41        (307, "Temporary Redirect"),
42        (308, "Permanent Redirect"),
43        // Client error responses
44        (400, "Bad Request"),
45        (401, "Unauthorized"),
46        (402, "Payment Required"), // NOTE: Experimental
47        (403, "Forbidden"),
48        (404, "Not Found"),
49        (405, "Method Not Allowed"),
50        (406, "Not Acceptable"),
51        (407, "Proxy Authentication Required"),
52        (408, "Request Timeout"),
53        (409, "Conflict"),
54        (410, "Gone"),
55        (411, "Length Required"),
56        (412, "Precondition Failed"),
57        (413, "Payload Too Large"),
58        (414, "URI Too Long"),
59        (415, "Unsupported Media Type"),
60        (416, "Range Not Satisfiable"),
61        (417, "Expectation Failed"),
62        (418, "I'm a teapot"),
63        (421, "Misdirected Request"),
64        (422, "Unprocessable Content"), // NOTE: WebDAV
65        (423, "Locked"),                // NOTE: WebDAV
66        (424, "Failed Dependency"),     // NOTE: WebDAV
67        (425, "Too Early"),             // NOTE: Experimental
68        (426, "Upgrade Required"),
69        (428, "Precondition Required"),
70        (429, "Too Many Requests"),
71        (431, "Request Header Fields Too Large"),
72        (451, "Unavailable For Legal Reasons"),
73        // Server error responses
74        (500, "Internal Server Error"),
75        (501, "Not Implemented"),
76        (502, "Bad Gateway"),
77        (503, "Service Unavailable"),
78        (504, "Gateway Timeout"),
79        (505, "HTTP Version Not Supported"),
80        (506, "Variant Also Negotiates"),
81        (507, "Insufficient Storage"), // NOTE: WebDAV
82        (508, "Loop Detected"),        // NOTE: WebDAV
83        (510, "Not Extended"),
84        (511, "Network Authentication Required"),
85    ])
86});
87
88impl<'a> Res {
89    pub fn new() -> Res {
90        Res {
91            outcome: String::new(),
92            status: 200,
93            headers: HashMap::new(),
94        }
95    }
96
97    pub fn status(mut self, status: usize) -> Res {
98        self.status = status;
99
100        self
101    }
102
103    pub fn set(&'a mut self, header_key: &'a str, header_value: &'a str) -> &'a mut Res {
104        self.headers
105            .insert(header_key.parse().unwrap(), header_value.parse().unwrap());
106
107        self
108    }
109
110    pub fn get(&self, header_key: &str) -> Option<&str> {
111        let header_value = self.headers.get(header_key)?;
112
113        Some(header_value)
114    }
115
116    fn status_title(&'a self) -> Option<&'static str> {
117        let title = *STATUS_TITLES.get(&self.status)?;
118
119        Some(title)
120    }
121
122    pub fn send(mut self, outcome: &str) -> Res {
123        self.outcome.push_str(outcome);
124        self
125    }
126
127    pub async fn send_outcome(self, mut stream: WriteHalf<'_>) {
128        let formatted_headers = self.format_headers();
129        let title = self.status_title().expect("Wrong status code");
130
131        let raw_res = format!(
132            "HTTP/1.1 {} {}\r\n{}\r\n{}",
133            self.status, title, formatted_headers, self.outcome
134        );
135
136        stream.write_all(raw_res.as_bytes()).await.unwrap();
137        stream.flush().await.unwrap();
138    }
139
140    fn format_headers(&'a self) -> String {
141        let mut formatted_headers = String::new();
142
143        for (key, value) in &self.headers {
144            formatted_headers += &format!("{key}: {value}\r\n");
145        }
146
147        formatted_headers
148    }
149}
150
151impl Default for Res {
152    fn default() -> Self {
153        Self::new()
154    }
155}