1use std::collections::HashMap;
12
13use crate::status::HttpStatus;
14
15pub trait Response {
17 fn to_response(&self) -> HttpResponse;
18}
19
20impl<T: AsRef<str>> Response for T {
21 fn to_response(&self) -> HttpResponse {
22 HttpResponse::new().set_body(self.as_ref())
23 }
24}
25
26impl Response for HttpResponse {
27 fn to_response(&self) -> HttpResponse {
29 self.clone()
30 }
31}
32
33#[derive(Eq, PartialEq, Clone, Debug, Default)]
35pub struct HttpResponse {
36 pub headers: HashMap<String, String>,
37 pub status: HttpStatus,
38 pub body: Option<String>,
39}
40
41impl HttpResponse {
42 #[must_use]
43 pub fn new_body(body: String, status: HttpStatus) -> Self {
44 let mut headers: HashMap<String, String> = HashMap::new();
45 headers.insert("Content-Length".into(), body.chars().count().to_string());
46 Self {
47 headers,
48 status,
49 body: Some(body),
50 }
51 }
52
53 #[must_use]
54 pub fn insert_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
55 self.headers.insert(key.into(), value.into());
56 self
57 }
58
59 #[must_use]
60 pub fn set_body(mut self, body: impl Into<String>) -> Self {
61 let body = body.into();
62 let body_len = body.chars().count();
63 self.body.replace(body);
64 self.headers
65 .insert("Content-Length".into(), body_len.to_string());
66 self
67 }
68
69 #[must_use]
70 pub fn set_status(mut self, status: HttpStatus) -> Self {
71 self.status = status;
72 self
73 }
74
75 #[must_use]
76 pub fn new() -> Self {
77 Self {
78 headers: HashMap::new(),
79 status: HttpStatus::Ok,
80 body: None,
81 }
82 }
83
84 pub(crate) fn into_bytes(self) -> Vec<u8> {
85 self.into_string().into_bytes()
86 }
87
88 pub(crate) fn into_string(self) -> String {
89 use std::fmt::Write;
90
91 let headers = self.headers.iter().fold(String::new(), |mut acc, (k, v)| {
92 acc.write_fmt(core::format_args!("{k}: {v}\r\n"))
93 .expect("if this fails fuck clippy");
94 acc
95 });
96
97 format!(
98 "HTTP/1.1 {}\r\n{headers}\r\n{}",
99 self.status,
100 self.body.unwrap_or_default()
101 )
102 }
103}