Skip to content

Commit

Permalink
support multi validations
Browse files Browse the repository at this point in the history
  • Loading branch information
zao111222333 committed Feb 12, 2025
1 parent cb2cf61 commit e611d5f
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 67 deletions.
42 changes: 23 additions & 19 deletions serde_derive/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result<To
let params = Parameters::new(&cont);
let (de_impl_generics, _, ty_generics, where_clause) = split_with_de_lifetime(&params);
let body = Stmts(deserialize_body(&cont, &params));
let vaildated_body = validate_body(&cont, body)?;
let vaildated_body = validate_body(&cont, body);
let delife = params.borrowed.de_lifetime();
let serde = cont.attrs.serde_path();

Expand Down Expand Up @@ -274,27 +274,31 @@ fn borrowed_lifetimes(cont: &Container) -> BorrowedLifetimes {
}
}

fn validate_body(cont: &Container, body: Stmts) -> syn::Result<TokenStream> {
fn validate_body(cont: &Container, body: Stmts) -> TokenStream {
let serde = cont.attrs.serde_path();
match (cont.attrs.validate(), cont.attrs.validator()) {
(None, false) => Ok(quote! { #body }),
(Some(validate), false) => Ok(quote! {
let body = { #body }?;
#validate(&body).map_err(#serde::de::Error::custom)?;
Ok(body)
}),
(None, true) => Ok(quote! {
let mut validations: Vec<TokenStream> = cont
.attrs
.validate()
.iter()
.map(|validate| {
quote! {
#validate(&body).map_err(#serde::de::Error::custom)?;
}
})
.collect();
if cont.attrs.validator() {
validations.push(
quote! {validator::Validate::validate(&body).map_err(#serde::de::Error::custom)?;},
);
}
if validations.is_empty() {
quote! { #body }
} else {
quote! {
let body = { #body }?;
validator::Validate::validate(&body).map_err(#serde::de::Error::custom)?;
#(#validations)*
Ok(body)
}),
// If both `validator` and `validate` are defined, error will be reported
// on the #[serde(validate = "...")]
// ^^^^^
(Some(validate), true) => Err(syn::Error::new(
validate.span(),
"can not define both `validator` and `validate`",
)),
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub struct Container {
remote: Option<syn::Path>,
identifier: Identifier,
serde_path: Option<syn::Path>,
validate: Option<syn::ExprPath>,
validate: Vec<syn::ExprPath>,
validator: bool,
is_packed: bool,
/// Error message generated when type can't be deserialized
Expand Down Expand Up @@ -258,7 +258,7 @@ impl Container {
let mut remote = Attr::none(cx, REMOTE);
let mut field_identifier = BoolAttr::none(cx, FIELD_IDENTIFIER);
let mut variant_identifier = BoolAttr::none(cx, VARIANT_IDENTIFIER);
let mut validate = Attr::none(cx, VALIDATE);
let mut validate = VecAttr::none(cx, VALIDATE);
let mut validator = BoolAttr::none(cx, VALIDATOR);
let mut serde_path = Attr::none(cx, CRATE);
let mut expecting = Attr::none(cx, EXPECTING);
Expand Down Expand Up @@ -493,7 +493,7 @@ impl Container {
} else if meta.path == VALIDATE {
// #[serde(validate = "...")]
if let Some(path) = parse_lit_into_expr_path(cx, VALIDATE, &meta)? {
validate.set(&meta.path, path);
validate.insert(&meta.path, path);
}
} else if meta.path == VALIDATOR {
// #[serde(validator)]
Expand Down Expand Up @@ -611,7 +611,7 @@ impl Container {
self.remote.as_ref()
}

pub fn validate(&self) -> Option<&syn::ExprPath> {
pub fn validate(&self) -> &[syn::ExprPath] {
self.validate.as_ref()
}

Expand Down
46 changes: 44 additions & 2 deletions test_suite/tests/test_validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ use serde_test::{assert_de_tokens, assert_de_tokens_error, Token};
use std::fmt::Display;

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(validate = "validate_struct")]
#[serde(validate = "validate_struct1")]
#[serde(validate = "validate_struct2")]
struct Struct {
a: u16,
}

fn validate_struct(deserialized: &Struct) -> Result<(), impl Display> {
fn validate_struct1(deserialized: &Struct) -> Result<(), impl Display> {
if deserialized.a == 0 {
return Err("field `a` can not be zero");
}
Ok(())
}

fn validate_struct2(deserialized: &Struct) -> Result<(), impl Display> {
if deserialized.a == 2 {
return Err("field `a` can not be two");
}
Ok(())
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(validate = "validate_tuple_struct")]
struct TupleStruct(u16);
Expand Down Expand Up @@ -53,6 +61,19 @@ fn test_struct() {
],
"field `a` can not be zero",
);

assert_de_tokens_error::<Struct>(
&[
Token::Struct {
name: "Struct",
len: 1,
},
Token::Str("a"),
Token::U16(2),
Token::StructEnd,
],
"field `a` can not be two",
);
}

#[test]
Expand Down Expand Up @@ -179,11 +200,19 @@ struct ValidateStruct {

#[derive(Debug, PartialEq, validator::Validate, Deserialize)]
#[serde(validator)]
#[serde(validate = "validator_struct_name")]
struct ValidatorStruct {
#[validate(email)]
mail: String,
}

fn validator_struct_name(deserialized: &ValidatorStruct) -> Result<(), impl Display> {
if deserialized.mail.starts_with("foo@") {
return Err("name can not be 'foo'");
}
Ok(())
}

#[test]
fn test_validate_struct() {
assert_de_tokens(
Expand Down Expand Up @@ -244,4 +273,17 @@ fn test_validator_struct() {
],
"mail: Validation error: email [{\"value\": String(\"email.example.com\")}]",
);

assert_de_tokens_error::<ValidatorStruct>(
&[
Token::Struct {
name: "ValidatorStruct",
len: 1,
},
Token::Str("mail"),
Token::Str("[email protected]"),
Token::StructEnd,
],
"name can not be 'foo'",
);
}
11 changes: 0 additions & 11 deletions test_suite/tests/ui/validate/both_validator_and_validate.rs

This file was deleted.

This file was deleted.

9 changes: 0 additions & 9 deletions test_suite/tests/ui/validate/validator_unimplemented.rs

This file was deleted.

17 changes: 0 additions & 17 deletions test_suite/tests/ui/validate/validator_unimplemented.stderr

This file was deleted.

0 comments on commit e611d5f

Please sign in to comment.