wayver's git archive


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

src/utils/response.rs@db2bc30f716095872b4ea33adafe072c0a8a7242

raw
Date Commit Message Author Files + -
2026-02-17 21:07 initial mvp wayverd 74 10800 0
...

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