Skip to main content

purwa_core/
extract.rs

1//! Validating Axum extractors (JSON and URL-encoded form).
2
3use axum::Json;
4use axum::extract::{Form, FromRequest, Request};
5use serde::de::DeserializeOwned;
6use validator::Validate;
7
8use crate::PurwaError;
9
10/// JSON body extractor: deserialize then run [`Validate`].
11///
12/// Use as the last extractor in the handler when combined with others.
13#[derive(Debug, Clone, Copy, Default)]
14pub struct ValidatedJson<T>(pub T);
15
16impl<T, S> FromRequest<S> for ValidatedJson<T>
17where
18    T: DeserializeOwned + Validate + Send + 'static,
19    S: Send + Sync,
20{
21    type Rejection = PurwaError;
22
23    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
24        let Json(inner) = Json::<T>::from_request(req, state)
25            .await
26            .map_err(PurwaError::from_json_rejection)?;
27        inner.validate()?;
28        Ok(ValidatedJson(inner))
29    }
30}
31
32/// Form / query extractor: deserialize then run [`Validate`].
33///
34/// Same semantics as Axum [`Form`] (GET query vs POST `application/x-www-form-urlencoded`).
35#[derive(Debug, Clone, Copy, Default)]
36pub struct ValidatedForm<T>(pub T);
37
38impl<T, S> FromRequest<S> for ValidatedForm<T>
39where
40    T: DeserializeOwned + Validate + Send + 'static,
41    S: Send + Sync,
42{
43    type Rejection = PurwaError;
44
45    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
46        let Form(inner) = Form::<T>::from_request(req, state)
47            .await
48            .map_err(PurwaError::from_form_rejection)?;
49        inner.validate()?;
50        Ok(ValidatedForm(inner))
51    }
52}