Skip to main content

rustolio_utils/http/response/
builder.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11use crate::bytes::Bytes;
12
13use crate::bytes::IntoUtf8Bytes;
14use crate::bytes::Utf8Bytes;
15
16use super::{Error, HeaderName, Outgoing, Response, Result, StatusCode, Version};
17
18#[cfg(not(target_arch = "wasm32"))]
19pub struct Builder<B> {
20    version: Version,
21    status: StatusCode,
22    headers: Vec<(Utf8Bytes, Utf8Bytes)>,
23    body: B,
24}
25
26#[cfg(not(target_arch = "wasm32"))]
27impl Default for Builder<()> {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl Builder<()> {
34    pub fn new() -> Self {
35        Builder {
36            version: Version::HTTP_11,
37            status: StatusCode::OK,
38            headers: Vec::new(),
39            body: (),
40        }
41    }
42}
43
44#[cfg(not(target_arch = "wasm32"))]
45impl<B> Builder<B> {
46    pub fn status(self, status: StatusCode) -> Self {
47        Self { status, ..self }
48    }
49
50    pub fn header(mut self, key: impl IntoUtf8Bytes, value: impl IntoUtf8Bytes) -> Self {
51        self.add_header(key, value);
52        self
53    }
54
55    fn add_header(&mut self, key: impl IntoUtf8Bytes, value: impl IntoUtf8Bytes) -> bool {
56        let key = key.into_utf8_bytes();
57        if self.headers.iter().any(|(k, _)| k == &key) {
58            return false;
59        }
60        self.headers.push((key, value.into_utf8_bytes()));
61        true
62    }
63}
64
65#[cfg(not(target_arch = "wasm32"))]
66impl Builder<()> {
67    pub fn empty(self) -> Builder<Outgoing> {
68        let body = Outgoing::empty();
69        Builder {
70            version: self.version,
71            status: self.status,
72            headers: self.headers,
73            body,
74        }
75    }
76
77    pub fn bytes(mut self, body: Bytes) -> Builder<Outgoing> {
78        self.add_header(HeaderName::CONTENT_TYPE, "application/octet-stream");
79        let body = Outgoing::from_bytes(body);
80        Builder {
81            version: self.version,
82            status: self.status,
83            headers: self.headers,
84            body,
85        }
86    }
87
88    // TODO: Use streaming instead of bytes
89    pub fn encoded<T: crate::prelude::Encode>(self, body: &T) -> Result<Builder<Outgoing>> {
90        let encoded = crate::bytes::encoding::encode_to_bytes(body).map_err(|_| Error::Parse)?;
91        Ok(self.bytes(encoded))
92    }
93
94    pub fn html(mut self, body: impl IntoUtf8Bytes) -> Builder<Outgoing> {
95        self.add_header(HeaderName::CONTENT_TYPE, "text/html; charset=utf-8");
96        let body = Outgoing::from_bytes(body.into());
97        Builder {
98            version: self.version,
99            status: self.status,
100            headers: self.headers,
101            body,
102        }
103    }
104
105    pub fn text(mut self, body: impl IntoUtf8Bytes) -> Builder<Outgoing> {
106        self.add_header(HeaderName::CONTENT_TYPE, "text/plain; charset=utf-8");
107        let body = Outgoing::from_bytes(body.into());
108        Builder {
109            version: self.version,
110            status: self.status,
111            headers: self.headers,
112            body,
113        }
114    }
115
116    // pub fn json<T>(mut self, body: &T) -> Result<Builder<Outgoing>>
117    // where
118    //     T: serde::ser::Serialize,
119    // {
120    //     let body = serde_json::to_vec(body).map_err(|_| Error::Parse)?;
121    //     self.add_header(HeaderName::CONTENT_TYPE, "application/json");
122    //     let body = Outgoing::from_bytes(Bytes::from(body));
123    //     Ok(Builder {
124    //         version: self.version,
125    //         status: self.status,
126    //         headers: self.headers,
127    //         body,
128    //     })
129    // }
130
131    pub fn body<B>(self, body: B) -> Builder<B> {
132        Builder {
133            version: self.version,
134            status: self.status,
135            headers: self.headers,
136            body,
137        }
138    }
139}
140
141#[cfg(not(target_arch = "wasm32"))]
142impl<B> Builder<B> {
143    pub fn build(self) -> Result<Response<B>> {
144        let mut res = http::Response::builder()
145            .version(self.version)
146            .status(self.status)
147            .body(self.body)?;
148        let headers = res.headers_mut();
149        for (key, value) in self.headers {
150            let key: http::HeaderName = key.to_vec().try_into().map_err(http::Error::from)?;
151            let value: http::HeaderValue = value.to_vec().try_into().map_err(http::Error::from)?;
152            headers.append(key, value);
153        }
154        Ok(Response(res))
155    }
156}