//! # Validify support //! //! ## Feature //! //! Enable the `validify` feature to use `Validated`, `Modified`, `Validified` and `ValidifiedByRef`. //! #[cfg(test)] pub mod test; use crate::{HasValidate, ValidationRejection}; use axum::async_trait; use axum::extract::{FromRequest, FromRequestParts}; use axum::http::request::Parts; use axum::http::Request; use axum::response::{IntoResponse, Response}; use std::fmt::{Display, Formatter}; use std::ops::{Deref, DerefMut}; use validify::{Modify, Validate, ValidationErrors, Validify}; /// # `Validated` data extractor /// /// `Validated` provides simple data validation based on `validify`. /// /// It only does validation, usage is similar to `Valid`. /// #[derive(Debug, Clone, Copy, Default)] pub struct Validated(pub E); impl Deref for Validated { type Target = E; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Validated { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Display for Validated { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl Validated { /// Consumes the `Validated` 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 } } /// # `Modified` data extractor / response /// /// ## Extractor /// /// `Modified` uses `validify`'s modification capabilities to alter data, without validation. /// /// Operations like trimming and case modification can be done based on `modify` attributes. /// /// ## Response /// /// `Modified` also implements the `IntoResponse` trait. When its inner `IntoResponse` type also implements the `HasModify` trait: /// /// `Modified` will call `validify`'s modify method to alter the inner data. /// Then call the inner type's own `into_response` method to convert it into a HTTP response. /// /// This allows applying modifications during response conversion by leveraging validify. #[derive(Debug, Clone, Copy, Default)] pub struct Modified(pub E); impl Deref for Modified { type Target = E; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Modified { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Display for Modified { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl Modified { /// Consumes the `Modified` and returns the modified data within. /// /// This returns the `E` type which represents the data that has been /// modified. pub fn into_inner(self) -> E { self.0 } } impl IntoResponse for Modified { fn into_response(mut self) -> Response { self.get_modify().modify(); self.0.into_response() } } /// # `Validified` data extractor /// /// `Validified` provides construction, modification and validation abilities based on `validify`. /// /// It requires a serde-based inner extractor. /// /// And can treat missing fields as validation errors. /// #[derive(Debug, Clone, Copy, Default)] pub struct Validified(pub E); impl Deref for Validified { type Target = E; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Validified { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Display for Validified { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl Validified { /// Consumes the `Validified` and returns the modified and 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 } } /// # `ValidifiedByRef` data extractor /// /// `ValidifiedByRef` is similar to `Validified`, but operates via reference. /// /// Suitable for inner extractors not based on `serde`. /// #[derive(Debug, Clone, Copy, Default)] pub struct ValidifiedByRef(pub E); impl Deref for ValidifiedByRef { type Target = E; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for ValidifiedByRef { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Display for ValidifiedByRef { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl ValidifiedByRef { /// Consumes the `ValidifiedByRef` and returns the modified and 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 } } /// `ValidifyRejection` is returned when the `Validated` / `Modified` / `Validified` / `ValidifiedByRef` extractor fails. /// pub type ValidifyRejection = ValidationRejection; impl From for ValidifyRejection { fn from(value: ValidationErrors) -> Self { Self::Valid(value) } } /// Trait for types that can supply a reference that can be modified. /// /// Extractor types `T` that implement this trait can be used with `Modified`. /// pub trait HasModify { /// Inner type that can be modified type Modify: Modify; /// Get the inner value fn get_modify(&mut self) -> &mut Self::Modify; } /// Extractor to extract payload for constructing data pub trait PayloadExtractor { /// Type of payload for constructing data type Payload; /// Get payload from the extractor fn get_payload(self) -> Self::Payload; } /// Trait for extractors whose inner data type that can be constructed using some payload, /// then modified and validated using `validify`. /// /// Extractor types `T` that implement this trait can be used with `Validified`. /// pub trait HasValidify: Sized { /// Inner type that can be modified and validated using `validify`. type Validify: Validify; /// Extracts payload from the request, /// which will be used to construct the `Self::Validify` type /// and perform modification and validation on it. type PayloadExtractor: PayloadExtractor::Payload>; /// Re-packages the validified data back into the inner Extractor type. fn from_validified(v: Self::Validify) -> Self; } #[async_trait] impl FromRequest for Validated where State: Send + Sync, Body: Send + Sync + 'static, Extractor: HasValidate + FromRequest, Extractor::Validate: validify::Validate, { type Rejection = ValidifyRejection<>::Rejection>; async fn from_request(req: Request, state: &State) -> Result { let inner = Extractor::from_request(req, state) .await .map_err(ValidifyRejection::Inner)?; inner.get_validate().validate()?; Ok(Validated(inner)) } } #[async_trait] impl FromRequestParts for Validated where State: Send + Sync, Extractor: HasValidate + FromRequestParts, Extractor::Validate: Validate, { type Rejection = ValidifyRejection<>::Rejection>; async fn from_request_parts(parts: &mut Parts, state: &State) -> Result { let inner = Extractor::from_request_parts(parts, state) .await .map_err(ValidifyRejection::Inner)?; inner.get_validate().validate()?; Ok(Validated(inner)) } } #[async_trait] impl FromRequest for Modified where State: Send + Sync, Body: Send + Sync + 'static, Extractor: HasModify + FromRequest, { type Rejection = >::Rejection; async fn from_request(req: Request, state: &State) -> Result { let mut inner = Extractor::from_request(req, state).await?; inner.get_modify().modify(); Ok(Modified(inner)) } } #[async_trait] impl FromRequestParts for Modified where State: Send + Sync, Extractor: HasModify + FromRequestParts, { type Rejection = >::Rejection; async fn from_request_parts(parts: &mut Parts, state: &State) -> Result { let mut inner = Extractor::from_request_parts(parts, state).await?; inner.get_modify().modify(); Ok(Modified(inner)) } } #[async_trait] impl FromRequest for Validified where State: Send + Sync, Body: Send + Sync + 'static, Extractor: HasValidify, Extractor::Validify: Validify, Extractor::PayloadExtractor: FromRequest, { type Rejection = ValidifyRejection<>::Rejection>; async fn from_request(req: Request, state: &State) -> Result { let payload = Extractor::PayloadExtractor::from_request(req, state) .await .map_err(ValidifyRejection::Inner)?; Ok(Validified(Extractor::from_validified( Extractor::Validify::validify(payload.get_payload())?, ))) } } #[async_trait] impl FromRequestParts for Validified where State: Send + Sync, Extractor: HasValidify, Extractor::Validify: Validify, Extractor::PayloadExtractor: FromRequestParts, { type Rejection = ValidifyRejection<>::Rejection>; async fn from_request_parts(parts: &mut Parts, state: &State) -> Result { let payload = Extractor::PayloadExtractor::from_request_parts(parts, state) .await .map_err(ValidifyRejection::Inner)?; Ok(Validified(Extractor::from_validified( Extractor::Validify::validify(payload.get_payload())?, ))) } } #[async_trait] impl FromRequest for ValidifiedByRef where State: Send + Sync, Body: Send + Sync + 'static, Extractor: HasValidate + HasModify + FromRequest, Extractor::Validate: Validate, { type Rejection = ValidifyRejection<>::Rejection>; async fn from_request(req: Request, state: &State) -> Result { let mut inner = Extractor::from_request(req, state) .await .map_err(ValidifyRejection::Inner)?; inner.get_modify().modify(); inner.get_validate().validate()?; Ok(ValidifiedByRef(inner)) } } #[async_trait] impl FromRequestParts for ValidifiedByRef where State: Send + Sync, Extractor: HasValidate + HasModify + FromRequestParts, Extractor::Validate: Validate, { type Rejection = ValidifyRejection<>::Rejection>; async fn from_request_parts(parts: &mut Parts, state: &State) -> Result { let mut inner = Extractor::from_request_parts(parts, state) .await .map_err(ValidifyRejection::Inner)?; inner.get_modify().modify(); inner.get_validate().validate()?; Ok(ValidifiedByRef(inner)) } } #[cfg(test)] mod tests { use super::*; use axum::Json; use serde::Serialize; use std::error::Error; use std::io; const VALIDIFY: &str = "validify"; #[test] fn validify_deref_deref_mut_into_inner() { let mut inner = String::from(VALIDIFY); let mut v = Validated(inner.clone()); assert_eq!(&inner, v.deref()); inner.push_str(VALIDIFY); v.deref_mut().push_str(VALIDIFY); assert_eq!(&inner, v.deref()); println!("{}", v); assert_eq!(inner, v.into_inner()); let mut inner = String::from(VALIDIFY); let mut v = Modified(inner.clone()); assert_eq!(&inner, v.deref()); inner.push_str(VALIDIFY); v.deref_mut().push_str(VALIDIFY); assert_eq!(&inner, v.deref()); println!("{}", v); assert_eq!(inner, v.into_inner()); let mut inner = String::from(VALIDIFY); let mut v = Validified(inner.clone()); assert_eq!(&inner, v.deref()); inner.push_str(VALIDIFY); v.deref_mut().push_str(VALIDIFY); assert_eq!(&inner, v.deref()); println!("{}", v); assert_eq!(inner, v.into_inner()); let mut inner = String::from(VALIDIFY); let mut v = ValidifiedByRef(inner.clone()); assert_eq!(&inner, v.deref()); inner.push_str(VALIDIFY); v.deref_mut().push_str(VALIDIFY); assert_eq!(&inner, v.deref()); println!("{}", v); assert_eq!(inner, v.into_inner()); } #[test] fn display_error() { // ValidifyRejection::Valid Display let mut report = ValidationErrors::new(); report.add(validify::ValidationError::new_schema(VALIDIFY)); let s = report.to_string(); let vr = ValidifyRejection::::Valid(report); assert_eq!(vr.to_string(), s); // ValidifyRejection::Inner Display let inner = String::from(VALIDIFY); let vr = ValidifyRejection::::Inner(inner.clone()); assert_eq!(inner.to_string(), vr.to_string()); // ValidifyRejection::Valid Error let mut report = ValidationErrors::new(); report.add(validify::ValidationError::new_schema(VALIDIFY)); let vr = ValidifyRejection::::Valid(report); assert!( matches!(vr.source(), Some(source) if source.downcast_ref::().is_some()) ); // ValidifyRejection::Valid Error let vr = ValidifyRejection::::Inner(io::Error::new(io::ErrorKind::Other, VALIDIFY)); assert!( matches!(vr.source(), Some(source) if source.downcast_ref::().is_some()) ); } #[test] fn modified_into_response() { #[derive(Validify, Serialize)] struct Data { #[modify(trim)] v: String, } println!( "{:?}", Modified(Json(Data { v: "a ".into() })).into_response() ); } }