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