Skip to content

Commit dd42db0

Browse files
authored
Merge pull request #72 from yassun7010/feat_support_vec_error_custom_fn
feat: support FnOnce<T> -> Result<(), Vec<Error>>.
2 parents e8b0a78 + 51ef5ef commit dd42db0

File tree

8 files changed

+235
-34
lines changed

8 files changed

+235
-34
lines changed

serde_valid/README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ assert_eq!(
200200
);
201201
```
202202

203-
## Custom Method
204-
203+
## Custom Validation
204+
### Single Error Validation
205205
You can use your custom validation using by `#[validate(custom)]`.
206206

207207
```rust
@@ -265,8 +265,29 @@ let s = Data {
265265
assert!(s.validate().is_ok());
266266
```
267267

268-
## Multi Fields Validation
269-
### Custom Validation
268+
### Multi Errors Validation
269+
If you want to return multiple errors in the use custom validation method, you can use `#[validate(custom)]` same as single error.
270+
271+
```rust
272+
use serde_valid::Validate;
273+
274+
// 🚀 Just change the return type from `Result<(), Error>` to `Result<(), Vec<Error>>` !!
275+
fn user_validation(_val: &i32) -> Result<(), Vec<serde_valid::validation::Error>> {
276+
Ok(())
277+
}
278+
279+
#[derive(Validate)]
280+
struct Data {
281+
#[validate(custom(user_validation))]
282+
val: i32,
283+
}
284+
285+
let s = Data { val: 1 };
286+
287+
assert!(s.validate().is_ok());
288+
```
289+
290+
### Multi Fields Validation
270291
Now, you can use `#[validate(custom)]` for multi fields validation.
271292

272293
```rust

serde_valid/src/lib.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@
210210
//! # }
211211
//! ```
212212
//!
213-
//! ## Custom Method
214-
//!
213+
//! ## Custom Validation
214+
//! ### Single Error Validation
215215
//! You can use your custom validation using by `#[validate(custom)]`.
216216
//!
217217
//! ```rust
@@ -275,8 +275,29 @@
275275
//! assert!(s.validate().is_ok());
276276
//! ```
277277
//!
278-
//! ## Multi Fields Validation
279-
//! ### Custom Validation
278+
//! ### Multi Errors Validation
279+
//! If you want to return multiple errors in the use custom validation method, you can use `#[validate(custom)]` same as single error.
280+
//!
281+
//! ```rust
282+
//! use serde_valid::Validate;
283+
//!
284+
//! // 🚀 Just change the return type from `Result<(), Error>` to `Result<(), Vec<Error>>` !!
285+
//! fn user_validation(_val: &i32) -> Result<(), Vec<serde_valid::validation::Error>> {
286+
//! Ok(())
287+
//! }
288+
//!
289+
//! #[derive(Validate)]
290+
//! struct Data {
291+
//! #[validate(custom(user_validation))]
292+
//! val: i32,
293+
//! }
294+
//!
295+
//! let s = Data { val: 1 };
296+
//!
297+
//! assert!(s.validate().is_ok());
298+
//! ```
299+
//!
300+
//! ### Multi Fields Validation
280301
//! Now, you can use `#[validate(custom)]` for multi fields validation.
281302
//!
282303
//! ```rust
@@ -656,18 +677,6 @@ where
656677

657678
pub use serde_valid_derive::Validate;
658679

