1use http_body_util::Full;
2
3use crate::{Body, BoxError, Bytes, Response, Result, StatusCode, header};
4
5pub trait ResponseExt: private::Sealed + Sized {
7 #[must_use]
9 fn empty() -> Response {
10 Response::new(Body::empty())
11 }
12
13 fn content_length(&self) -> Option<u64>;
15
16 fn content_type(&self) -> Option<mime::Mime>;
18
19 fn header<K, T>(&self, key: K) -> Option<T>
21 where
22 K: header::AsHeaderName,
23 T: std::str::FromStr;
24
25 fn ok(&self) -> bool;
29
30 fn with<B>(body: B, content_type: &str) -> Response
34 where
35 B: Into<Body>,
36 {
37 let mut resp = Response::new(body.into());
38 resp.headers_mut().insert(
39 header::CONTENT_TYPE,
40 header::HeaderValue::from_str(content_type).expect("Invalid content type"),
41 );
42 resp
43 }
44
45 fn text<B>(body: B) -> Response
47 where
48 B: Into<Full<Bytes>>,
49 {
50 Self::with(body.into(), mime::TEXT_PLAIN_UTF_8.as_ref())
51 }
52
53 fn html<B>(body: B) -> Response
55 where
56 B: Into<Full<Bytes>>,
57 {
58 Self::with(body.into(), mime::TEXT_HTML_UTF_8.as_ref())
59 }
60
61 fn binary<B>(body: B) -> Response
63 where
64 B: Into<Full<Bytes>>,
65 {
66 Self::with(body.into(), mime::APPLICATION_OCTET_STREAM.as_ref())
67 }
68
69 #[cfg(feature = "json")]
75 fn json<T>(body: T) -> Result<Response, crate::types::PayloadError>
76 where
77 T: serde::Serialize,
78 {
79 let body = serde_json::to_vec(&body).map_err(crate::types::PayloadError::Json)?;
80 Ok(Response::with(
81 Full::from(body),
82 mime::APPLICATION_JSON.as_ref(),
83 ))
84 }
85
86 fn stream<S, D, E>(stream: S) -> Response
88 where
89 S: futures_util::Stream<Item = Result<D, E>> + Send + 'static,
90 D: Into<Bytes> + 'static,
91 E: Into<BoxError> + 'static,
92 {
93 Response::new(Body::from_stream(stream))
94 }
95
96 #[cfg(feature = "fs")]
98 fn download<T>(
99 path: T,
100 name: Option<&str>,
101 ) -> impl std::future::Future<Output = Result<Self>> + Send
102 where
103 T: AsRef<std::path::Path> + Send;
104
105 fn attachment(value: &str) -> Response {
111 let val = header::HeaderValue::from_str(value)
112 .expect("content-disposition is not the correct value");
113 let mut resp = Response::default();
114 resp.headers_mut().insert(header::CONTENT_DISPOSITION, val);
115 resp
116 }
117
118 fn location<T>(location: T) -> Response
122 where
123 T: AsRef<str>,
124 {
125 let val = header::HeaderValue::try_from(location.as_ref())
126 .expect("location is not the correct value");
127 let mut resp = Response::default();
128 resp.headers_mut().insert(header::CONTENT_LOCATION, val);
129 resp
130 }
131
132 fn redirect<T>(url: T) -> Response
136 where
137 T: AsRef<str>,
138 {
139 let val =
140 header::HeaderValue::try_from(url.as_ref()).expect("url is not the correct value");
141 let mut resp = Response::default();
142 resp.headers_mut().insert(header::LOCATION, val);
143 resp
144 }
145
146 fn redirect_with_status<T>(url: T, status: StatusCode) -> Response
150 where
151 T: AsRef<str>,
152 {
153 assert!(status.is_redirection(), "not a redirection status code");
154
155 let mut resp = Self::redirect(url);
156 *resp.status_mut() = status;
157 resp
158 }
159
160 fn see_other<T>(url: T) -> Response
164 where
165 T: AsRef<str>,
166 {
167 Self::redirect_with_status(url, StatusCode::SEE_OTHER)
168 }
169
170 fn temporary<T>(url: T) -> Response
174 where
175 T: AsRef<str>,
176 {
177 Self::redirect_with_status(url, StatusCode::TEMPORARY_REDIRECT)
178 }
179
180 fn permanent<T>(url: T) -> Response
184 where
185 T: AsRef<str>,
186 {
187 Self::redirect_with_status(url, StatusCode::PERMANENT_REDIRECT)
188 }
189}
190
191impl ResponseExt for Response {
192 fn content_length(&self) -> Option<u64> {
193 self.headers()
194 .get(header::CONTENT_LENGTH)
195 .map(header::HeaderValue::to_str)
196 .and_then(Result::ok)
197 .map(str::parse)
198 .and_then(Result::ok)
199 }
200
201 fn content_type(&self) -> Option<mime::Mime> {
202 self.header(header::CONTENT_TYPE)
203 }
204
205 fn header<K, T>(&self, key: K) -> Option<T>
206 where
207 K: header::AsHeaderName,
208 T: std::str::FromStr,
209 {
210 self.headers()
211 .get(key)
212 .map(header::HeaderValue::to_str)
213 .and_then(Result::ok)
214 .map(str::parse)
215 .and_then(Result::ok)
216 }
217
218 fn ok(&self) -> bool {
219 self.status().is_success()
220 }
221
222 #[cfg(feature = "fs")]
223 async fn download<T>(path: T, name: Option<&str>) -> Result<Self>
224 where
225 T: AsRef<std::path::Path> + Send,
226 {
227 let value = name
228 .unwrap_or_else(|| {
229 path.as_ref()
230 .file_name()
231 .and_then(std::ffi::OsStr::to_str)
232 .map_or("download", |filename| filename)
233 })
234 .escape_default();
235
236 let mut resp = Self::attachment(&format!("attachment; filename=\"{value}\""));
237 *resp.body_mut() = Body::from_stream(tokio_util::io::ReaderStream::new(
238 tokio::fs::File::open(path)
239 .await
240 .map_err(crate::Error::from)?,
241 ));
242 Ok(resp)
243 }
244}
245
246mod private {
247 pub trait Sealed {}
248 impl Sealed for super::Response {}
249}