Skip to content

Commit

Permalink
Merge pull request #72 from yassun7010/feat_support_vec_error_custom_fn
Browse files Browse the repository at this point in the history
feat: support FnOnce<T> -> Result<(), Vec<Error>>.
  • Loading branch information
yassun7010 authored Jun 20, 2024
2 parents e8b0a78 + 51ef5ef commit dd42db0
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 34 deletions.
29 changes: 25 additions & 4 deletions serde_valid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ assert_eq!(
);
```

## Custom Method

## Custom Validation
### Single Error Validation
You can use your custom validation using by `#[validate(custom)]`.

```rust
Expand Down Expand Up @@ -265,8 +265,29 @@ let s = Data {
assert!(s.validate().is_ok());
```

## Multi Fields Validation
### Custom Validation
### Multi Errors Validation
If you want to return multiple errors in the use custom validation method, you can use `#[validate(custom)]` same as single error.

```rust
use serde_valid::Validate;

// 🚀 Just change the return type from `Result<(), Error>` to `Result<(), Vec<Error>>` !!
fn user_validation(_val: &i32) -> Result<(), Vec<serde_valid::validation::Error>> {
Ok(())
}

#[derive(Validate)]
struct Data {
#[validate(custom(user_validation))]
val: i32,
}

let s = Data { val: 1 };

assert!(s.validate().is_ok());
```

### Multi Fields Validation
Now, you can use `#[validate(custom)]` for multi fields validation.

```rust
Expand Down
41 changes: 25 additions & 16 deletions serde_valid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@
//! # }
//! ```
//!
//! ## Custom Method
//!
//! ## Custom Validation
//! ### Single Error Validation
//! You can use your custom validation using by `#[validate(custom)]`.
//!
//! ```rust
Expand Down Expand Up @@ -275,8 +275,29 @@
//! assert!(s.validate().is_ok());
//! ```
//!
//! ## Multi Fields Validation
//! ### Custom Validation
//! ### Multi Errors Validation
//! If you want to return multiple errors in the use custom validation method, you can use `#[validate(custom)]` same as single error.
//!
//! ```rust
//! use serde_valid::Validate;
//!
//! // 🚀 Just change the return type from `Result<(), Error>` to `Result<(), Vec<Error>>` !!
//! fn user_validation(_val: &i32) -> Result<(), Vec<serde_valid::validation::Error>> {
//! Ok(())
//! }
//!
//! #[derive(Validate)]
//! struct Data {
//! #[validate(custom(user_validation))]
//! val: i32,
//! }
//!
//! let s = Data { val: 1 };
//!
//! assert!(s.validate().is_ok());
//! ```
//!
//! ### Multi Fields Validation
//! Now, you can use `#[validate(custom)]` for multi fields validation.
//!
//! ```rust
Expand Down Expand Up @@ -656,18 +677,6 @@ where

pub use serde_valid_derive::Validate;

#[doc(hidden)]
pub mod helpers {
/// This function is used to avoid [rustc(E0282)](https://doc.rust-lang.org/error_codes/E0282.html) error in `#[validate(custom)]` validator on the struct.
#[inline]
pub fn wrap_closure_validation<T>(
data: &T,
f: impl FnOnce(&T) -> Result<(), crate::validation::Error>,
) -> Result<(), crate::validation::Error> {
f(data)
}
}

#[cfg(test)]
pub mod tests {
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
Expand Down
1 change: 1 addition & 0 deletions serde_valid/src/validation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod array;
mod composited;
pub mod custom;
pub mod error;
mod generic;
mod numeric;
Expand Down
81 changes: 81 additions & 0 deletions serde_valid/src/validation/custom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/// This function is used to avoid [rustc(E0282)](https://doc.rust-lang.org/error_codes/E0282.html) error in `#[validate(custom)]` validator on the struct.
#[inline]
pub fn wrap_closure_validation<T: ?Sized, M: IntoVecErrors>(
data: &T,
f: impl FnOnce(&T) -> Result<(), M>,
) -> Result<(), Vec<crate::validation::Error>> {
f(data).map_err(|e| e.into_vec_errors())
}

#[inline]
pub fn wrap_into_vec_errors<M: IntoVecErrors>(
result: Result<(), M>,
) -> Result<(), Vec<crate::validation::Error>> {
result.map_err(|e| e.into_vec_errors())
}

pub trait IntoVecErrors {
fn into_vec_errors(self) -> Vec<crate::validation::Error>;
}

impl IntoVecErrors for Vec<crate::validation::Error> {
fn into_vec_errors(self) -> Vec<crate::validation::Error> {
self
}
}

impl IntoVecErrors for crate::validation::Error {
fn into_vec_errors(self) -> Vec<crate::validation::Error> {
vec![self]
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_custom_fn_single_error() {
fn single_error(data: &i32) -> Result<(), crate::validation::Error> {
if *data > 0 {
Ok(())
} else {
Err(crate::validation::Error::Custom(
"Value must be greater than 0".to_string(),
))
}
}

assert!(wrap_closure_validation(&1i32, single_error).is_ok());
}

#[test]
fn test_custom_fn_multiple_errors() {
fn multiple_errors(data: &i32) -> Result<(), Vec<crate::validation::Error>> {
let mut errors = Vec::new();
if *data > 0 {
return Ok(());
} else {
errors.push(crate::validation::Error::Custom(
"Value must be greater than 0".to_string(),
));
}

if *data < 10 {
return Ok(());
} else {
errors.push(crate::validation::Error::Custom(
"Value must be less than 10".to_string(),
));
}

if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}

assert!(wrap_closure_validation(&1i32, multiple_errors).is_ok());
}
}
89 changes: 89 additions & 0 deletions serde_valid/tests/custom_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,92 @@ fn unnamed_struct_custom_closure_is_err() {
assert_eq!(s.0, 5);
assert!(s.validate().is_err());
}

#[test]
fn named_struct_custom_vec_errors_is_ok() {
fn validation(_val: &TestStruct) -> Result<(), Vec<serde_valid::validation::Error>> {
Ok(())
}

#[derive(Validate)]
#[validate(custom(validation))]
struct TestStruct {
val: i32,
}

let s = TestStruct { val: 5 };
assert_eq!(s.val, 5);
assert!(s.validate().is_ok());
}

#[test]
fn named_struct_custom_vec_errors_is_err() {
fn validation(_val: &TestStruct) -> Result<(), Vec<serde_valid::validation::Error>> {
Err(vec![
serde_valid::validation::Error::Custom("Error 1".to_owned()),
serde_valid::validation::Error::Custom("Error 2".to_owned()),
])
}

#[derive(Validate)]
#[validate(custom(validation))]
struct TestStruct {
val: i32,
}

let s = TestStruct { val: 5 };

assert_eq!(s.val, 5);
assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": ["Error 1", "Error 2"],
"properties": {}
})
.to_string()
);
}

