Skip to main content

sark_core/http/response/
general.rs

1//! General-purpose owned HTTP response.
2
3use http::{HeaderName, HeaderValue, StatusCode};
4use o3::buffer::Shared;
5use serde::Serialize;
6
7use super::{Body, FixedResponse, HeaderList, MonoResponseInner, StaticHeaders};
8
9#[derive(Clone)]
10pub struct Response {
11    pub(super) status: StatusCode,
12    pub(super) headers: HeaderList,
13    pub(super) wire_headers: Vec<u8>,
14    pub(super) body: Body<'static>,
15    pub(super) chunked_parts: Option<Vec<Shared>>,
16}
17
18impl std::fmt::Debug for Response {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.debug_struct("Response")
21            .field("status", &self.status)
22            .field("headers", &self.headers)
23            .field("wire_headers_len", &self.wire_headers.len())
24            .field("body_len", &self.body.len())
25            .field("chunked", &self.chunked_parts.is_some())
26            .finish()
27    }
28}
29
30impl Response {
31    pub fn new(status: StatusCode) -> Self {
32        Self {
33            status,
34            headers: HeaderList::new(),
35            wire_headers: Vec::new(),
36            body: Body::empty(),
37            chunked_parts: None,
38        }
39    }
40
41    pub fn ok() -> Self {
42        Self::new(StatusCode::OK)
43    }
44
45    pub fn text(body: &str) -> Self {
46        Self::text_with_status(StatusCode::OK, body)
47    }
48
49    pub fn text_with_status(status: StatusCode, body: &str) -> Self {
50        let mut resp = Self::new(status);
51        resp.content_type("text/plain");
52        resp.set_body_str(body);
53        resp
54    }
55
56    pub fn json<T: Serialize>(value: &T) -> Result<Self, serde_json::Error> {
57        Self::json_with_status(StatusCode::OK, value)
58    }
59
60    pub fn json_with_status<T: Serialize>(
61        status: StatusCode,
62        value: &T,
63    ) -> Result<Self, serde_json::Error> {
64        let s = serde_json::to_string(value)?;
65        let mut resp = Self::new(status);
66        resp.content_type("application/json");
67        resp.set_body_str(&s);
68        Ok(resp)
69    }
70
71    pub fn insert_header(&mut self, name: HeaderName, value: HeaderValue) -> &mut Self {
72        let _ = self.headers.insert(name, value);
73        self
74    }
75
76    pub fn append_wire_header(&mut self, name: &'static str, value: &str) -> &mut Self {
77        Self::assert_wire_header_name(name);
78        assert!(
79            !value.as_bytes().iter().any(|b| *b == b'\r' || *b == b'\n'),
80            "wire header value must not contain CR/LF"
81        );
82        self.append_wire_header_bytes(name, value.as_bytes())
83    }
84
85    pub fn append_wire_header_static(
86        &mut self,
87        name: &'static str,
88        value: &'static str,
89    ) -> &mut Self {
90        Self::assert_wire_header_name(name);
91        self.append_wire_header_bytes(name, value.as_bytes())
92    }
93
94    fn append_wire_header_bytes(&mut self, name: &str, value: &[u8]) -> &mut Self {
95        self.wire_headers.extend_from_slice(name.as_bytes());
96        self.wire_headers.extend_from_slice(b": ");
97        self.wire_headers.extend_from_slice(value);
98        self.wire_headers.extend_from_slice(b"\r\n");
99        self
100    }
101
102    pub fn content_type(&mut self, value: &'static str) -> &mut Self {
103        let _ = self
104            .headers
105            .insert(http::header::CONTENT_TYPE, HeaderValue::from_static(value));
106        self
107    }
108
109    pub fn not_found() -> Self {
110        Self::new(StatusCode::NOT_FOUND)
111    }
112
113    pub fn status(&self) -> StatusCode {
114        self.status
115    }
116
117    pub fn set_status(&mut self, status: StatusCode) {
118        self.status = status;
119    }
120
121    pub fn headers(&self) -> &HeaderList {
122        &self.headers
123    }
124
125    pub fn headers_mut(&mut self) -> &mut HeaderList {
126        &mut self.headers
127    }
128
129    pub fn wire_headers(&self) -> &[u8] {
130        self.wire_headers.as_ref()
131    }
132
133    pub fn has_wire_headers(&self) -> bool {
134        !self.wire_headers.is_empty()
135    }
136
137    pub fn body(&self) -> &[u8] {
138        self.body.as_bytes()
139    }
140
141    pub fn body_is_shared(&self) -> bool {
142        self.body.is_shared()
143    }
144
145    pub fn body_mut(&mut self) -> &mut Vec<u8> {
146        self.body.as_owned_mut()
147    }
148
149    pub fn into_body(self) -> Vec<u8> {
150        self.body.into_owned()
151    }
152
153    pub fn into_body_bytes(self) -> Shared {
154        self.body.into_bytes()
155    }
156
157    pub fn set_body<B>(&mut self, body: B)
158    where
159        B: Into<Body<'static>>,
160    {
161        self.body = body.into();
162    }
163
164    pub fn set_body_str(&mut self, body: &str) -> &mut Self {
165        self.body = Body::Owned(body.as_bytes().to_vec());
166        self
167    }
168
169    pub fn push_chunk(&mut self, data: impl Into<Shared>) {
170        self.chunked_parts
171            .get_or_insert_with(Vec::new)
172            .push(data.into());
173    }
174
175    pub fn chunked_parts(&self) -> Option<&[Shared]> {
176        self.chunked_parts.as_deref()
177    }
178
179    pub fn is_chunked(&self) -> bool {
180        self.chunked_parts.is_some()
181    }
182
183    fn assert_wire_header_name(name: &str) {
184        assert!(
185            sark_protocol::validate_response_header_name(name).is_ok(),
186            "invalid or managed wire header name: {name}"
187        );
188    }
189}
190
191impl<const N: usize> From<MonoResponseInner<'static, N>> for Response {
192    fn from(response: MonoResponseInner<'static, N>) -> Self {
193        Self {
194            status: response.status,
195            headers: response.headers.map(|h| *h).unwrap_or_default(),
196            wire_headers: response.head.into_bytes().as_ref().to_vec(),
197            body: response.body,
198            chunked_parts: None,
199        }
200    }
201}
202
203impl<const N: usize, H> From<FixedResponse<'static, N, H>> for Response
204where
205    H: StaticHeaders,
206{
207    fn from(response: FixedResponse<'static, N, H>) -> Self {
208        Self {
209            status: response.status,
210            headers: HeaderList::new(),
211            wire_headers: response.wire_headers().as_ref().to_vec(),
212            body: Body::Shared(response.body),
213            chunked_parts: None,
214        }
215    }
216}