1#[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 pub struct Valid<T>(pub T);
17
18 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}