axum/response/
mod.rs

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