torus_http/
response.rs

1//! Exports a trait for generating http responses, with a simple default
2//! implementation for string bodies.
3//!
4//! # Example:
5//!
6//! ```rust
7//! let response = HttpResponse::new().set_body("hey there").insert_header("Cool-Header", "so cool");
8//! let response = "hello".into_response();
9//! ```
10
11use std::collections::HashMap;
12
13use crate::status::HttpStatus;
14
15/// Trait that allows things to be sent back from the server
16pub 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    // TODO: make this not need to clone, can't just take ownership since dyn
28    fn to_response(&self) -> HttpResponse {
29        self.clone()
30    }
31}
32
33/// Struct that contains all the information that will be sent to the client
34#[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}