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.map_ok(Frame::data).map_err(|e| -> BoxError { Box::new(e) });
201 Self::stream(BodyExt::boxed(StreamBody::new(mapped)))
202 }
203
204 pub fn status(mut self, code: u16) -> Self {
206 self.status = StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
207 self
208 }
209
210 pub fn header(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
211 if let (Ok(name), Ok(value)) = (
212 HeaderName::from_bytes(name.as_ref().as_bytes()),
213 HeaderValue::from_str(value.as_ref()),
214 ) {
215 self.headers.insert(name, value);
216 }
217 self
218 }
219
220 pub fn status_code(&self) -> StatusCode {
221 self.status
222 }
223
224 pub fn headers(&self) -> &HeaderMap {
225 &self.headers
226 }
227
228 pub fn headers_mut(&mut self) -> &mut HeaderMap {
229 &mut self.headers
230 }
231
232 pub fn take_body(&mut self) -> Body {
233 mem::replace(&mut self.body, Body::Bytes(Bytes::new()))
234 }
235
236 pub fn set_body(&mut self, body: impl Into<Body>) {
237 self.body = body.into();
238 }
239
240 pub fn body_bytes(&self) -> Option<&[u8]> {
242 match &self.body {
243 Body::Bytes(b) => Some(b.as_ref()),
244 Body::Stream(_) => None,
245 }
246 }
247
248 pub async fn file(path: impl AsRef<Path>) -> Self {
250 file::serve_path(path.as_ref()).await
251 }
252
253 pub async fn file_in(dir: impl AsRef<Path>, relative: impl AsRef<Path>) -> Self {
255 file::serve_in(dir.as_ref(), relative.as_ref()).await
256 }
257
258 pub async fn download(path: impl AsRef<Path>) -> Self {
260 let path = path.as_ref();
261 let name = path
262 .file_name()
263 .and_then(|s| s.to_str())
264 .unwrap_or("download");
265 Self::file(path).await.attachment(name)
266 }
267
268 pub async fn download_in(dir: impl AsRef<Path>, relative: impl AsRef<Path>) -> Self {
270 let relative = relative.as_ref();
271 let name = relative
272 .file_name()
273 .and_then(|s| s.to_str())
274 .unwrap_or("download");
275 Self::file_in(dir, relative).await.attachment(name)
276 }
277
278 pub(crate) fn clear_body(&mut self) {
279 self.body = Body::Bytes(Bytes::new());
280 }
281
282 fn set_text(&mut self, body: String) {
283 self.headers.insert(
284 http::header::CONTENT_TYPE,
285 HeaderValue::from_static("text/plain; charset=utf-8"),
286 );
287 self.body = Body::Bytes(Bytes::from(body));
288 }
289
290 pub(crate) fn into_http_body(self) -> ResponseBody {
291 match self.body {
292 Body::Bytes(b) => Full::new(b)
293 .map_err(|_: Infallible| unreachable!())
294 .boxed(),
295 Body::Stream(b) => b,
296 }
297 }
298}
299