Skip to main content

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}