Skip to main content

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//! use torus_http::response::{HttpResponse, Response};
8//! let response = HttpResponse::new().set_body("hey there").insert_header("Cool-Header", "so cool");
9//! let response = "hello".to_response();
10//! ```
11
12use std::collections::HashMap;
13
14use crate::status::{HttpStatus, ServerErrorResponse};
15
16/// Trait that allows things to be sent back from the server
17pub trait Response {
18    fn to_response(&self) -> HttpResponse;
19}
20
21impl Response for &str {
22    fn to_response(&self) -> HttpResponse {
23        HttpResponse::new_body((*self).to_string(), HttpStatus::default())
24    }
25}
26
27impl Response for String {
28    fn to_response(&self) -> HttpResponse {
29        HttpResponse::new_body(self.clone(), HttpStatus::default())
30    }
31}
32
33impl<S: Response> Response for Option<S> {
34    fn to_response(&self) -> HttpResponse {
35        match self {
36            Some(e) => e.to_response(),
37            None => HttpResponse::new().set_status(HttpStatus::ServerError(
38                ServerErrorResponse::InternalServerError,
39            )),
40        }
41    }
42}
43
44impl<S: Response, E: Response> Response for Result<S, E> {
45    fn to_response(&self) -> HttpResponse {
46        match self {
47            Ok(s) => s.to_response().set_status(HttpStatus::default()),
48            Err(e) => e
49                .to_response()
50                .set_status(ServerErrorResponse::InternalServerError.into()),
51        }
52    }
53}
54
55impl Response for HttpResponse {
56    // TODO: make this not need to clone, can't just take ownership since dyn
57    fn to_response(&self) -> HttpResponse {
58        self.clone()
59    }
60}
61
62/// Struct that contains all the information that will be sent to the client
63#[derive(Eq, PartialEq, Clone, Debug, Default)]
64pub struct HttpResponse {
65    pub headers: HashMap<String, String>,
66    pub status: HttpStatus,
67    pub body: Option<String>,
68}
69
70impl HttpResponse {
71    #[must_use]
72    pub fn new_body(body: String, status: HttpStatus) -> Self {
73        let mut headers: HashMap<String, String> = HashMap::new();
74        headers.insert("Content-Length".into(), body.chars().count().to_string());
75        Self {
76            headers,
77            status,
78            body: Some(body),
79        }
80    }
81
82    #[must_use]
83    pub fn insert_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
84        self.headers.insert(key.into(), value.into());
85        self
86    }
87
88    #[must_use]
89    pub fn set_body(mut self, body: impl Into<String>) -> Self {
90        let body = body.into();
91        let body_len = body.chars().count();
92        self.body.replace(body);
93        self.headers
94            .insert("Content-Length".into(), body_len.to_string());
95        self
96    }
97
98    #[must_use]
99    pub fn set_status(mut self, status: HttpStatus) -> Self {
100        self.status = status;
101        self
102    }
103
104    #[must_use]
105    pub fn new() -> Self {
106        Self {
107            headers: HashMap::new(),
108            status: HttpStatus::default(),
109            body: None,
110        }
111    }
112
113    pub(crate) fn into_bytes(self) -> Vec<u8> {
114        self.into_string().into_bytes()
115    }
116
117    pub(crate) fn into_string(self) -> String {
118        use std::fmt::Write;
119
120        let headers = self.headers.iter().fold(String::new(), |mut acc, (k, v)| {
121            acc.write_fmt(core::format_args!("{k}: {v}\r\n"))
122                .expect("if this fails fuck clippy");
123            acc
124        });
125
126        format!(
127            "HTTP/1.1 {}\r\n{headers}\r\n{}",
128            self.status,
129            self.body.unwrap_or_default()
130        )
131    }
132}