darkwing/server/extractors/validation_extractor.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
use axum::{
async_trait,
extract::{rejection::JsonRejection, FromRequest},
Json,
};
use serde::de::DeserializeOwned;
use validator::Validate;
use crate::server::error::Error;
/// A wrapper type that validates incoming JSON request bodies against a set of
/// validation rules.
///
/// This extractor combines JSON deserialization with automatic validation using
/// the `validator` crate. It's designed to be used as a request extractor in
/// Axum route handlers.
///
/// # Type Parameters
///
/// * `T` - The type to deserialize the JSON into. Must implement both
/// `DeserializeOwned` and `Validate`.
///
/// # Examples
///
/// ```rust
/// use serde::Deserialize;
/// use validator::Validate;
///
/// #[derive(Deserialize, Validate)]
/// struct CreateUser {
/// #[validate(length(min = 3, max = 50))]
/// username: String,
/// #[validate(email)]
/// email: String,
/// }
///
/// async fn create_user(
/// ValidationExtractor(payload): ValidationExtractor<CreateUser>
/// ) -> impl Response {
/// // payload is guaranteed to be valid here
/// // ...
/// }
/// ```
///
/// # Validation Flow
///
/// 1. The incoming request body is first deserialized as JSON into type `T`
/// 2. The deserialized value is validated using its `Validate` implementation
/// 3. If both steps succeed, the value is wrapped in `ValidationExtractor` and
/// passed to the handler
/// 4. If either step fails, an error is returned and the handler is not called
pub struct ValidationExtractor<T>(pub T);
/// Implementation of `FromRequest` for `ValidationExtractor<T>`.
///
/// This implementation enables automatic extraction and validation of JSON
/// request bodies in Axum route handlers.
///
/// # Type Parameters
///
/// * `T` - The type to deserialize and validate (must implement
/// `DeserializeOwned` and `Validate`)
/// * `S` - The state type for the Axum application
///
/// # Errors
///
/// Returns an error in the following cases:
/// - JSON deserialization fails
/// - Validation rules are not satisfied
#[async_trait]
impl<T, S> FromRequest<S> for ValidationExtractor<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
Json<T>: FromRequest<S, Rejection = JsonRejection>,
{
type Rejection = Error;
async fn from_request(
req: axum::extract::Request,
state: &S,
) -> Result<Self, Self::Rejection> {
let Json(value) = Json::<T>::from_request(req, state).await?;
value.validate()?;
Ok(ValidationExtractor(value))
}
}