axum/response/
mod.rs

1#![doc = include_str!("../docs/response.md")]
2
3use http::{header, HeaderValue, StatusCode};
4
5mod redirect;
6
7#[cfg(feature = "tokio")]
8pub mod sse;
9
10#[doc(no_inline)]
11#[cfg(feature = "json")]
12pub use crate::Json;
13
14#[cfg(feature = "form")]
15#[doc(no_inline)]
16pub use crate::form::Form;
17
18#[doc(no_inline)]
19pub use crate::Extension;
20
21#[doc(inline)]
22pub use axum_core::response::{
23    AppendHeaders, ErrorResponse, IntoResponse, IntoResponseParts, Response, ResponseParts, Result,
24};
25
26#[doc(inline)]
27pub use self::redirect::Redirect;
28
29#[doc(inline)]
30#[cfg(feature = "tokio")]
31pub use sse::Sse;
32
33/// An HTML response.
34///
35/// Will automatically get `Content-Type: text/html`.
36#[derive(Clone, Copy, Debug)]
37#[must_use]
38pub struct Html<T>(pub T);
39
40impl<T> IntoResponse for Html<T>
41where
42    T: IntoResponse,
43{
44    fn into_response(self) -> Response {
45        (
46            [(
47                header::CONTENT_TYPE,
48                HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
49            )],
50            self.0,
51        )
52            .into_response()
53    }
54}
55
56impl<T> From<T> for Html<T> {
57    fn from(inner: T) -> Self {
58        Self(inner)
59    }
60}
61
62/// An empty response with 204 No Content status.
63///
64/// Due to historical and implementation reasons, the `IntoResponse` implementation of `()`
65/// (unit type) returns an empty response with 200 [`StatusCode::OK`] status.
66/// If you specifically want a 204 [`StatusCode::NO_CONTENT`] status, you can use either `StatusCode` type
67/// directly, or this shortcut struct for self-documentation.
68///
69/// ```
70/// use axum::{extract::Path, response::NoContent};
71///
72/// async fn delete_user(Path(user): Path<String>) -> Result<NoContent, String> {
73///     // ...access database...
74/// # drop(user);
75///     Ok(NoContent)
76/// }
77/// ```
78#[derive(Debug, Clone, Copy)]
79pub struct NoContent;
80
81impl IntoResponse for NoContent {
82    fn into_response(self) -> Response {
83        StatusCode::NO_CONTENT.into_response()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use crate::extract::Extension;
90    use crate::{routing::get, Router};
91    use axum_core::response::IntoResponse;
92    use http::HeaderMap;
93    use http::{StatusCode, Uri};
94
95    // just needs to compile
96    #[allow(dead_code)]
97    fn impl_trait_result_works() {
98        async fn impl_trait_ok() -> Result<impl IntoResponse, ()> {
99            Ok(())
100        }
101
102        async fn impl_trait_err() -> Result<(), impl IntoResponse> {
103            Err(())
104        }
105
106        async fn impl_trait_both(uri: Uri) -> Result<impl IntoResponse, impl IntoResponse> {
107            if uri.path() == "/" {
108                Ok(())
109            } else {
110                Err(())
111            }
112        }
113
114        async fn impl_trait(uri: Uri) -> impl IntoResponse {
115            if uri.path() == "/" {
116                Ok(())
117            } else {
118                Err(())
119            }
120        }
121
122        _ = Router::<()>::new()
123            .route("/", get(impl_trait_ok))
124            .route("/", get(impl_trait_err))
125            .route("/", get(impl_trait_both))
126            .route("/", get(impl_trait));
127    }
128
129    // just needs to compile
130    #[allow(dead_code)]
131    fn tuple_responses() {
132        async fn status() -> impl IntoResponse {
133            StatusCode::OK
134        }
135
136        async fn status_headermap() -> impl IntoResponse {
137            (StatusCode::OK, HeaderMap::new())
138        }
139
140        async fn status_header_array() -> impl IntoResponse {
141            (StatusCode::OK, [("content-type", "text/plain")])
142        }
143
144        async fn status_headermap_body() -> impl IntoResponse {
145            (StatusCode::OK, HeaderMap::new(), String::new())
146        }
147
148        async fn status_header_array_body() -> impl IntoResponse {
149            (
150                StatusCode::OK,
151                [("content-type", "text/plain")],
152                String::new(),
153            )
154        }
155
156        async fn status_headermap_impl_into_response() -> impl IntoResponse {
157            (StatusCode::OK, HeaderMap::new(), impl_into_response())
158        }
159
160        async fn status_header_array_impl_into_response() -> impl IntoResponse {
161            (
162                StatusCode::OK,
163                [("content-type", "text/plain")],
164                impl_into_response(),
165            )
166        }
167
168        fn impl_into_response() -> impl IntoResponse {}
169
170        async fn status_header_array_extension_body() -> impl IntoResponse {
171            (
172                StatusCode::OK,
173                [("content-type", "text/plain")],
174                Extension(1),
175                String::new(),
176            )
177        }
178
179        async fn status_header_array_extension_mixed_body() -> impl IntoResponse {
180            (
181                StatusCode::OK,
182                [("content-type", "text/plain")],
183                Extension(1),
184                HeaderMap::new(),
185                String::new(),
186            )
187        }
188
189        //
190
191        async fn headermap() -> impl IntoResponse {
192            HeaderMap::new()
193        }
194
195        async fn header_array() -> impl IntoResponse {
196            [("content-type", "text/plain")]
197        }
198
199        async fn headermap_body() -> impl IntoResponse {
200            (HeaderMap::new(), String::new())
201        }
202
203        async fn header_array_body() -> impl IntoResponse {
204            ([("content-type", "text/plain")], String::new())
205        }
206
207        async fn headermap_impl_into_response() -> impl IntoResponse {
208            (HeaderMap::new(), impl_into_response())
209        }
210
211        async fn header_array_impl_into_response() -> impl IntoResponse {
212            ([("content-type", "text/plain")], impl_into_response())
213        }
214
215        async fn header_array_extension_body() -> impl IntoResponse {
216            (
217                [("content-type", "text/plain")],
218                Extension(1),
219                String::new(),
220            )
221        }
222
223        async fn header_array_extension_mixed_body() -> impl IntoResponse {
224            (
225                [("content-type", "text/plain")],
226                Extension(1),
227                HeaderMap::new(),
228                String::new(),
229            )
230        }
231
232        _ = Router::<()>::new()
233            .route("/", get(status))
234            .route("/", get(status_headermap))
235            .route("/", get(status_header_array))
236            .route("/", get(status_headermap_body))
237            .route("/", get(status_header_array_body))
238            .route("/", get(status_headermap_impl_into_response))
239            .route("/", get(status_header_array_impl_into_response))
240            .route("/", get(status_header_array_extension_body))
241            .route("/", get(status_header_array_extension_mixed_body))
242            .route("/", get(headermap))
243            .route("/", get(header_array))
244            .route("/", get(headermap_body))
245            .route("/", get(header_array_body))
246            .route("/", get(headermap_impl_into_response))
247            .route("/", get(header_array_impl_into_response))
248            .route("/", get(header_array_extension_body))
249            .route("/", get(header_array_extension_mixed_body));
250    }
251
252    #[test]
253    fn no_content() {
254        assert_eq!(
255            super::NoContent.into_response().status(),
256            StatusCode::NO_CONTENT,
257        )
258    }
259}