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))
  }
}