raw
1use std::sync::Arc;
2
3use axum::{
4 http::{HeaderValue, StatusCode, header},
5 response::{IntoResponse, Response},
6};
7
8use crate::{config::Config, http::BileState};
9
10pub(crate) type Result<T = Response, E = crate::error::Error> = std::result::Result<T, E>;
11
12pub(crate) struct Css<T>(pub T);
13
14impl<T: IntoResponse> IntoResponse for Css<T> {
15 fn into_response(self) -> Response {
16 (
17 [
18 (
19 header::CONTENT_TYPE,
20 HeaderValue::from_static(mime::TEXT_CSS_UTF_8.as_ref()),
21 ),
22 (
23 header::CACHE_CONTROL,
24 HeaderValue::from_static("max-age=31536000, immutable"),
25 ),
26 ],
27 self.0,
28 )
29 .into_response()
30 }
31}
32
33pub(crate) struct Ico<T>(pub T);
34
35impl<T: IntoResponse> IntoResponse for Ico<T> {
36 fn into_response(self) -> Response {
37 (
38 [
39 (
40 header::CONTENT_TYPE,
41 HeaderValue::from_static("image/x-icon"),
42 ),
43 (
44 header::CACHE_CONTROL,
45 HeaderValue::from_static("max-age=31536000, immutable"),
46 ),
47 ],
48 self.0,
49 )
50 .into_response()
51 }
52}
53
54pub(crate) struct Json<T>(pub T);
55
56impl<T: IntoResponse> IntoResponse for Json<T> {
57 fn into_response(self) -> Response {
58 (
59 [
60 (
61 header::CONTENT_TYPE,
62 HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
63 ),
64 (
65 header::CACHE_CONTROL,
66 HeaderValue::from_static("max-age=31536000, immutable"),
67 ),
68 ],
69 self.0,
70 )
71 .into_response()
72 }
73}
74
75pub(crate) struct Png<T>(pub T);
76
77impl<T: IntoResponse> IntoResponse for Png<T> {
78 fn into_response(self) -> Response {
79 (
80 [
81 (
82 header::CONTENT_TYPE,
83 HeaderValue::from_static(mime::IMAGE_PNG.as_ref()),
84 ),
85 (
86 header::CACHE_CONTROL,
87 HeaderValue::from_static("max-age=31536000, immutable"),
88 ),
89 ],
90 self.0,
91 )
92 .into_response()
93 }
94}
95
96pub(crate) struct Text<T>(pub T);
97
98impl<T: IntoResponse> IntoResponse for Text<T> {
99 fn into_response(self) -> Response {
100 (
101 [
102 (
103 header::CONTENT_TYPE,
104 HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
105 ),
106 (
107 header::CACHE_CONTROL,
108 HeaderValue::from_static("max-age=300, private"),
109 ),
110 ],
111 self.0,
112 )
113 .into_response()
114 }
115}
116
117pub(crate) struct Html<T: askama::Template>(pub T);
118
119impl<T: askama::Template> IntoResponse for Html<T> {
120 fn into_response(self) -> Response {
121 match self.0.render() {
122 Ok(rendered) => (
123 [
124 (
125 header::CONTENT_TYPE,
126 HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
127 ),
128 (
129 header::CACHE_CONTROL,
130 HeaderValue::from_static("max-age=300, private"),
131 ),
132 ],
133 rendered,
134 )
135 .into_response(),
136 Err(err) => {
137 tracing::error!(err=?err, "failed to render html response");
138
139 (
140 StatusCode::INTERNAL_SERVER_ERROR,
141 [(
142 header::CONTENT_TYPE,
143 HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
144 )],
145 "a serious error has occured",
146 )
147 .into_response()
148 }
149 }
150 }
151}
152
153pub(crate) struct Xml<T: askama::Template>(pub T);
154
155impl<T: askama::Template> IntoResponse for Xml<T> {
156 fn into_response(self) -> Response {
157 match self.0.render() {
158 Ok(rendered) => (
159 [
160 (
161 header::CONTENT_TYPE,
162 HeaderValue::from_static(mime::TEXT_XML.as_ref()),
163 ),
164 (
165 header::CACHE_CONTROL,
166 HeaderValue::from_static("max-age=300, private"),
167 ),
168 ],
169 rendered,
170 )
171 .into_response(),
172 Err(err) => {
173 tracing::error!(err=?err, "failed to render xml response");
174
175 (
176 StatusCode::INTERNAL_SERVER_ERROR,
177 [(
178 header::CONTENT_TYPE,
179 HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
180 )],
181 "a serious error has occured",
182 )
183 .into_response()
184 }
185 }
186 }
187}
188
189#[must_use = "needs to be returned from a handler or otherwise turned into a Response to be useful"]
190#[derive(Debug, Clone)]
191pub(crate) struct Redirect {
192 status_code: StatusCode,
193 location: HeaderValue,
194}
195
196impl Redirect {
197 pub(crate) const PERMANENT_ROOT: Self = Self {
198 status_code: StatusCode::PERMANENT_REDIRECT,
199 location: HeaderValue::from_static("/"),
200 };
201 pub(crate) const TEMPORARY_ROOT: Self = Self {
202 status_code: StatusCode::TEMPORARY_REDIRECT,
203 location: HeaderValue::from_static("/"),
204 };
205
206 #[tracing::instrument(skip_all)]
207 pub(crate) fn to(uri: &str) -> Option<Self> {
208 Self::with_status_code(StatusCode::SEE_OTHER, uri)
209 }
210
211 #[tracing::instrument(skip_all)]
212 pub(crate) fn temporary(uri: &str) -> Option<Self> {
213 Self::with_status_code(StatusCode::TEMPORARY_REDIRECT, uri)
214 }
215
216 #[tracing::instrument(skip_all)]
217 pub(crate) fn permanent(uri: &str) -> Option<Self> {
218 Self::with_status_code(StatusCode::PERMANENT_REDIRECT, uri)
219 }
220
221 #[tracing::instrument(skip_all)]
222 fn with_status_code(status_code: StatusCode, uri: &str) -> Option<Self> {
223 assert!(
224 status_code.is_redirection(),
225 "not a redirection status code"
226 );
227
228 let location = match HeaderValue::try_from(uri) {
229 Ok(location) => location,
230 Err(err) => {
231 tracing::error!(err=?err, "failed to convert uri to header");
232
233 return None;
234 }
235 };
236
237 Some(Self {
238 status_code,
239 location,
240 })
241 }
242}
243
244impl IntoResponse for Redirect {
245 fn into_response(self) -> Response {
246 (self.status_code, [(header::LOCATION, self.location)]).into_response()
247 }
248}
249
250#[derive(askama::Template)]
251#[template(path = "error.html")]
252pub(crate) struct ErrorPage {
253 config: Arc<Config>,
254 status: StatusCode,
255}
256
257impl ErrorPage {
258 pub(crate) fn with_status(self, status: StatusCode) -> Self {
259 Self {
260 config: self.config,
261 status,
262 }
263 }
264}
265
266impl From<BileState> for ErrorPage {
267 fn from(value: BileState) -> Self {
268 Self {
269 config: value.config,
270 status: StatusCode::INTERNAL_SERVER_ERROR,
271 }
272 }
273}
274
275impl<'s> From<&'s BileState> for ErrorPage {
276 fn from(value: &'s BileState) -> Self {
277 Self {
278 config: value.config.clone(),
279 status: StatusCode::INTERNAL_SERVER_ERROR,
280 }
281 }
282}
283
284impl IntoResponse for ErrorPage {
285 fn into_response(self) -> Response {
286 (self.status, Html(self)).into_response()
287 }
288}
289