659-
#[doc(hidden)]
660-
pub mod helpers {
661-
/// 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.
662-
#[inline]
663-
pub fn wrap_closure_validation<T>(
664-
data: &T,
665-
f: impl FnOnce(&T) -> Result<(), crate::validation::Error>,
666-
) -> Result<(), crate::validation::Error> {
667-
f(data)
668-
}
669-
}
670-
671680
#[cfg(test)]
672681
pub mod tests {
673682
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

serde_valid/src/validation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod array;
22
mod composited;
3+
pub mod custom;
34
pub mod error;
45
mod generic;
56
mod numeric;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/// 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.
2+
#[inline]
3+
pub fn wrap_closure_validation<T: ?Sized, M: IntoVecErrors>(
4+
data: &T,
5+
f: impl FnOnce(&T) -> Result<(), M>,
6+
) -> Result<(), Vec<crate::validation::Error>> {
7+
f(data).map_err(|e| e.into_vec_errors())
8+
}
9+
10+
#[inline]
11+
pub fn wrap_into_vec_errors<M: IntoVecErrors>(
12+
result: Result<(), M>,
13+
) -> Result<(), Vec<crate::validation::Error>> {
14+
result.map_err(|e| e.into_vec_errors())
15+
}
16+
17+
pub trait IntoVecErrors {
18+
fn into_vec_errors(self) -> Vec<crate::validation::Error>;
19+
}
20+
21+
impl IntoVecErrors for Vec<crate::validation::Error> {
22+
fn into_vec_errors(self) -> Vec<crate::validation::Error> {
23+
self
24+
}
25+
}
26+
27+
impl IntoVecErrors for crate::validation::Error {
28+
fn into_vec_errors(self) -> Vec<crate::validation::Error> {
29+
vec![self]
30+
}
31+
}
32+
33+
#[cfg(test)]
34+
mod test {
35+
use super::*;
36+
37+
#[test]
38+
fn test_custom_fn_single_error() {
39+
fn single_error(data: &i32) -> Result<(), crate::validation::Error> {
40+
if *data > 0 {
41+
Ok(())
42+
} else {
43+
Err(crate::validation::Error::Custom(
44+
"Value must be greater than 0".to_string(),
45+
))
46+
}
47+
}
48+
49+
assert!(wrap_closure_validation(&1i32, single_error).is_ok());
50+
}
51+
52+
#[test]
53+
fn test_custom_fn_multiple_errors() {
54+
fn multiple_errors(data: &i32) -> Result<(), Vec<crate::validation::Error>> {
55+
let mut errors = Vec::new();
56+
if *data > 0 {
57+
return Ok(());
58+
} else {
59+
errors.push(crate::validation::Error::Custom(
60+
"Value must be greater than 0".to_string(),
61+
));
62+
}
63+
64+
if *data < 10 {
65+
return Ok(());
66+
} else {
67+
errors.push(crate::validation::Error::Custom(
68+
"Value must be less than 10".to_string(),
69+
));
70+
}
71+
72+
if errors.is_empty() {
73+
Ok(())
74+
} else {
75+
Err(errors)
76+
}
77+
}
78+
79+
assert!(wrap_closure_validation(&1i32, multiple_errors).is_ok());
80+
}
81+
}

serde_valid/tests/custom_test.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,92 @@ fn unnamed_struct_custom_closure_is_err() {
192192
assert_eq!(s.0, 5);
193193
assert!(s.validate().is_err());
194194
}
195+
196+
#[test]
197+
fn named_struct_custom_vec_errors_is_ok() {
198+
fn validation(_val: &TestStruct) -> Result<(), Vec<serde_valid::validation::Error>> {
199+
Ok(())
200+
}
201+
202+
#[derive(Validate)]
203+
#[validate(custom(validation))]
204+
struct TestStruct {
205+
val: i32,
206+
}
207+
208+
let s = TestStruct { val: 5 };
209+
assert_eq!(s.val, 5);
210+
assert!(s.validate().is_ok());
211+
}
212+
213+
#[test]
214+
fn named_struct_custom_vec_errors_is_err() {
215+
fn validation(_val: &TestStruct) -> Result<(), Vec<serde_valid::validation::Error>> {
216+
Err(vec![
217+
serde_valid::validation::Error::Custom("Error 1".to_owned()),
218+
serde_valid::validation::Error::Custom("Error 2".to_owned()),
219+
])
220+
}
221+
222+
#[derive(Validate)]
223+
#[validate(custom(validation))]
224+
struct TestStruct {
225+
val: i32,
226+
}
227+
228+
let s = TestStruct { val: 5 };
229+
230+
assert_eq!(s.val, 5);
231+
assert_eq!(
232+
s.validate().unwrap_err().to_string(),
233+
json!({
234+
"errors": ["Error 1", "Error 2"],
235+
"properties": {}
236+
})
237+
.to_string()
238+
);
239+
}
240+
241+
#[test]
242+
fn named_struct_custom_closure_vec_errors_is_ok() {
243+
fn sample_struct_validation(_val: i32) -> Result<(), Vec<serde_valid::validation::Error>> {
244+
Ok(())
245+
}
246+
247+
#[derive(Validate)]
248+
#[validate(custom(|s| sample_struct_validation(s.val)))]
249+
struct TestStruct {
250+
val: i32,
251+
}
252+
253+
let s = TestStruct { val: 5 };
254+
assert_eq!(s.val, 5);
255+
assert!(s.validate().is_ok());
256+
}
257+
258+
#[test]
259+
fn named_struct_custom_closure_vec_errors_is_err() {
260+
fn sample_struct_validation(_val: i32) -> Result<(), Vec<serde_valid::validation::Error>> {
261+
Err(vec![
262+
serde_valid::validation::Error::Custom("Error 1".to_owned()),
263+
serde_valid::validation::Error::Custom("Error 2".to_owned()),
264+
])
265+
}
266+
267+
#[derive(Validate)]
268+
#[validate(custom(|s| sample_struct_validation(s.val)))]
269+
struct TestStruct {
270+
val: i32,
271+
}
272+
273+
let s = TestStruct { val: 5 };
274+
assert_eq!(s.val, 5);
275+
assert_eq!(
276+
s.validate().unwrap_err().to_string(),
277+
json!({
278+
"errors": ["Error 1", "Error 2"],
279+
"properties": {}
280+
})
281+
.to_string()
282+
);
283+
}

serde_valid_derive/src/attribute/field_validate/generic/custom.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ pub fn extract_generic_custom_validator(
3535
}?;
3636

3737
Ok(quote!(
38-
if let Err(__error) = #custom_fn_name(#field_ident) {
38+
if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#custom_fn_name(#field_ident)) {
3939
#errors
4040
.entry(#rename)
4141
.or_default()
42-
.push(__error);
42+
.extend(__errors);
4343
};
4444
))
4545
}

