sova_core/response/
mod.rs1mod file;
2mod typed;
3
4pub use typed::{referer_or, Html, Json, NoContent, Redirect, Text};
5
6use crate::error::IntoResponse;
7use bytes::Bytes;
8use futures_util::TryStreamExt;
9use http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
10use http_body::Frame;
11use http_body_util::combinators::BoxBody;
12use http_body_util::{BodyExt, Full, StreamBody};
13use serde::Serialize;
14use std::convert::Infallible;
15use std::mem;
16use std::path::Path;
17
18pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
19pub type HttpBody = BoxBody<Bytes, BoxError>;
21pub type ResponseBody = HttpBody;
23
24pub struct Response {
26 pub(crate) status: StatusCode,
27 pub(crate) headers: HeaderMap,
28 pub(crate) body: Body,
29}
30
31impl std::fmt::Debug for Response {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("Response")
34 .field("status", &self.status)
35 .finish_non_exhaustive()
36 }
37}
38
39pub enum Body {
40 Bytes(Bytes),
41 Stream(ResponseBody),
42}
43
44impl From<Bytes> for Body {
45 fn from(b: Bytes) -> Self {
46 Body::Bytes(b)
47 }
48}
49
50impl From<Vec<u8>> for Body {
51 fn from(b: Vec<u8>) -> Self {
52 Body::Bytes(Bytes::from(b))
53 }
54}
55
56impl From<&'static [u8]> for Body {
57 fn from(b: &'static [u8]) -> Self {
58 Body::Bytes(Bytes::from_static(b))
59 }
60}
61
62impl From<String> for Body {
63 fn from(s: String) -> Self {
64 Body::Bytes(Bytes::from(s))
65 }
66}
67
68impl Body {
69 pub async fn collect(self) -> Result<Bytes, BoxError> {
71 match self {
72 Body::Bytes(b) => Ok(b),
73 Body::Stream(stream) => {
74 let collected = BodyExt::collect(stream).await?;
75 Ok(collected.to_bytes())
76 }
77 }
78 }
79}
80
81impl Default for Response {
82 fn default() -> Self {
83 Self::empty()
84 }
85}
86
87impl Response {
88 pub fn empty() -> Self {
89 Self {
90 status: StatusCode::OK,
91 headers: HeaderMap::new(),
92 body: Body::Bytes(Bytes::new()),
93 }
94 }
95
96 pub fn text(body: impl Into<String>) -> Self {
97 let mut res = Self::empty();
98 res.set_text(body.into());
99 res
100 }
101
102 pub fn html(body: impl Into<String>) -> Self {
103 let mut res = Self::empty();
104 res.headers.insert(
105 http::header::CONTENT_TYPE,
106 HeaderValue::from_static("text/html; charset=utf-8"),
107 );
108 res.body = Body::Bytes(Bytes::from(body.into()));
109 res
110 }
111
112 pub fn json<T: Serialize>(value: &T) -> Self {
113 match serde_json::to_vec(value) {
114 Ok(bytes) => {
115 let mut res = Self::empty();
116 res.headers.insert(
117 http::header::CONTENT_TYPE,
118 HeaderValue::from_static("application/json"),
119 );
120 res.body = Body::Bytes(Bytes::from(bytes));
121 res
122 }
123 Err(err) => Self::text(format!("JSON encode error: {err}")).status(500),
124 }
125 }
126
127 pub fn redirect(location: impl AsRef<str>) -> Self {
128 Redirect::to(location.as_ref()).into_response()
129 }
130
131 pub fn bytes(data: impl Into<Bytes>, mime: &str) -> Self {
133 let mut res = Self::empty();
134 if let Ok(v) = HeaderValue::from_str(mime) {
135 res.headers.insert(http::header::CONTENT_TYPE, v);
136 }
137 let data = data.into();
138 if let Ok(v) = HeaderValue::from_str(&data.len().to_string()) {
139 res.headers.insert(http::header::CONTENT_LENGTH, v);
140 }
141 res.body = Body::Bytes(data);
142 res
143 }
144
145 pub fn attachment(mut self, filename: &str) -> Self {
147 let safe = filename.replace(['"', '\r', '\n', '\\'], "_");
148 let value = format!("attachment; filename=\"{safe}\"");
149 if let Ok(v) = HeaderValue::from_str(&value) {
150 self.headers.insert(http::header::CONTENT_DISPOSITION, v);
151 }
152 self
153 }
154
155 pub fn sse<S, E>(stream: S) -> Self
159 where
160 S: futures_util::Stream<Item = Result<String, E>> + Send + Sync + 'static,
161 E: Into<BoxError> + Send + 'static,
162 {
163 use futures_util::StreamExt;
164 let mapped = stream.map(|item| {
165 item.map(|s| {
166 let mut out = String::new();
167 for line in s.split('\n') {
168 out.push_str("data: ");
169 out.push_str(line);
170 out.push('\n');
171 }
172 out.push('\n');
173 Bytes::from(out)
174 })
175 .map_err(Into::into)
176 });
177 let mapped = mapped.map_ok(Frame::data).map_err(|e: BoxError| e);
178 let mut res = Self::stream(BodyExt::boxed(StreamBody::new(mapped)));
179 res.headers.insert(
180 http::header::CONTENT_TYPE,
181 HeaderValue::from_static("text/event-stream"),
182 );
183 res.headers.insert(
184 http::header::CACHE_CONTROL,
185 HeaderValue::from_static("no-cache"),
186 );
187 res
188 }
189
190 pub fn stream(body: ResponseBody) -> Self {
191 let mut res = Self::empty();
192 res.body = Body::Stream(body);
193 res
194 }
195
196 pub fn from_reader_stream<S>(stream: S) -> Self
197 where
198 S: futures_util::Stream<Item = Result<Bytes, std::io::Error>> + Send + Sync + 'static,
199 {
200 let mapped = stream
201 .map_ok(Frame::data)
202 .map_err(|e| -> BoxError { Box::new(e) });
203 Self::stream(BodyExt::boxed(StreamBody::new(mapped)))
204 }
205
206 pub fn status(mut self, code: u16) -> Self {
208 self.status = StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
209 self
210 }
211
212 pub fn header(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
213 if let (Ok(name), Ok(value)) = (
214 HeaderName::from_bytes(name.as_ref().as_bytes()),
215 HeaderValue::from_str(value.as_ref()),
216 ) {
217 self.headers.insert(name, value);
218 }
219 self
220 }
221
222 pub fn status_code(&self) -> StatusCode {
223 self.status
224 }
225
226 pub fn headers(&self) -> &HeaderMap {
227 &self.headers
228 }
229
230 pub fn headers_mut(&mut self) -> &mut HeaderMap {
231 &mut self.headers
232 }
233
234 pub fn take_body(&mut self) -> Body {
235 mem::replace(&mut self.body, Body::Bytes(Bytes::new()))
236 }
237
238 pub fn set_body(&mut self, body: impl Into<Body>) {
239 self.body = body.into();
240 }
241
242 pub fn is_html(&self) -> bool {
244 self.headers
245 .get(http::header::CONTENT_TYPE)
246 .and_then(|v| v.to_str().ok())
247 .is_some_and(|ct| ct.to_ascii_lowercase().contains("text/html"))
248 }
249
250 pub fn map_buffered_html(&mut self, f: impl FnOnce(&str) -> Option<String>) -> bool {
253 if !self.is_html() {
254 return false;
255 }
256 let body = self.take_body();
257 match body {
258 Body::Bytes(bytes) => {
259 if let Ok(html) = std::str::from_utf8(&bytes) {
260 if let Some(new_html) = f(html) {
261 self.set_body(new_html);
262 return true;
263 }
264 }
265 self.set_body(bytes);
266 false
267 }
268 other => {
269 self.set_body(other);
270 false
271 }
272 }
273 }
274
275 pub fn body_bytes(&self) -> Option<&[u8]> {
277 match &self.body {
278 Body::Bytes(b) => Some(b.as_ref()),
279 Body::Stream(_) => None,
280 }
281 }
282
283 pub async fn file(path: impl AsRef<Path>) -> Self {
285 file::serve_path(path.as_ref()).await
286 }
287
288 pub async fn file_in(dir: impl AsRef<Path>, relative: impl AsRef<Path>) -> Self {
290 file::serve_in(dir.as_ref(), relative.as_ref()).await
291 }
292
293 pub async fn download(path: impl AsRef<Path>) -> Self {
295 let path = path.as_ref();
296 let name = path
297 .file_name()
298 .and_then(|s| s.to_str())
299 .unwrap_or("download");
300 Self::file(path).await.attachment(name)
301 }
302
303 pub async fn download_in(dir: impl AsRef<Path>, relative: impl AsRef<Path>) -> Self {
305 let relative = relative.as_ref();
306 let name = relative
307 .file_name()
308 .and_then(|s| s.to_str())
309 .unwrap_or("download");
310 Self::file_in(dir, relative).await.attachment(name)
311 }
312
313 pub(crate) fn clear_body(&mut self) {
314 self.body = Body::Bytes(Bytes::new());
315 }
316
317 fn set_text(&mut self, body: String) {
318 self.headers.insert(
319 http::header::CONTENT_TYPE,
320 HeaderValue::from_static("text/plain; charset=utf-8"),
321 );
322 self.body = Body::Bytes(Bytes::from(body));
323 }
324
325 pub(crate) fn into_parts(self) -> (StatusCode, HeaderMap, ResponseBody) {
327 let body = match self.body {
328 Body::Bytes(b) => Full::new(b).map_err(|_: Infallible| unreachable!()).boxed(),
329 Body::Stream(b) => b,
330 };
331 (self.status, self.headers, body)
332 }
333}