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
13static STATUS_TITLES: Lazy<HashMap<usize, &'static str>> = Lazy::new(|| {
16 HashMap::from([
17 (100, "Continue"),
19 (101, "Switching Protocol"),
20 (102, "Processing"),
21 (103, "Early Hints"), (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"), (208, "Already Reported"),
32 (226, "IM Used"),
33 (300, "Multiple Choices"),
35 (301, "Moved Permanently"),
36 (302, "Found"),
37 (303, "See Other"),
38 (304, "Not Modified"),
39 (305, "Use Proxy"), (306, "unused"),
41 (307, "Temporary Redirect"),
42 (308, "Permanent Redirect"),
43 (400, "Bad Request"),
45 (401, "Unauthorized"),
46 (402, "Payment Required"), (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"), (423, "Locked"), (424, "Failed Dependency"), (425, "Too Early"), (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 (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"), (508, "Loop Detected"), (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}