wayver's git archive


a simple self-hosted git server
git clone https://git.wayver.dev/bile

src/http/response.rs@363f1b706491794c3359d55e1dda10fb104d5344

raw
Date Commit Message Author Files + -
2026-02-20 20:25 remove unneeded result type alias wayverd 13 27 31
...

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) 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 {
251    config: Arc<Config>,
252    status: StatusCode,
253}
254
255impl ErrorPage {
256    pub(crate) fn with_status(self, status: StatusCode) -> Self {
257        Self {
258            config: self.config,
259            status,
260        }
261    }
262}
263
264impl From<BileState> for ErrorPage {
265    fn from(value: BileState) -> Self {
266        Self {
267            config: value.config,
268            status: StatusCode::INTERNAL_SERVER_ERROR,
269        }
270    }
271}
272
273impl<'s> From<&'s BileState> for ErrorPage {
274    fn from(value: &'s BileState) -> Self {
275        Self {
276            config: value.config.clone(),
277            status: StatusCode::INTERNAL_SERVER_ERROR,
278        }
279    }
280}
281
282impl IntoResponse for ErrorPage {
283    fn into_response(self) -> Response {
284        (self.status, Html(self)).into_response()
285    }
286}
287