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#[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#[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 #[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 #[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 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}