oxedyne/fe2o3/fe2o3_text/tests/annealer_corpus/axum_json.rs
11.3 KiB, 1 run
created by r1870400018:11690, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | use crate::extract::Request; |
| 2 | use crate::extract::{rejection::*, FromRequest}; |
| 3 | use axum_core::extract::OptionalFromRequest; |
| 4 | use axum_core::response::{IntoResponse, IntoResponseFailed, Response}; |
| 5 | use bytes::{BufMut, Bytes, BytesMut}; |
| 6 | use http::{ |
| 7 | header::{self, HeaderMap, HeaderValue}, |
| 8 | StatusCode, |
| 9 | }; |
| 10 | use serde_core::{de::DeserializeOwned, Serialize}; |
| 11 | |
| 12 | /// JSON Extractor / Response. |
| 13 | /// |
| 14 | /// When used as an extractor, it can deserialize request bodies into some type that |
| 15 | /// implements [`serde::de::DeserializeOwned`]. The request will be rejected (and a [`JsonRejection`] will |
| 16 | /// be returned) if: |
| 17 | /// |
| 18 | /// - The request doesn't have a `Content-Type: application/json` (or similar) header. |
| 19 | /// - The body doesn't contain syntactically valid JSON. |
| 20 | /// - The body contains syntactically valid JSON, but it couldn't be deserialized into the target type. |
| 21 | /// - Buffering the request body fails. |
| 22 | /// |
| 23 | /// ⚠️ Since parsing JSON requires consuming the request body, the `Json` extractor must be |
| 24 | /// *last* if there are multiple extractors in a handler. |
| 25 | /// See ["the order of extractors"][order-of-extractors] |
| 26 | /// |
| 27 | /// [order-of-extractors]: crate::extract#the-order-of-extractors |
| 28 | /// |
| 29 | /// See [`JsonRejection`] for more details. |
| 30 | /// |
| 31 | /// # Extractor example |
| 32 | /// |
| 33 | /// ```rust,no_run |
| 34 | /// use axum::{ |
| 35 | /// extract, |
| 36 | /// routing::post, |
| 37 | /// Router, |
| 38 | /// }; |
| 39 | /// use serde::Deserialize; |
| 40 | /// |
| 41 | /// #[derive(Deserialize)] |
| 42 | /// struct CreateUser { |
| 43 | /// email: String, |
| 44 | /// password: String, |
| 45 | /// } |
| 46 | /// |
| 47 | /// async fn create_user(extract::Json(payload): extract::Json<CreateUser>) { |
| 48 | /// // payload is a `CreateUser` |
| 49 | /// } |
| 50 | /// |
| 51 | /// let app = Router::new().route("/users", post(create_user)); |
| 52 | /// # let _: Router = app; |
| 53 | /// ``` |
| 54 | /// |
| 55 | /// When used as a response, it can serialize any type that implements [`serde::Serialize`] to |
| 56 | /// `JSON`, and will automatically set `Content-Type: application/json` header. |
| 57 | /// |
| 58 | /// If the [`Serialize`] implementation decides to fail |
| 59 | /// or if a map with non-string keys is used, |
| 60 | /// a 500 response will be issued |
| 61 | /// whose body is the error message in UTF-8. |
| 62 | /// |
| 63 | /// # Response example |
| 64 | /// |
| 65 | /// ``` |
| 66 | /// use axum::{ |
| 67 | /// extract::Path, |
| 68 | /// routing::get, |
| 69 | /// Router, |
| 70 | /// Json, |
| 71 | /// }; |
| 72 | /// use serde::Serialize; |
| 73 | /// use uuid::Uuid; |
| 74 | /// |
| 75 | /// #[derive(Serialize)] |
| 76 | /// struct User { |
| 77 | /// id: Uuid, |
| 78 | /// username: String, |
| 79 | /// } |
| 80 | /// |
| 81 | /// async fn get_user(Path(user_id) : Path<Uuid>) -> Json<User> { |
| 82 | /// let user = find_user(user_id).await; |
| 83 | /// Json(user) |
| 84 | /// } |
| 85 | /// |
| 86 | /// async fn find_user(user_id: Uuid) -> User { |
| 87 | /// // ... |
| 88 | /// # unimplemented!() |
| 89 | /// } |
| 90 | /// |
| 91 | /// let app = Router::new().route("/users/{id}", get(get_user)); |
| 92 | /// # let _: Router = app; |
| 93 | /// ``` |
| 94 | #[derive(Debug, Clone, Copy, Default)] |
| 95 | #[cfg_attr(docsrs, doc(cfg(feature = "json")))] |
| 96 | #[must_use] |
| 97 | pub struct Json<T>(pub T); |
| 98 | |
| 99 | impl<T, S> FromRequest<S> for Json<T> |
| 100 | where |
| 101 | T: DeserializeOwned, |
| 102 | S: Send + Sync, |
| 103 | { |
| 104 | type Rejection = JsonRejection; |
| 105 | |
| 106 | async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> { |
| 107 | if !json_content_type(req.headers()) { |
| 108 | return Err(MissingJsonContentType.into()); |
| 109 | } |
| 110 | |
| 111 | let bytes = Bytes::from_request(req, state).await?; |
| 112 | Self::from_bytes(&bytes) |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | impl<T, S> OptionalFromRequest<S> for Json<T> |
| 117 | where |
| 118 | T: DeserializeOwned, |
| 119 | S: Send + Sync, |
| 120 | { |
| 121 | type Rejection = JsonRejection; |
| 122 | |
| 123 | async fn from_request(req: Request, state: &S) -> Result<Option<Self>, Self::Rejection> { |
| 124 | let headers = req.headers(); |
| 125 | if headers.get(header::CONTENT_TYPE).is_some() { |
| 126 | if json_content_type(headers) { |
| 127 | let bytes = Bytes::from_request(req, state).await?; |
| 128 | Ok(Some(Self::from_bytes(&bytes)?)) |
| 129 | } else { |
| 130 | Err(MissingJsonContentType.into()) |
| 131 | } |
| 132 | } else { |
| 133 | Ok(None) |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | fn json_content_type(headers: &HeaderMap) -> bool { |
| 139 | headers |
| 140 | .get(header::CONTENT_TYPE) |
| 141 | .and_then(|content_type| content_type.to_str().ok()) |
| 142 | .and_then(|content_type| content_type.parse::<mime::Mime>().ok()) |
| 143 | .is_some_and(|mime| { |
| 144 | mime.type_() == "application" |
| 145 | && (mime.subtype() == "json" || mime.suffix().is_some_and(|name| name == "json")) |
| 146 | }) |
| 147 | } |
| 148 | |
| 149 | axum_core::__impl_deref!(Json); |
| 150 | |
| 151 | impl<T> From<T> for Json<T> { |
| 152 | fn from(inner: T) -> Self { |
| 153 | Self(inner) |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | impl<T> Json<T> |
| 158 | where |
| 159 | T: DeserializeOwned, |
| 160 | { |
| 161 | /// Construct a `Json<T>` from a byte slice. Most users should prefer to use the `FromRequest` impl |
| 162 | /// but special cases may require first extracting a `Request` into `Bytes` then optionally |
| 163 | /// constructing a `Json<T>`. |
| 164 | pub fn from_bytes(bytes: &[u8]) -> Result<Self, JsonRejection> { |
| 165 | // Extracted into separate fn so it's only compiled once for all T. |
| 166 | fn make_rejection(err: serde_path_to_error::Error<serde_json::Error>) -> JsonRejection { |
| 167 | match err.inner().classify() { |
| 168 | serde_json::error::Category::Data => JsonDataError::from_err(err).into(), |
| 169 | serde_json::error::Category::Syntax | serde_json::error::Category::Eof => { |
| 170 | JsonSyntaxError::from_err(err).into() |
| 171 | } |
| 172 | serde_json::error::Category::Io => { |
| 173 | if cfg!(debug_assertions) { |
| 174 | // we don't use `serde_json::from_reader` and instead always buffer |
| 175 | // bodies first, so we shouldn't encounter any IO errors |
| 176 | unreachable!() |
| 177 | } else { |
| 178 | JsonSyntaxError::from_err(err).into() |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | let mut deserializer = serde_json::Deserializer::from_slice(bytes); |
| 185 | |
| 186 | serde_path_to_error::deserialize(&mut deserializer) |
| 187 | .map_err(make_rejection) |
| 188 | .and_then(|value| { |
| 189 | deserializer |
| 190 | .end() |
| 191 | .map(|()| Self(value)) |
| 192 | .map_err(|err| JsonSyntaxError::from_err(err).into()) |
| 193 | }) |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | impl<T> IntoResponse for Json<T> |
| 198 | where |
| 199 | T: Serialize, |
| 200 | { |
| 201 | fn into_response(self) -> Response { |
| 202 | // Extracted into separate fn so it's only compiled once for all T. |
| 203 | fn make_response(buf: BytesMut, ser_result: serde_json::Result<()>) -> Response { |
| 204 | match ser_result { |
| 205 | Ok(()) => ( |
| 206 | [( |
| 207 | header::CONTENT_TYPE, |
| 208 | HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), |
| 209 | )], |
| 210 | buf.freeze(), |
| 211 | ) |
| 212 | .into_response(), |
| 213 | Err(err) => ( |
| 214 | StatusCode::INTERNAL_SERVER_ERROR, |
| 215 | [( |
| 216 | header::CONTENT_TYPE, |
| 217 | HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()), |
| 218 | )], |
| 219 | IntoResponseFailed, |
| 220 | err.to_string(), |
| 221 | ) |
| 222 | .into_response(), |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | // Use a small initial capacity of 128 bytes like serde_json::to_vec |
| 227 | // https://docs.rs/serde_json/1.0.82/src/serde_json/ser.rs.html#2189 |
| 228 | let mut buf = BytesMut::with_capacity(128).writer(); |
| 229 | let res = serde_json::to_writer(&mut buf, &self.0); |
| 230 | make_response(buf.into_inner(), res) |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | #[cfg(test)] |
| 235 | mod tests { |
| 236 | use super::*; |
| 237 | use crate::{routing::post, test_helpers::*, Router}; |
| 238 | use serde::Deserialize; |
| 239 | use serde_json::{json, Value}; |
| 240 | |
| 241 | #[crate::test] |
| 242 | async fn deserialize_body() { |
| 243 | #[derive(Debug, Deserialize)] |
| 244 | struct Input { |
| 245 | foo: String, |
| 246 | } |
| 247 | |
| 248 | let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo })); |
| 249 | |
| 250 | let client = TestClient::new(app); |
| 251 | let res = client.post("/").json(&json!({ "foo": "bar" })).await; |
| 252 | let body = res.text().await; |
| 253 | |
| 254 | assert_eq!(body, "bar"); |
| 255 | } |
| 256 | |
| 257 | #[crate::test] |
| 258 | async fn consume_body_to_json_requires_json_content_type() { |
| 259 | #[derive(Debug, Deserialize)] |
| 260 | struct Input { |
| 261 | foo: String, |
| 262 | } |
| 263 | |
| 264 | let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo })); |
| 265 | |
| 266 | let client = TestClient::new(app); |
| 267 | let res = client.post("/").body(r#"{ "foo": "bar" }"#).await; |
| 268 | |
| 269 | let status = res.status(); |
| 270 | |
| 271 | assert_eq!(status, StatusCode::UNSUPPORTED_MEDIA_TYPE); |
| 272 | } |
| 273 | |
| 274 | #[crate::test] |
| 275 | async fn json_content_types() { |
| 276 | async fn valid_json_content_type(content_type: &str) -> bool { |
| 277 | println!("testing {content_type:?}"); |
| 278 | |
| 279 | let app = Router::new().route("/", post(|Json(_): Json<Value>| async {})); |
| 280 | |
| 281 | let res = TestClient::new(app) |
| 282 | .post("/") |
| 283 | .header("content-type", content_type) |
| 284 | .body("{}") |
| 285 | .await; |
| 286 | |
| 287 | res.status() == StatusCode::OK |
| 288 | } |
| 289 | |
| 290 | assert!(valid_json_content_type("application/json").await); |
| 291 | assert!(valid_json_content_type("application/json; charset=utf-8").await); |
| 292 | assert!(valid_json_content_type("application/json;charset=utf-8").await); |
| 293 | assert!(valid_json_content_type("application/cloudevents+json").await); |
| 294 | assert!(!valid_json_content_type("text/json").await); |
| 295 | } |
| 296 | |
| 297 | #[crate::test] |
| 298 | async fn invalid_json_syntax() { |
| 299 | let app = Router::new().route("/", post(|_: Json<serde_json::Value>| async {})); |
| 300 | |
| 301 | let client = TestClient::new(app); |
| 302 | let res = client |
| 303 | .post("/") |
| 304 | .body("{") |
| 305 | .header("content-type", "application/json") |
| 306 | .await; |
| 307 | |
| 308 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); |
| 309 | } |
| 310 | |
| 311 | #[crate::test] |
| 312 | async fn extra_chars_after_valid_json_syntax() { |
| 313 | #[derive(Debug, Deserialize)] |
| 314 | struct Input { |
| 315 | foo: String, |
| 316 | } |
| 317 | |
| 318 | let app = Router::new().route("/", post(|input: Json<Input>| async { input.0.foo })); |
| 319 | |
| 320 | let client = TestClient::new(app); |
| 321 | let res = client |
| 322 | .post("/") |
| 323 | .body(r#"{ "foo": "bar" } baz "#) |
| 324 | .header("content-type", "application/json") |
| 325 | .await; |
| 326 | |
| 327 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); |
| 328 | let body_text = res.text().await; |
| 329 | assert_eq!( |
| 330 | body_text, |
| 331 | "Failed to parse the request body as JSON: trailing characters at line 1 column 18" |
| 332 | ); |
| 333 | } |
| 334 | |
| 335 | #[derive(Deserialize)] |
| 336 | struct Foo { |
| 337 | #[allow(dead_code)] |
| 338 | a: i32, |
| 339 | #[allow(dead_code)] |
| 340 | b: Vec<Bar>, |
| 341 | } |
| 342 | |
| 343 | #[derive(Deserialize)] |
| 344 | struct Bar { |
| 345 | #[allow(dead_code)] |
| 346 | x: i32, |
| 347 | #[allow(dead_code)] |
| 348 | y: i32, |
| 349 | } |
| 350 | |
| 351 | #[crate::test] |
| 352 | async fn invalid_json_data() { |
| 353 | let app = Router::new().route("/", post(|_: Json<Foo>| async {})); |
| 354 | |
| 355 | let client = TestClient::new(app); |
| 356 | let res = client |
| 357 | .post("/") |
| 358 | .body("{\"a\": 1, \"b\": [{\"x\": 2}]}") |
| 359 | .header("content-type", "application/json") |
| 360 | .await; |
| 361 | |
| 362 | assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY); |
| 363 | let body_text = res.text().await; |
| 364 | assert_eq!( |
| 365 | body_text, |
| 366 | "Failed to deserialize the JSON body into the target type: b[0]: missing field `y` at line 1 column 23" |
| 367 | ); |
| 368 | } |
| 369 | } |