1pub use axum_core::{
5 extract::{FromRequest, Request},
6 response::{IntoResponse, Response},
7};
8pub use bytes::Bytes;
9pub use http;
10pub use mime;
11pub use serde::{de::DeserializeOwned, Deserialize, Serialize};
12
13#[macro_export]
27macro_rules! extractor {
28 (
29 $name:tt,
30 $ext:tt,
31 $content_type: tt,
32 $de:ident,
33 $de_err:ident,
34 $ser:ident,
35 $test:ident
36 ) => {
37 #[doc = stringify!($name)]
38 #[doc = " Extractor / Response."]
39 #[doc = ""]
40 #[doc = "When used as an extractor, it can deserialize request bodies into some type that"]
41 #[doc = "implements [`serde::Deserialize`]. The request will be rejected (and a [`crate::Rejection`] will"]
42 #[doc = "be returned) if:"]
43 #[doc = "- The request doesn't have a `Content-Type:"]
44 #[doc = $content_type]
45 #[doc = "` (or similar) header."]
46 #[doc = "- The body doesn't contain syntactically valid "]
47 #[doc = stringify!($name)]
48 #[doc = "."]
49 #[doc = "- The body contains syntactically valid "]
50 #[doc = stringify!($name)]
51 #[doc = " but it couldn't be deserialized into the target"]
52 #[doc = "type."]
53 #[doc = "- Buffering the request body fails."]
54 #[doc = ""]
55 #[doc = "⚠️ Since parsing "]
56 #[doc = stringify!($name)]
57 #[doc = " requires consuming the request body, the "]
58 #[doc = stringify!($ext)]
59 #[doc = " extractor must be last if there are multiple extractors in a handler."]
60 #[doc = ""]
61 #[doc = "See [`crate::Rejection`] for more details."]
62 #[doc = "# Extractor example"]
63 #[doc = " ```rust,ignore"]
64 #[doc = " use axum::{"]
65 #[doc = " routing::post,"]
66 #[doc = " Router,"]
67 #[doc = " };"]
68 #[doc = concat!(" use axum_serde::", stringify!($ext), ";")]
69 #[doc = " use serde::Deserialize;"]
70 #[doc = ""]
71 #[doc = " #[derive(Deserialize)]"]
72 #[doc = " struct CreateUser {"]
73 #[doc = " email: String,"]
74 #[doc = " password: String,"]
75 #[doc = " }"]
76 #[doc = ""]
77 #[doc = concat!(" async fn create_user(", stringify!($ext), "(payload): ", stringify!($ext), "<CreateUser>) {")]
78 #[doc = " // payload is a `CreateUser`"]
79 #[doc = " }"]
80 #[doc = ""]
81 #[doc = " let app = Router::new().route(\"/users\", post(create_user));"]
82 #[doc = " # let _: Router = app;"]
83 #[doc = " ```"]
84 #[doc = " When used as a response, it can serialize any type that implements [`serde::Serialize`] to"]
85 #[doc = " `"]
86 #[doc = stringify!($name)]
87 #[doc = "`, and will automatically set `Content-Type:"]
88 #[doc = $content_type]
89 #[doc = "` header."]
90 #[doc = ""]
91 #[doc = " # Response example"]
92 #[doc = ""]
93 #[doc = " ```rust,ignore"]
94 #[doc = " use axum::{"]
95 #[doc = " extract::Path,"]
96 #[doc = " routing::get,"]
97 #[doc = " Router,"]
98 #[doc = " };"]
99 #[doc = concat!(" use axum_serde::", stringify!($ext), ";")]
100 #[doc = " use serde::Serialize;"]
101 #[doc = " use uuid::Uuid;"]
102 #[doc = ""]
103 #[doc = " #[derive(Serialize)]"]
104 #[doc = " struct User {"]
105 #[doc = " id: Uuid,"]
106 #[doc = " username: String,"]
107 #[doc = " }"]
108 #[doc = ""]
109 #[doc = concat!(" async fn get_user(Path(user_id) : Path<Uuid>) -> ", stringify!($ext), "<User> {")]
110 #[doc = " let user = find_user(user_id).await;"]
111 #[doc = concat!(" ", stringify!($ext), "(user)")]
112 #[doc = " }"]
113 #[doc = ""]
114 #[doc = " async fn find_user(user_id: Uuid) -> User {"]
115 #[doc = " // ..."]
116 #[doc = " # unimplemented!()"]
117 #[doc = " }"]
118 #[doc = ""]
119 #[doc = " let app = Router::new().route(\"/users/:id\", get(get_user));"]
120 #[doc = " # let _: Router = app;"]
121 #[doc = " ```"]
122 #[derive(Debug, Clone, Copy, Default)]
123 #[must_use]
124 pub struct $ext<T>(pub T);
125
126 impl<T> From<T> for $ext<T> {
127 fn from(inner: T) -> Self {
128 Self(inner)
129 }
130 }
131
132 impl<T> std::ops::Deref for $ext<T> {
133 type Target = T;
134
135 #[inline]
136 fn deref(&self) -> &Self::Target {
137 &self.0
138 }
139 }
140
141 impl<T> std::ops::DerefMut for $ext<T> {
142 #[inline]
143 fn deref_mut(&mut self) -> &mut Self::Target {
144 &mut self.0
145 }
146 }
147
148 impl<T> $ext<T> {
149 #[doc = "Consumes the `"]
150 #[doc = stringify!($ext)]
151 #[doc = "` extractor and returns the inner data."]
152 pub fn into_inner(self) -> T {
153 self.0
154 }
155
156 #[doc = "Content type of "]
157 #[doc = stringify!($name)]
158 #[doc = " format."]
159 pub const CONTENT_TYPE: &'static str = $content_type;
160
161 #[doc = concat!("Construct a `", stringify!($ext), "<T>` from a byte slice.")]
162 #[doc = concat!("Most users should prefer to use the FromRequest impl but special cases may require first extracting a Request into Bytes then optionally constructing a `", stringify!($ext), "<T>`.")]
163 pub fn from_bytes(bytes: &[u8]) -> Result<Self, $crate::Rejection<$de_err>>
164 where T: $crate::macros::DeserializeOwned
165 {
166 Ok($ext($de(&bytes).map_err($crate::Rejection::InvalidContentFormat)?))
167 }
168 }
169
170 impl<T, S> $crate::macros::FromRequest<S> for $ext<T>
171 where
172 T: $crate::macros::DeserializeOwned,
173 S: Send + Sync,
174 {
175 type Rejection = $crate::Rejection<$de_err>;
176
177 async fn from_request(
178 req: $crate::macros::Request,
179 state: &S,
180 ) -> Result<Self, Self::Rejection> {
181 if $crate::check_content_type(req.headers(), Self::CONTENT_TYPE) {
182 let src = $crate::macros::Bytes::from_request(req, state).await?;
183 Self::from_bytes(&src)
184 } else {
185 Err($crate::Rejection::UnsupportedMediaType(Self::CONTENT_TYPE))
186 }
187 }
188 }
189
190 impl<T> $crate::macros::IntoResponse for $ext<T>
191 where
192 T: $crate::macros::Serialize,
193 {
194 fn into_response(self) -> $crate::macros::Response {
195 match $ser(&self.0) {
196 Ok(vec) => (
197 [(
198 $crate::macros::http::header::CONTENT_TYPE,
199 $crate::macros::http::HeaderValue::from_static(Self::CONTENT_TYPE),
200 )],
201 vec,
202 )
203 .into_response(),
204 Err(err) => (
205 $crate::macros::http::StatusCode::INTERNAL_SERVER_ERROR,
206 [(
207 $crate::macros::http::header::CONTENT_TYPE,
208 $crate::macros::http::HeaderValue::from_static($crate::macros::mime::TEXT_PLAIN_UTF_8.as_ref()),
209 )],
210 err.to_string(),
211 )
212 .into_response(),
213 }
214 }
215 }
216
217 #[cfg(test)]
218 mod $test {
219 use super::*;
220 use axum::Router;
221 use axum::routing::{get, post};
222 use $crate::macros::{
223 Bytes,
224 {Deserialize, Serialize},
225 http::{
226 StatusCode,
227 header::{HeaderValue, CONTENT_TYPE}
228 }
229 };
230
231 #[derive(Deserialize, Serialize, Default)]
232 struct Value {
233 v0: String,
234 v1: i32,
235 }
236
237 const TEST_ROUTE: &'static str = "/value";
238 const EXT_CONTENT_TYPE: &'static str = $content_type;
239
240 #[tokio::test]
241 async fn extractor() {
242 use axum_test::TestServer;
243
244 async fn handler($ext(_user): $ext<Value>) {
245 }
246
247 let my_app = Router::new()
248 .route(TEST_ROUTE, post(handler));
249
250 let server = TestServer::new(my_app).expect("Failed to create test server");
251
252 let value = Value::default();
253 let bytes = Bytes::from($ser(&value).expect("Failed to serialize value"));
254
255 let response = server.post(TEST_ROUTE)
256 .bytes(bytes.clone())
257 .add_header(CONTENT_TYPE, HeaderValue::from_static(EXT_CONTENT_TYPE))
258 .await;
259
260 assert_eq!(response.status_code(), StatusCode::OK);
261
262 let response = server.post(TEST_ROUTE)
263 .bytes(bytes.clone())
264 .await;
265 assert_eq!(response.status_code(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
266
267 let response = server.post(TEST_ROUTE)
268 .bytes(bytes)
269 .add_header(CONTENT_TYPE, HeaderValue::from_static("invalid/type"))
270 .await;
271 assert_eq!(response.status_code(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
272
273 let response = server.post(TEST_ROUTE)
274 .bytes($crate::macros::Bytes::from_static(b"invalid data"))
275 .add_header(CONTENT_TYPE, HeaderValue::from_static(EXT_CONTENT_TYPE))
276 .await;
277 assert_eq!(response.status_code(), StatusCode::UNPROCESSABLE_ENTITY);
278 }
279
280 #[tokio::test]
281 async fn response() {
282 use axum_test::TestServer;
283
284 async fn handler() -> $ext<Value> {
285 $ext(Value::default())
286 }
287
288 let my_app = Router::new()
289 .route(TEST_ROUTE, get(handler));
290
291 let server = TestServer::new(my_app).expect("Failed to create test server");
292
293 let response = server.get(TEST_ROUTE).await;
294 assert!($crate::check_content_type(response.headers(), EXT_CONTENT_TYPE));
295 let body = response.as_bytes();
296 let _value: Value = $de(&body).expect("Failed to deserialize");
297 }
298 }
299 };
300}