diff --git a/src/lib.rs b/src/lib.rs index 6af94d2..3be4abd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ use axum::response::{IntoResponse, Response}; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::ops::{Deref, DerefMut}; -use validator::{Validate, ValidationErrors}; +use validator::{Validate, ValidateArgs, ValidationErrors}; /// Http status code returned when there are validation errors. #[cfg(feature = "422")] @@ -73,6 +73,31 @@ impl Valid { } } +/// +#[derive(Debug, Clone, Copy, Default)] +pub struct ValidArgs(pub E); + +impl Deref for ValidArgs { + type Target = E; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ValidArgs { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl ValidArgs { + /// Consume the `ValidArgs` extractor and returns the inner type. + pub fn into_inner(self) -> E { + self.0 + } +} + /// `ValidationContext` configures the response returned when validation fails. /// /// By providing a ValidationContext to the Valid extractor, you can customize @@ -260,6 +285,14 @@ pub trait HasValidate { fn get_validate(&self) -> &Self::Validate; } +/// +pub trait HasValidateArgs<'v> { + /// Inner type that can be validated for correctness + type ValidateArgs: ValidateArgs<'v>; + /// Get the inner value + fn get_validate_args(&self) -> &Self::ValidateArgs; +} + #[async_trait] impl FromRequest for Valid where @@ -319,6 +352,69 @@ where } } +#[async_trait] +impl<'v, S, B, E> FromRequest for ValidArgs +where + S: Send + Sync, + B: Send + Sync + 'static, + E: HasValidateArgs<'v> + FromRequest, + E::ValidateArgs: ValidateArgs<'v>, + <>::ValidateArgs as ValidateArgs<'v>>::Args: FromRef, + ValidationContext: FromRef, +{ + type Rejection = ValidRejection<>::Rejection>; + + async fn from_request(req: Request, state: &S) -> Result { + let context: ValidationContext = FromRef::from_ref(state); + let inner = E::from_request(req, state) + .await + .map_err(|e| ValidRejection { + error: ValidError::Inner(e), + response_builder: context.response_builder, + })?; + let args = FromRef::from_ref(state); + inner + .get_validate_args() + .validate_args(args) + .map_err(|e| ValidRejection { + error: ValidError::Valid(e), + response_builder: context.response_builder, + })?; + Ok(ValidArgs(inner)) + } +} + +#[async_trait] +impl<'v, S, E> FromRequestParts for ValidArgs +where + S: Send + Sync, + E: HasValidateArgs<'v> + FromRequestParts, + E::ValidateArgs: ValidateArgs<'v>, + <>::ValidateArgs as ValidateArgs<'v>>::Args: FromRef, + ValidationContext: FromRef, +{ + type Rejection = ValidRejection<>::Rejection>; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let context: ValidationContext = FromRef::from_ref(state); + let inner = E::from_request_parts(parts, state) + .await + .map_err(|e| ValidRejection { + error: ValidError::Inner(e), + response_builder: context.response_builder, + })?; + let args = FromRef::from_ref(state); + inner + .get_validate_args() + .validate_args(args) + .map_err(|e| ValidRejection { + error: ValidError::Valid(e), + response_builder: context.response_builder, + })?; + Ok(ValidArgs(inner)) + } +} + #[cfg(test)] pub mod tests { use crate::{Valid, ValidError};