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