refactor Rejections

This commit is contained in:
gengteng
2023-10-09 19:53:19 +08:00
parent cc4cd9f70c
commit a3ca376769
4 changed files with 81 additions and 117 deletions

View File

@@ -48,13 +48,17 @@ version = "0.8.0"
default-features = false default-features = false
optional = true optional = true
[dependencies.serde]
version = "1.0.188"
optional = true
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.72" anyhow = "1.0.72"
axum = { version = "0.6.20", features = ["macros"] } axum = { version = "0.6.20", features = ["macros"] }
tokio = { version = "1.29.1", features = ["full"] } tokio = { version = "1.29.1", features = ["full"] }
hyper = { version = "0.14.27", features = ["full"] } hyper = { version = "0.14.27", features = ["full"] }
reqwest = { version = "0.11.18", features = ["json", "multipart"] } reqwest = { version = "0.11.18", features = ["json", "multipart"] }
serde = { version = "1.0.181", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] }
validator = { version = "0.16.0", features = ["derive"] } validator = { version = "0.16.0", features = ["derive"] }
serde_json = "1.0.104" serde_json = "1.0.104"
serde_yaml = "0.9.25" serde_yaml = "0.9.25"
@@ -75,7 +79,7 @@ typed_header = ["axum/headers"]
typed_multipart = ["axum_typed_multipart"] typed_multipart = ["axum_typed_multipart"]
msgpack = ["axum-msgpack"] msgpack = ["axum-msgpack"]
yaml = ["axum-yaml"] yaml = ["axum-yaml"]
into_json = ["json"] into_json = ["json", "serde"]
422 = [] 422 = []
extra = ["axum-extra"] extra = ["axum-extra"]
extra_typed_path = ["axum-extra/typed-routing"] extra_typed_path = ["axum-extra/typed-routing"]

View File

