modo/extractor/json.rs
1use axum::extract::FromRequest;
2use http::Request;
3use serde::de::DeserializeOwned;
4
5use crate::sanitize::Sanitize;
6
7/// Axum extractor that deserializes a JSON request body into `T` and then sanitizes it.
8///
9/// Returns a 400 Bad Request error if the body is not valid JSON or cannot be
10/// deserialized. `T` must implement both [`serde::de::DeserializeOwned`] and
11/// [`crate::sanitize::Sanitize`].
12///
13/// # Example
14///
15/// ```
16/// use modo::extractor::JsonRequest;
17/// use modo::Sanitize;
18/// use serde::Deserialize;
19///
20/// #[derive(Deserialize)]
21/// struct CreateItem { name: String }
22///
23/// impl Sanitize for CreateItem {
24/// fn sanitize(&mut self) { self.name = self.name.trim().to_string(); }
25/// }
26///
27/// async fn create(JsonRequest(body): JsonRequest<CreateItem>) {
28/// // body.name is already trimmed
29/// }
30/// ```
31pub struct JsonRequest<T>(pub T);
32
33impl<S, T> FromRequest<S> for JsonRequest<T>
34where
35 S: Send + Sync,
36 T: DeserializeOwned + Sanitize,
37{
38 type Rejection = crate::error::Error;
39
40 async fn from_request(
41 req: Request<axum::body::Body>,
42 state: &S,
43 ) -> Result<Self, Self::Rejection> {
44 let axum::Json(mut value) = axum::Json::<T>::from_request(req, state)
45 .await
46 .map_err(|e| crate::error::Error::bad_request(format!("invalid JSON: {e}")))?;
47 value.sanitize();
48 Ok(JsonRequest(value))
49 }
50}