1use core::fmt;
2use enum_map::Enum;
3use memchr::memmem;
4use std::path::PathBuf;
5
6#[derive(PartialEq, Debug, Clone, Copy, Enum)]
7pub enum Method {
8 GET,
9 POST,
10}
11
12#[derive(PartialEq, Debug, Clone, Copy)]
13pub enum Version {
14 V0_9,
15 V1_0,
16 V1_1,
17 V2_0,
18}
19
20pub type StatusCode = http::StatusCode;
21
22#[derive(Debug, PartialEq, Eq, Clone, Copy)]
23pub enum MimeType {
24 HTML,
25 PlainText,
26 JavaScript,
27 Json,
28 CSS,
29 SVG,
30 Icon,
31 Binary,
32 JPEG,
33 PNG,
34 Custom(&'static str),
35}
36
37impl TryFrom<&[u8]> for Method {
38 type Error = String;
39 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
40 match value {
41 b"GET" => Ok(Method::GET),
42 b"POST" => Ok(Method::POST),
43 _ => Err("Invalid Method".to_owned()),
44 }
45 }
46}
47
48impl TryFrom<&[u8]> for Version {
49 type Error = String;
50 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
51 match value {
52 b"HTTP/1.0" => Ok(Version::V1_0),
53 b"HTTP/1.1" => Ok(Version::V1_1),
54 b"HTTP/2.2" => Ok(Version::V2_0),
55 _ => Err("invalid version".to_owned()),
56 }
57 }
58}
59
60#[derive(Debug, PartialEq, Clone)]
62pub struct Header {
63 pub key: String,
64 pub value: String,
65}
66
67pub trait IntoHeader {
68 fn into_header(self) -> Header;
69}
70
71impl IntoHeader for Header {
72 fn into_header(self) -> Header {
73 self
74 }
75}
76
77impl IntoHeader for &Header {
78 fn into_header(self) -> Header {
79 self.clone()
80 }
81}
82
83impl IntoHeader for (&str, &str) {
84 fn into_header(self) -> Header {
85 let (key, value) = self;
86 Header::new(key, value)
87 }
88}
89
90impl IntoHeader for (&str, &String) {
91 fn into_header(self) -> Header {
92 let (key, value) = self;
93 Header::new(key, value)
94 }
95}
96
97impl fmt::Display for Method {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::GET => write!(f, "GET"),
101 Self::POST => write!(f, "POST"),
102 }
103 }
104}
105impl From<&MimeType> for String {
106 fn from(mime: &MimeType) -> String {
107 let media_type = mime.media_type();
108 let charset = mime.charset();
109 let boundary = mime.boundary();
110 if let (Some(boundary), Some(charset)) = (boundary, charset) {
111 format!("{}; charset={}; boundary={}", media_type, charset, boundary)
112 } else if let (None, Some(charset)) = (boundary, charset) {
113 format!("{}; charset={}", media_type, charset)
114 } else {
115 format!("{};", media_type)
116 }
117 }
118}
119
120impl From<MimeType> for String {
121 fn from(mime: MimeType) -> String {
122 String::from(&mime)
123 }
124}
125
126impl From<PathBuf> for MimeType {
127 fn from(value: PathBuf) -> Self {
128 if let Some(ext) = value.extension() {
129 return MimeType::from_extension(&ext.to_string_lossy());
130 } else {
131 MimeType::PlainText
132 }
133 }
134}
135
136impl MimeType {
137 pub fn media_type(&self) -> &str {
138 match self {
139 Self::PlainText => "text/plain",
140 Self::HTML => "text/html",
141 Self::JavaScript => "text/javascript",
142 Self::Json => "application/json",
143 Self::CSS => "text/css",
144 Self::SVG => "image/svg+xml",
145 Self::Icon => "image/vnd.microsoft.icon",
146 Self::Binary => "application/octet-stream",
147 Self::JPEG => "image/jpeg",
148 Self::PNG => "image/png",
149 Self::Custom(str) => str,
150 }
151 }
152
153 pub fn charset(&self) -> Option<&str> {
154 match self {
155 Self::SVG | Self::Icon | Self::Binary | Self::JPEG => None,
156 _ => Some("utf-8"),
157 }
158 }
159
160 pub fn boundary(&self) -> Option<&str> {
161 None
162 }
163
164 pub fn from_extension(extension: &str) -> Self {
165 match extension {
166 "json" => Self::Json,
167 "js" => Self::JavaScript,
168 "css" => Self::CSS,
169 "svg" => Self::SVG,
170 "ico" => Self::Icon,
171 "bin" => Self::Binary,
172 "html" => Self::HTML,
173 "jpeg" | "jpg" => Self::JPEG,
174 "png" => Self::PNG,
175 _ => Self::PlainText,
176 }
177 }
178}
179
180impl TryFrom<String> for Header {
181 type Error = &'static str;
182 fn try_from(string: String) -> Result<Self, Self::Error> {
183 let split: Vec<&str> = string.split(": ").collect();
184 match split.len().cmp(&2) {
185 std::cmp::Ordering::Equal => {
186 let key = split[0].to_lowercase();
187 let value = split[1].to_string();
188 Ok(Self { key, value })
189 }
190 std::cmp::Ordering::Greater => Err("Too many ': '"),
191 std::cmp::Ordering::Less => Err("Invalid Key Value Pair"),
192 }
193 }
194}
195
196impl TryFrom<&String> for Header {
197 type Error = &'static str;
198 fn try_from(string: &String) -> Result<Self, Self::Error> {
199 let split: Vec<&str> = string.split(": ").collect();
200 match split.len().cmp(&2) {
201 std::cmp::Ordering::Equal => {
202 let key = split[0].to_lowercase();
203 let value = split[1].to_string();
204 Ok(Self { key, value })
205 }
206 std::cmp::Ordering::Greater => Err("Too many ': '"),
207 std::cmp::Ordering::Less => Err("Invalid Key Value Pair"),
208 }
209 }
210}
211
212impl TryFrom<&str> for Header {
213 type Error = &'static str;
214 fn try_from(string: &str) -> Result<Self, Self::Error> {
215 let split: Vec<&str> = string.split(": ").collect();
216 match split.len().cmp(&2) {
217 std::cmp::Ordering::Equal => {
218 let key = split[0].to_lowercase();
219 let value = split[1].to_string();
220 Ok(Self { key, value })
221 }
222 std::cmp::Ordering::Greater => Err("Too many ': '"),
223 std::cmp::Ordering::Less => Err("Invalid Key Value Pair"),
224 }
225 }
226}
227
228impl TryFrom<&[u8]> for Header {
229 type Error = &'static str;
230 fn try_from(h_str: &[u8]) -> Result<Self, Self::Error> {
231 let sep = b": ";
232 let key_end = memmem::find(h_str, sep).ok_or("missing ': '")?;
233 let value_start = key_end + sep.len();
234 let key = &h_str[0..key_end];
235 let value = &h_str[value_start..h_str.len()];
236 Ok(Self {
237 key: String::from_utf8_lossy(key).to_string().to_lowercase(),
238 value: String::from_utf8_lossy(value).to_string(),
239 })
240 }
241}
242
243impl From<&Header> for String {
244 fn from(header: &Header) -> String {
245 format!("{}: {}", header.key, header.value)
246 }
247}
248
249impl From<Header> for String {
250 fn from(header: Header) -> String {
251 format!("{}: {}", header.key, header.value)
252 }
253}
254
255impl Header {
256 pub fn new(key: &str, value: &str) -> Header {
257 Header {
258 key: key.to_lowercase(),
259 value: value.to_string(),
260 }
261 }
262 pub fn new_server() -> Vec<Header> {
264 const VERSION: &str = env!("CARGO_PKG_VERSION");
265 const NAME: &str = env!("CARGO_PKG_NAME");
266 vec![Header {
267 key: "Server".to_string(),
268 value: format!("{NAME} {VERSION}"),
269 }]
270 }
271}
272
273impl fmt::Display for Version {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 let s = match self {
276 Version::V0_9 => "",
277 Version::V1_0 => "HTTP/1.0",
278 Version::V1_1 => "HTTP/1.1",
279 Version::V2_0 => "HTTP/2",
280 };
281 write!(f, "{}", s)
282 }
283}
284
285impl From<Version> for &str {
286 fn from(version: Version) -> &'static str {
287 match version {
288 Version::V0_9 => "",
289 Version::V1_0 => "HTTP/1.0",
290 Version::V1_1 => "HTTP/1.1",
291 Version::V2_0 => "HTTP/2",
292 }
293 }
294}
295
296impl From<Version> for String {
297 fn from(version: Version) -> String {
298 version.to_string()
299 }
300}