#![doc = include_str!("../README.md")] #![deny(unsafe_code, missing_docs, clippy::unwrap_used)] #[cfg(feature = "extra")] pub mod extra; #[cfg(feature = "form")] pub mod form; pub mod garde; #[cfg(feature = "json")] pub mod json; #[cfg(feature = "msgpack")] pub mod msgpack; pub mod path; #[cfg(feature = "query")] pub mod query; #[cfg(test)] pub mod test; #[cfg(feature = "typed_header")] pub mod typed_header; #[cfg(feature = "typed_multipart")] pub mod typed_multipart; #[cfg(feature = "yaml")] pub mod yaml; use axum::async_trait; use axum::extract::{FromRef, FromRequest, FromRequestParts}; use axum::http::request::Parts; use axum::http::{Request, StatusCode}; use axum::response::{IntoResponse, Response}; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; use std::ops::{Deref, DerefMut}; use validator::{Validate, ValidateArgs, ValidationErrors}; /// Http status code returned when there are validation errors. #[cfg(feature = "422")] pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::UNPROCESSABLE_ENTITY; /// Http status code returned when there are validation errors. #[cfg(not(feature = "422"))] pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::BAD_REQUEST; /// # `Valid` data extractor /// /// This extractor can be used in combination with axum's extractors like /// Json, Form, Query, Path, etc to validate their inner data automatically. /// It can also work with custom extractors that implement the `HasValidate` trait. /// /// See the docs for each integration module to find examples of using /// `Valid` with that extractor. /// /// For examples with custom extractors, check out the `tests/custom.rs` file. /// #[derive(Debug, Clone, Copy, Default)] pub struct Valid(pub E); impl Deref for Valid { type Target = E; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Valid { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Display for ValidEx { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl Valid { /// Consume the `Valid` extractor and returns the inner type. pub fn into_inner(self) -> E { self.0 } } /// # `ValidEx` data extractor /// /// `ValidEx` can be incorporated with extractors from various modules, similar to `Valid`. /// Two differences exist between `ValidEx` and `Valid`: /// /// - The inner data type in `ValidEx` implements `ValidateArgs` instead of `Validate`. /// - `ValidEx` includes a second field that represents arguments used during validation of the first field. /// /// The implementation of `ValidateArgs` is often automatically handled by validator's derive macros /// (for more details, please refer to the validator's documentation). /// /// Although current module documentation predominantly showcases `Valid` examples, the usage of `ValidEx` is analogous. /// #[derive(Debug, Clone, Copy, Default)] pub struct ValidEx(pub E, pub A); impl Deref for ValidEx { type Target = E; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for ValidEx { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Display for Valid { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl ValidEx { /// Consumes the `ValidEx` and returns the validated data within. /// /// This returns the `E` type which represents the data that has been /// successfully validated. pub fn into_inner(self) -> E { self.0 } /// Returns a reference to the validation arguments. /// /// This provides access to the `A` type which contains the arguments used /// to validate the data. These arguments were passed to the validation /// function. pub fn arguments<'a>(&'a self) -> <>::T as ValidateArgs<'a>>::Args where A: Arguments<'a>, { self.1.get() } } fn response_builder(ve: ValidationErrors) -> Response { #[cfg(feature = "into_json")] { (VALIDATION_ERROR_STATUS, axum::Json(ve)).into_response() } #[cfg(not(feature = "into_json"))] { (VALIDATION_ERROR_STATUS, ve.to_string()).into_response() } } /// `Arguments` provides the validation arguments for the data type `T`. /// /// This trait has an associated type `T` which represents the data type to /// validate. `T` must implement the `ValidateArgs` trait which defines the /// validation logic. /// /// It's important to mention that types implementing `Arguments` should be a part of the router's state /// (either through implementing `FromRef` or by directly becoming the state) /// to enable automatic arguments retrieval during validation. /// pub trait Arguments<'a> { /// The data type to validate using this arguments type T: ValidateArgs<'a>; /// This method gets the arguments required by `ValidateArgs::validate_args` fn get(&'a self) -> <>::T as ValidateArgs<'a>>::Args; } /// `ValidRejection` is returned when the `Valid` extractor fails. /// /// This enumeration captures two types of errors that can occur when using `Valid`: errors related to the validation /// logic itself (encapsulated in `Valid`), and errors that may arise within the inner extractor (represented by `Inner`). /// #[derive(Debug)] pub enum ValidRejection { /// `Valid` variant captures errors related to the validation logic. It contains `ValidationErrors` /// which is a collection of validation failures for each field. Valid(ValidationErrors), /// `Inner` variant represents potential errors that might occur within the inner extractor. Inner(E), } impl Display for ValidRejection { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { ValidRejection::Valid(errors) => write!(f, "{errors}"), ValidRejection::Inner(error) => write!(f, "{error}"), } } } impl Error for ValidRejection { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { ValidRejection::Valid(ve) => Some(ve), ValidRejection::Inner(e) => Some(e), } } } impl From for ValidRejection { fn from(value: ValidationErrors) -> Self { Self::Valid(value) } } impl IntoResponse for ValidRejection { fn into_response(self) -> Response { match self { ValidRejection::Valid(ve) => response_builder(ve), ValidRejection::Inner(e) => e.into_response(), } } } /// Trait for types that can supply a reference that can be validated. /// /// Extractor types `T` that implement this trait can be used with `Valid`. /// pub trait HasValidate { /// Inner type that can be validated for correctness type Validate; /// Get the inner value fn get_validate(&self) -> &Self::Validate; } /// Trait for types that can supply a reference that can be validated using arguments. /// /// Extractor types `T` that implement this trait can be used with `ValidEx`. /// pub trait HasValidateArgs<'v> { /// Inner type that can be validated using arguments type ValidateArgs: ValidateArgs<'v>; /// Get the inner value fn get_validate_args(&self) -> &Self::ValidateArgs; } #[async_trait] impl FromRequest for Valid where State: Send + Sync, Body: Send + Sync + 'static, Extractor: HasValidate + FromRequest, Extractor::Validate: Validate, { type Rejection = ValidRejection<>::Rejection>; async fn from_request(req: Request, state: &State) -> Result { let inner = Extractor::from_request(req, state) .await .map_err(ValidRejection::Inner)?; inner.get_validate().validate()?; Ok(Valid(inner)) } } #[async_trait] impl FromRequestParts for Valid where State: Send + Sync, Extractor: HasValidate + FromRequestParts, Extractor::Validate: Validate, { type Rejection = ValidRejection<>::Rejection>; async fn from_request_parts(parts: &mut Parts, state: &State) -> Result { let inner = Extractor::from_request_parts(parts, state) .await .map_err(ValidRejection::Inner)?; inner.get_validate().validate()?; Ok(Valid(inner)) } } #[async_trait] impl FromRequest for ValidEx where State: Send + Sync, Body: Send + Sync + 'static, Args: Send + Sync + FromRef + for<'a> Arguments<'a, T = >::ValidateArgs>, Extractor: for<'v> HasValidateArgs<'v> + FromRequest, for<'v> >::ValidateArgs: ValidateArgs<'v>, { type Rejection = ValidRejection<>::Rejection>; async fn from_request(req: Request, state: &State) -> Result { let arguments: Args = FromRef::from_ref(state); let inner = Extractor::from_request(req, state) .await .map_err(ValidRejection::Inner)?; inner.get_validate_args().validate_args(arguments.get())?; Ok(ValidEx(inner, arguments)) } } #[async_trait] impl FromRequestParts for ValidEx where State: Send + Sync, Args: Send + Sync + FromRef + for<'a> Arguments<'a, T = >::ValidateArgs>, Extractor: for<'v> HasValidateArgs<'v> + FromRequestParts, for<'v> >::ValidateArgs: ValidateArgs<'v>, { type Rejection = ValidRejection<>::Rejection>; async fn from_request_parts(parts: &mut Parts, state: &State) -> Result { let arguments: Args = FromRef::from_ref(state); let inner = Extractor::from_request_parts(parts, state) .await .map_err(ValidRejection::Inner)?; inner.get_validate_args().validate_args(arguments.get())?; Ok(ValidEx(inner, arguments)) } } #[cfg(test)] pub mod tests { use crate::{Arguments, Valid, ValidEx, ValidRejection}; use reqwest::{RequestBuilder, StatusCode}; use serde::Serialize; use std::error::Error; use std::io; use std::ops::{Deref, DerefMut}; use validator::{Validate, ValidateArgs, ValidationError, ValidationErrors}; /// # Valid test parameter pub trait ValidTestParameter: Serialize + 'static { /// Create a valid parameter fn valid() -> &'static Self; /// Create an error serializable array fn error() -> &'static [(&'static str, &'static str)]; /// Create a invalid parameter fn invalid() -> &'static Self; } /// # Valid Tests /// /// This trait defines three test cases to check /// if an extractor combined with the Valid type works properly. /// /// 1. For a valid request, the server should return `200 OK`. /// 2. For an invalid request according to the extractor, the server should return the error HTTP status code defined by the extractor itself. /// 3. For an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS as the error code. /// pub trait ValidTest { /// The HTTP status code returned when inner extractor failed. const ERROR_STATUS_CODE: StatusCode; /// The HTTP status code returned when the outer extractor fails. /// Use crate::VALIDATION_ERROR_STATUS by default. const INVALID_STATUS_CODE: StatusCode = crate::VALIDATION_ERROR_STATUS; /// If the response body can be serialized into JSON format const JSON_SERIALIZABLE: bool = true; /// Build a valid request, the server should return `200 OK`. fn set_valid_request(builder: RequestBuilder) -> RequestBuilder; /// Build an invalid request according to the extractor, the server should return `Self::ERROR_STATUS_CODE` fn set_error_request(builder: RequestBuilder) -> RequestBuilder; /// Build an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder; } #[cfg(feature = "extra")] pub trait Rejection { const STATUS_CODE: StatusCode; } const TEST: &str = "test"; #[test] fn valid_deref_deref_mut_into_inner() { let mut inner = String::from(TEST); let mut v = Valid(inner.clone()); assert_eq!(&inner, v.deref()); inner.push_str(TEST); v.deref_mut().push_str(TEST); assert_eq!(&inner, v.deref()); assert_eq!(inner, v.into_inner()); } #[test] fn valid_ex_deref_deref_mut_into_inner_arguments() { let mut inner = String::from(TEST); let mut v = ValidEx(inner.clone(), ()); assert_eq!(&inner, v.deref()); inner.push_str(TEST); v.deref_mut().push_str(TEST); assert_eq!(&inner, v.deref()); assert_eq!(inner, v.into_inner()); fn validate(_v: i32, _args: i32) -> Result<(), ValidationError> { Ok(()) } #[derive(Validate)] struct Data { #[validate(custom(function = "validate", arg = "i32"))] v: i32, } struct DataVA { a: i32, } impl<'a> Arguments<'a> for DataVA { type T = Data; fn get(&'a self) -> <>::T as ValidateArgs<'a>>::Args { self.a } } let data = Data { v: 12 }; let args = DataVA { a: 123 }; let ve = ValidEx(data, args); assert_eq!(ve.v, 12); let a = ve.arguments(); assert_eq!(a, 123); } #[test] fn display_error() { // ValidRejection::Valid Display let mut ve = ValidationErrors::new(); ve.add(TEST, ValidationError::new(TEST)); let vr = ValidRejection::::Valid(ve.clone()); assert_eq!(vr.to_string(), ve.to_string()); // ValidRejection::Inner Display let inner = String::from(TEST); let vr = ValidRejection::::Inner(inner.clone()); assert_eq!(inner.to_string(), vr.to_string()); // ValidRejection::Valid Error let mut ve = ValidationErrors::new(); ve.add(TEST, ValidationError::new(TEST)); let vr = ValidRejection::::Valid(ve.clone()); assert!( matches!(vr.source(), Some(source) if source.downcast_ref::().is_some()) ); // ValidRejection::Valid Error let vr = ValidRejection::::Inner(io::Error::new(io::ErrorKind::Other, TEST)); assert!( matches!(vr.source(), Some(source) if source.downcast_ref::().is_some()) ); } }