tosic_http/
response.rs

1//! This module contains the definition of the [`HttpResponse`] struct and its associated methods.
2
3use crate::body::message_body::MessageBody;
4use crate::body::BoxBody;
5use crate::request::HttpRequest;
6use crate::traits::responder::Responder;
7use http::StatusCode;
8use serde::Serialize;
9use std::fmt::Debug;
10use std::io::Write;
11use tokio::io;
12
13#[derive(Clone, Debug)]
14/// The HTTP response struct
15pub struct HttpResponse<Body = BoxBody> {
16    pub(crate) body: Body,
17    pub(crate) status_code: StatusCode,
18    pub(crate) headers: http::HeaderMap,
19    pub(crate) version: http::Version,
20}
21
22impl HttpResponse<BoxBody> {
23    /// Create a new instance of the `HttpResponse` struct with a specific status code.
24    pub fn new<T: TryInto<StatusCode>>(status_code: T) -> Self
25    where
26        T::Error: Debug,
27    {
28        Self {
29            body: BoxBody::new(()),
30            status_code: status_code.try_into().unwrap(),
31            headers: http::HeaderMap::new(),
32            version: http::Version::HTTP_11,
33        }
34    }
35
36    /// Get the status code of the response
37    pub fn status_code(&self) -> StatusCode {
38        self.status_code
39    }
40
41    /// Get the headers of the response
42    pub fn headers(&self) -> &http::HeaderMap {
43        &self.headers
44    }
45
46    /// Get the mutable headers of the response
47    pub fn headers_mut(&mut self) -> &mut http::HeaderMap {
48        &mut self.headers
49    }
50
51    /// Set the body of the response
52    pub fn set_body(self, body: BoxBody) -> Self {
53        Self { body, ..self }
54    }
55
56    /// Convert the response to bytes
57    pub(crate) fn to_bytes(&self) -> io::Result<Vec<u8>> {
58        let mut response_bytes = Vec::new();
59
60        let status_line = format!(
61            "{:?} {} {}\r\n",
62            self.version,
63            self.status_code.as_str(),
64            self.status_code.canonical_reason().unwrap_or("Unknown")
65        );
66
67        response_bytes.write_all(status_line.as_bytes())?;
68
69        for (key, value) in &self.headers {
70            let header_line = format!("{}: {}\r\n", key, value.to_str().unwrap());
71            response_bytes.write_all(header_line.as_bytes())?;
72        }
73
74        response_bytes.write_all(b"\r\n")?;
75
76        let body = self.clone().body.try_into_bytes().unwrap_or_default(); // TODO: There is probably a better way to handle this
77
78        response_bytes.write_all(&body)?;
79
80        Ok(response_bytes)
81    }
82
83    /// Sets the body for the response.
84    ///
85    /// The provided `body` must implement both [`MessageBody`] and [`Clone`], and it must have a `'static` lifetime to be compatible.
86    ///
87    /// ## Note
88    ///
89    /// Sending tuples as the body is supported, but it's important to note that the layout of tuple elements may be unpredictable when serialized to bytes, as Rust does not guarantee element ordering in tuples.
90    ///
91    /// # Parameters
92    ///
93    /// - `body`: The body content to be set for the response, implementing [`MessageBody`] and [`Clone`].
94    ///
95    /// # Returns
96    ///
97    /// Returns `Self`, allowing for method chaining.
98    ///
99    /// # Example
100    ///
101    /// ```
102    /// # use tosic_http::response::HttpResponse;
103    ///
104    /// let response = HttpResponse::new(200)
105    ///     .body("Hello, world!");
106    /// ```
107    pub fn body<B>(mut self, body: B) -> Self
108    where
109        B: MessageBody + Clone + 'static,
110    {
111        self.body = BoxBody::new(body);
112
113        self
114    }
115
116    #[allow(non_snake_case)]
117    /// Creates a new `HttpResponse` with a status code of 200 (OK).
118    pub fn Ok() -> Self {
119        Self::new(200)
120    }
121
122    /// Creates a new `HttpResponse` with a status code of 201 (Created).
123    pub fn json<B>(mut self, data: &B) -> Self
124    where
125        B: Serialize,
126    {
127        let json = serde_json::to_string(data).expect("Unable to Serialize");
128        self.headers_mut()
129            .insert("Content-Type", "application/json".parse().unwrap());
130        self.body(json)
131    }
132}
133
134impl Responder for HttpResponse<BoxBody> {
135    type Body = BoxBody;
136
137    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
138        self
139    }
140}
141
142impl Responder for String {
143    type Body = BoxBody;
144
145    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
146        HttpResponse::new(200).set_body(BoxBody::new(self))
147    }
148}
149
150impl Responder for &'static str {
151    type Body = BoxBody;
152
153    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
154        HttpResponse::new(200).set_body(BoxBody::new(self))
155    }
156}