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
26/// Struct that contains all the information that will be sent to the client
27#[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}