serde_valid_derive/src/attribute/struct_validate/generic/custom.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ fn extract_struct_custom_from_meta_path(meta_path: &syn::Path) -> Result<Validat
5353
let rule_fn_name = &meta_path;
5454

5555
Ok(quote!(
56-
if let Err(__error) = #rule_fn_name(self) {
57-
__rule_vec_errors.push(__error);
56+
if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#rule_fn_name(self)) {
57+
__rule_vec_errors.extend(__errors);
5858
};
5959
))
6060
}
@@ -63,8 +63,8 @@ fn extract_struct_custom_from_meta_list(
6363
meta_list: &syn::MetaList,
6464
) -> Result<Validator, crate::Errors> {
6565
Ok(quote!(
66-
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #meta_list) {
67-
__rule_vec_errors.push(__error);
66+
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #meta_list) {
67+
__rule_vec_errors.extend(__errors);
6868
};
6969
))
7070
}
@@ -73,8 +73,8 @@ fn extract_struct_custom_from_closure(
7373
closure: &syn::ExprClosure,
7474
) -> Result<Validator, crate::Errors> {
7575
Ok(quote!(
76-
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #closure) {
77-
__rule_vec_errors.push(__error);
76+
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #closure) {
77+
__rule_vec_errors.extend(__errors);
7878
};
7979
))
8080
}

serde_valid_derive/src/attribute/variant_validate/generic/custom.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ fn extract_variant_custom_from_meta_path(
5555
let rule_fn_name = &meta_path;
5656

5757
Ok(quote!(
58-
if let Err(__error) = #rule_fn_name(self) {
59-
__rule_vec_errors.push(__error);
58+
if let Err(__errors) = serde_valid::validation::custom::wrap_into_vec_errors(#rule_fn_name(self)) {
59+
__rule_vec_errors.extend(__errors);
6060
};
6161
))
6262
}
@@ -65,8 +65,8 @@ fn extract_variant_custom_from_meta_list(
6565
meta_list: &syn::MetaList,
6666
) -> Result<Validator, crate::Errors> {
6767
Ok(quote!(
68-
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #meta_list) {
69-
__rule_vec_errors.push(__error);
68+
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #meta_list) {
69+
__rule_vec_errors.extend(__errors);
7070
};
7171
))
7272
}
@@ -75,8 +75,8 @@ fn extract_variant_custom_from_closure(
7575
closure: &syn::ExprClosure,
7676
) -> Result<Validator, crate::Errors> {
7777
Ok(quote!(
78-
if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #closure) {
79-
__rule_vec_errors.push(__error);
78+
if let Err(__errors) = serde_valid::validation::custom::wrap_closure_validation(self, #closure) {
79+
__rule_vec_errors.extend(__errors);
8080
};
8181
))
8282
}

0 commit comments

Comments
 (0)