@@ -8,14 +8,12 @@
#[cfg(test)] #[cfg(test)]
pub mod test; pub mod test;
use crate::{HasValidate, VALIDATION_ERROR_STATUS}; use crate::{HasValidate, ValidationRejection};
use axum::async_trait; use axum::async_trait;
use axum::extract::{FromRef, FromRequest, FromRequestParts}; use axum::extract::{FromRef, FromRequest, FromRequestParts};
use axum::http::request::Parts; use axum::http::request::Parts;
use axum::http::Request; use axum::http::Request;
use axum::response::{IntoResponse, Response}; use garde::{Report, Validate};
use garde::Validate;
use std::error::Error;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@@ -54,61 +52,13 @@ impl<E> Garde<E> {
} }
} }
fn response_builder(ve: garde::Report) -> 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()
}
}
/// `GardeRejection` is returned when the `Garde` extractor fails. /// `GardeRejection` is returned when the `Garde` extractor fails.
/// ///
/// This enumeration captures two types of errors that can occur when using `Garde`: errors related to the validation pub type GardeRejection<E> = ValidationRejection<Report, E>;
/// logic itself (encapsulated in `Garde`), and errors that may arise within the inner extractor (represented by `Inner`).
///
#[derive(Debug)]
pub enum GardeRejection<E> {
/// `Report` variant captures errors related to the validation logic. It contains `garde::Report`
/// which is a collection of validation failures for each field.
Report(garde::Report),
/// `Inner` variant represents potential errors that might occur within the inner extractor.
Inner(E),
}
impl<E: Display> Display for GardeRejection<E> { impl<E> From<Report> for GardeRejection<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn from(value: Report) -> Self {
match self { Self::Valid(value)
GardeRejection::Report(errors) => write!(f, "{errors}"),
GardeRejection::Inner(error) => write!(f, "{error}"),
}
}
}
impl<E: Error + 'static> Error for GardeRejection<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
GardeRejection::Report(ve) => Some(ve),
GardeRejection::Inner(e) => Some(e),
}
}
}
impl<E> From<garde::Report> for GardeRejection<E> {
fn from(value: garde::Report) -> Self {
Self::Report(value)
}
}
impl<E: IntoResponse> IntoResponse for GardeRejection<E> {
fn into_response(self) -> Response {
match self {
GardeRejection::Report(ve) => response_builder(ve),
GardeRejection::Inner(e) => e.into_response(),
}
} }
} }
@@ -158,6 +108,7 @@ where
mod tests { mod tests {
use super::*; use super::*;
use garde::{Path, Report}; use garde::{Path, Report};
use std::error::Error;
use std::io; use std::io;
const GARDE: &str = "garde"; const GARDE: &str = "garde";
@@ -176,25 +127,25 @@ mod tests {
#[test] #[test]
fn display_error() { fn display_error() {
// ValidRejection::Valid Display // GardeRejection::Valid Display
let mut report = Report::new(); let mut report = Report::new();
report.append(Path::empty(), garde::Error::new(GARDE)); report.append(Path::empty(), garde::Error::new(GARDE));
let s = report.to_string(); let s = report.to_string();
let vr = GardeRejection::<String>::Report(report); let vr = GardeRejection::<String>::Valid(report);
assert_eq!(vr.to_string(), s); assert_eq!(vr.to_string(), s);
// ValidRejection::Inner Display // GardeRejection::Inner Display
let inner = String::from(GARDE); let inner = String::from(GARDE);
let vr = GardeRejection::<String>::Inner(inner.clone()); let vr = GardeRejection::<String>::Inner(inner.clone());
assert_eq!(inner.to_string(), vr.to_string()); assert_eq!(inner.to_string(), vr.to_string());
// ValidRejection::Valid Error // GardeRejection::Valid Error
let mut report = Report::new(); let mut report = Report::new();
report.append(Path::empty(), garde::Error::new(GARDE)); report.append(Path::empty(), garde::Error::new(GARDE));
let vr = GardeRejection::<io::Error>::Report(report); let vr = GardeRejection::<io::Error>::Valid(report);
assert!(matches!(vr.source(), Some(source) if source.downcast_ref::<Report>().is_some())); assert!(matches!(vr.source(), Some(source) if source.downcast_ref::<Report>().is_some()));
// ValidRejection::Valid Error // GardeRejection::Valid Error
let vr = GardeRejection::<io::Error>::Inner(io::Error::new(io::ErrorKind::Other, GARDE)); let vr = GardeRejection::<io::Error>::Inner(io::Error::new(io::ErrorKind::Other, GARDE));
assert!( assert!(
matches!(vr.source(), Some(source) if source.downcast_ref::<io::Error>().is_some()) matches!(vr.source(), Some(source) if source.downcast_ref::<io::Error>().is_some())

View File

@@ -24,6 +24,9 @@ pub mod validator;
pub mod yaml; pub mod yaml;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use std::error::Error;
use std::fmt::Display;
/// Http status code returned when there are validation errors. /// Http status code returned when there are validation errors.
#[cfg(feature = "422")] #[cfg(feature = "422")]
@@ -49,6 +52,61 @@ pub use crate::validator::{Arguments, HasValidateArgs, Valid, ValidEx, ValidReje
#[cfg(feature = "garde")] #[cfg(feature = "garde")]
pub use crate::garde::{Garde, GardeRejection}; pub use crate::garde::{Garde, GardeRejection};
/// `ValidationRejection` is returned when the validation extractor fails.
///
/// This enumeration captures two types of errors that can occur when using `Valid`: errors related to the validation
/// extractor itself , and errors that may arise within the inner extractor (represented by `Inner`).
///
#[derive(Debug)]
pub enum ValidationRejection<V, E> {
/// `Valid` variant captures errors related to the validation logic.
Valid(V),
/// `Inner` variant represents potential errors that might occur within the inner extractor.
Inner(E),
}
impl<V: Display, E: Display> Display for ValidationRejection<V, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidationRejection::Valid(errors) => write!(f, "{errors}"),
ValidationRejection::Inner(error) => write!(f, "{error}"),
}
}
}
impl<V: Error + 'static, E: Error + 'static> Error for ValidationRejection<V, E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ValidationRejection::Valid(ve) => Some(ve),
ValidationRejection::Inner(e) => Some(e),
}
}
}
#[cfg(feature = "into_json")]
impl<V: serde::Serialize, E: IntoResponse> IntoResponse for ValidationRejection<V, E> {
fn into_response(self) -> Response {
match self {
ValidationRejection::Valid(v) => {
(VALIDATION_ERROR_STATUS, axum::Json(v)).into_response()
}
ValidationRejection::Inner(e) => e.into_response(),
}
}
}
#[cfg(not(feature = "into_json"))]
impl<V: Display, E: IntoResponse> IntoResponse for ValidationRejection<V, E> {
fn into_response(self) -> Response {
match self {
ValidationRejection::Valid(v) => {
(VALIDATION_ERROR_STATUS, v.to_string()).into_response()
}
ValidationRejection::Inner(e) => e.into_response(),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use reqwest::{RequestBuilder, StatusCode}; use reqwest::{RequestBuilder, StatusCode};

View File

@@ -8,13 +8,11 @@
#[cfg(test)] #[cfg(test)]
pub mod test; pub mod test;
use crate::{HasValidate, VALIDATION_ERROR_STATUS}; use crate::{HasValidate, ValidationRejection};
use axum::async_trait; use axum::async_trait;
use axum::extract::{FromRef, FromRequest, FromRequestParts}; use axum::extract::{FromRef, FromRequest, FromRequestParts};
use axum::http::request::Parts; use axum::http::request::Parts;
use axum::http::Request; use axum::http::Request;
use axum::response::{IntoResponse, Response};
use std::error::Error;
use std::fmt::Display; use std::fmt::Display;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use validator::{Validate, ValidateArgs, ValidationErrors}; use validator::{Validate, ValidateArgs, ValidationErrors};
@@ -118,17 +116,6 @@ impl<E, A> ValidEx<E, A> {
} }
} }
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`. /// `Arguments` provides the validation arguments for the data type `T`.
/// ///
/// This trait has an associated type `T` which represents the data type to /// This trait has an associated type `T` which represents the data type to
@@ -146,37 +133,9 @@ pub trait Arguments<'a> {
fn get(&'a self) -> <<Self as Arguments<'a>>::T as ValidateArgs<'a>>::Args; fn get(&'a self) -> <<Self as Arguments<'a>>::T as ValidateArgs<'a>>::Args;
} }
/// `ValidRejection` is returned when the `Valid` extractor fails. /// `ValidRejection` is returned when the `Valid` or `ValidEx` extractor fails.
/// ///
/// This enumeration captures two types of errors that can occur when using `Valid`: errors related to the validation pub type ValidRejection<E> = ValidationRejection<ValidationErrors, E>;
/// logic itself (encapsulated in `Valid`), and errors that may arise within the inner extractor (represented by `Inner`).
///
#[derive(Debug)]
pub enum ValidRejection<E> {
/// `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<E: Display> Display for ValidRejection<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidRejection::Valid(errors) => write!(f, "{errors}"),
ValidRejection::Inner(error) => write!(f, "{error}"),
}
}
}
impl<E: Error + 'static> Error for ValidRejection<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ValidRejection::Valid(ve) => Some(ve),
ValidRejection::Inner(e) => Some(e),
}
}
}
impl<E> From<ValidationErrors> for ValidRejection<E> { impl<E> From<ValidationErrors> for ValidRejection<E> {
fn from(value: ValidationErrors) -> Self { fn from(value: ValidationErrors) -> Self {
@@ -184,15 +143,6 @@ impl<E> From<ValidationErrors> for ValidRejection<E> {
} }
} }
impl<E: IntoResponse> IntoResponse for ValidRejection<E> {
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 using arguments. /// 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`. /// Extractor types `T` that implement this trait can be used with `ValidEx`.
@@ -292,6 +242,7 @@ where
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use std::error::Error;
use std::fmt::Formatter; use std::fmt::Formatter;
use std::io; use std::io;
use validator::ValidationError; use validator::ValidationError;