Skip to main content

socle/
extract.rs

1//! Axum extractors provided by socle.
2
3#[cfg(feature = "validation")]
4pub use validation::Valid;
5
6#[cfg(feature = "validation")]
7mod validation {
8    use axum::Json;
9    use axum::extract::{FromRequest, Request};
10    use axum::response::{IntoResponse, Response};
11    use validator::Validate;
12
13    use crate::handler_error::{ErrorCode, HandlerError, ValidationError};
14
15    /// Axum extractor that deserializes a JSON body and then validates it.
16    pub struct Valid<T>(pub T);
17
18    /// Rejection type returned when deserialization or validation fails.
19    pub struct ValidRejection(Response);
20
21    impl IntoResponse for ValidRejection {
22        fn into_response(self) -> Response {
23            self.0
24        }
25    }
26
27    impl<T, S> FromRequest<S> for Valid<T>
28    where
29        T: serde::de::DeserializeOwned + Validate + Send + 'static,
30        S: Send + Sync,
31    {
32        type Rejection = ValidRejection;
33
34        async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
35            let Json(value) = Json::<T>::from_request(req, state)
36                .await
37                .map_err(|rejection| {
38                    let err = HandlerError::new(ErrorCode::BadRequest, rejection.to_string());
39                    ValidRejection(err.into_response())
40                })?;
41
42            value.validate().map_err(|errs| {
43                let field_errors = errs
44                    .field_errors()
45                    .into_iter()
46                    .flat_map(|(field, errors)| {
47                        errors.iter().map(move |e| ValidationError {
48                            field: format!("/{field}"),
49                            message: e.message.as_deref().unwrap_or("invalid value").to_string(),
50                            rule: Some(e.code.to_string()),
51                        })
52                    })
53                    .collect::<Vec<_>>();
54
55                let err =
56                    HandlerError::new(ErrorCode::ValidationFailed, "request validation failed")
57                        .with_errors(field_errors);
58                ValidRejection(err.into_response())
59            })?;
60
61            Ok(Valid(value))
62        }
63    }
64}