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