#[test]
fn named_struct_custom_closure_vec_errors_is_ok() {
fn sample_struct_validation(_val: i32) -> Result<(), Vec<serde_valid::validation::Error>> {
Ok(())
}

#[derive(Validate)]
#[validate(custom(|s| sample_struct_validation(s.val)))]
struct TestStruct {
val: i32,
}

let s = TestStruct { val: 5 };
assert_eq!(s.val, 5);
assert!(s.validate().is_ok());
}

#[test]
fn named_struct_custom_closure_vec_errors_is_err() {
fn sample_struct_validation(_val: i32) -> Result<(), Vec<serde_valid::validation::Error>> {
Err(vec![
serde_valid::validation::Error::Custom("Error 1".to_owned()),
serde_valid::validation::Error::Custom("Error 2".to_owned()),
])
}

#[derive(Validate)]
#[validate(custom(|s| sample_struct_validation(s.val)))]
struct TestStruct {
val: i32,
}

let s = TestStruct { val: 5 };
assert_eq!(s.val, 5);
assert_eq!(
s.validate().unwrap_err().to_string(),
json!({
"errors": ["Error 1", "Error 2"],
"properties": {}
})
.to_string()
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ pub fn extract_generic_custom_validator(
}?;

Ok(quote!(
if let Err(__error) = #custom_fn_name(#field_ident) {
if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#custom_fn_name(#field_ident)) {
#errors
.entry(#rename)
.or_default()
.push(__error);
.extend(__errors);
};
))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ fn extract_struct_custom_from_meta_path(meta_path: &syn::Path) -> Result<Validat
let rule_fn_name = &meta_path;

Ok(quote!(
if let Err(__error) = #rule_fn_name(self) {
__rule_vec_errors.push(__error);
if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#rule_fn_name(self)) {
__rule_vec_errors.extend(__errors);
};
))
}
Expand All @@ -63,8 +63,8 @@ fn extract_struct_custom_from_meta_list(
meta_list: &syn::MetaList,
) -> Result<Validator, crate::Errors> {
Ok(quote!(
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #meta_list) {
__rule_vec_errors.push(__error);
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #meta_list) {
__rule_vec_errors.extend(__errors);
};
))
}
Expand All @@ -73,8 +73,8 @@ fn extract_struct_custom_from_closure(
closure: &syn::ExprClosure,
) -> Result<Validator, crate::Errors> {
Ok(quote!(
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #closure) {
__rule_vec_errors.push(__error);
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #closure) {
__rule_vec_errors.extend(__errors);
};
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ fn extract_variant_custom_from_meta_path(
let rule_fn_name = &meta_path;

Ok(quote!(
if let Err(__error) = #rule_fn_name(self) {
__rule_vec_errors.push(__error);
if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#rule_fn_name(self)) {
__rule_vec_errors.extend(__errors);
};
))
}
Expand All @@ -65,8 +65,8 @@ fn extract_variant_custom_from_meta_list(
meta_list: &syn::MetaList,
) -> Result<Validator, crate::Errors> {
Ok(quote!(
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #meta_list) {
__rule_vec_errors.push(__error);
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #meta_list) {
__rule_vec_errors.extend(__errors);
};
))
}
Expand All @@ -75,8 +75,8 @@ fn extract_variant_custom_from_closure(
closure: &syn::ExprClosure,
) -> Result<Validator, crate::Errors> {
Ok(quote!(
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #closure) {
__rule_vec_errors.push(__error);
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #closure) {
__rule_vec_errors.extend(__errors);
};
))
}

0 comments on commit dd42db0

Please sign in to comment.