axum/response/
mod.rs

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