From 780b391f2ebdc78de7810a4a998667f031428108 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Tue, 11 Feb 2025 04:47:35 -0800 Subject: [PATCH 1/9] Support `#[serde(validate = "some_function")]` for container, compatible with validator crate --- serde_derive/src/de.rs | 17 ++- serde_derive/src/internals/attr.rs | 12 ++ serde_derive/src/internals/symbol.rs | 1 + test_suite/Cargo.toml | 1 + test_suite/tests/test_validate.rs | 209 +++++++++++++++++++++++++++ 5 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 test_suite/tests/test_validate.rs diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 4967e35d1..573dfd589 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -26,6 +26,7 @@ pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result syn::Result, { #used - #body + #vaildated_body } } } @@ -54,7 +55,7 @@ pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result, { - #body + #vaildated_body } #fn_deserialize_in_place @@ -273,6 +274,18 @@ fn borrowed_lifetimes(cont: &Container) -> BorrowedLifetimes { } } +fn validate_body(cont: &Container, body: Stmts) -> TokenStream { + if let Some(vaildate) = cont.attrs.vaildate() { + quote! { + let __body = { #body }?; + #vaildate(&__body).map_err(_serde::de::Error::custom)?; + Ok(__body) + } + } else { + quote! { #body } + } +} + fn deserialize_body(cont: &Container, params: &Parameters) -> Fragment { if cont.attrs.transparent() { deserialize_transparent(cont, params) diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 6d846ed01..71940011b 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -169,6 +169,7 @@ pub struct Container { remote: Option, identifier: Identifier, serde_path: Option, + vaildate: Option, is_packed: bool, /// Error message generated when type can't be deserialized expecting: Option, @@ -256,6 +257,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 vaildate = Attr::none(cx, VALIDATE); let mut serde_path = Attr::none(cx, CRATE); let mut expecting = Attr::none(cx, EXPECTING); let mut non_exhaustive = false; @@ -486,6 +488,11 @@ impl Container { if let Some(path) = parse_lit_into_path(cx, CRATE, &meta)? { serde_path.set(&meta.path, path); } + } else if meta.path == VALIDATE { + // #[serde(validate = "...")] + if let Some(path) = parse_lit_into_expr_path(cx, VALIDATE, &meta)? { + vaildate.set(&meta.path, path); + } } else if meta.path == EXPECTING { // #[serde(expecting = "a message")] if let Some(s) = get_lit_str(cx, EXPECTING, &meta)? { @@ -539,6 +546,7 @@ impl Container { remote: remote.get(), identifier: decide_identifier(cx, item, field_identifier, variant_identifier), serde_path: serde_path.get(), + vaildate: vaildate.get(), is_packed, expecting: expecting.get(), non_exhaustive, @@ -597,6 +605,10 @@ impl Container { self.remote.as_ref() } + pub fn vaildate(&self) -> Option<&syn::ExprPath> { + self.vaildate.as_ref() + } + pub fn is_packed(&self) -> bool { self.is_packed } diff --git a/serde_derive/src/internals/symbol.rs b/serde_derive/src/internals/symbol.rs index 59ef8de7c..3813dc787 100644 --- a/serde_derive/src/internals/symbol.rs +++ b/serde_derive/src/internals/symbol.rs @@ -38,6 +38,7 @@ pub const TRANSPARENT: Symbol = Symbol("transparent"); pub const TRY_FROM: Symbol = Symbol("try_from"); pub const UNTAGGED: Symbol = Symbol("untagged"); pub const VARIANT_IDENTIFIER: Symbol = Symbol("variant_identifier"); +pub const VALIDATE: Symbol = Symbol("validate"); pub const WITH: Symbol = Symbol("with"); impl PartialEq for Ident { diff --git a/test_suite/Cargo.toml b/test_suite/Cargo.toml index 4076e735a..32a45177a 100644 --- a/test_suite/Cargo.toml +++ b/test_suite/Cargo.toml @@ -19,3 +19,4 @@ serde = { path = "../serde", features = ["rc"] } serde_derive = { path = "../serde_derive", features = ["deserialize_in_place"] } serde_test = "1.0.176" trybuild = { version = "1.0.97", features = ["diff"] } +validator = { version = "0.20", features = ["derive"] } diff --git a/test_suite/tests/test_validate.rs b/test_suite/tests/test_validate.rs new file mode 100644 index 000000000..dfe1b564f --- /dev/null +++ b/test_suite/tests/test_validate.rs @@ -0,0 +1,209 @@ +use serde_derive::{Deserialize, Serialize}; +use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; +use std::fmt::Display; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(validate = "validate_struct")] +struct Struct { + a: u16, +} + +fn validate_struct(deserialized: &Struct) -> Result<(), impl Display> { + if deserialized.a == 0 { + return Err("field `a` can not be zero"); + } + Ok(()) +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(validate = "validate_tuple_struct")] +struct TupleStruct(u16); + +fn validate_tuple_struct(deserialized: &TupleStruct) -> Result<(), impl Display> { + if deserialized.0 == 0 { + return Err("field `0` can not be zero"); + } + Ok(()) +} + +#[test] +fn test_struct() { + assert_de_tokens( + &Struct { a: 1 }, + &[ + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("a"), + Token::U16(1), + Token::StructEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("a"), + Token::U16(0), + Token::StructEnd, + ], + "field `a` can not be zero", + ); +} + +#[test] +fn test_tuple_struct() { + assert_de_tokens( + &TupleStruct(1), + &[ + Token::TupleStruct { + name: "TupleStruct", + len: 1, + }, + Token::U16(1), + Token::TupleStructEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::TupleStruct { + name: "TupleStruct", + len: 1, + }, + Token::U16(0), + Token::TupleStructEnd, + ], + "field `0` can not be zero", + ); +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(validate = "validate_struct_variant")] +enum StructVariant { + Struct { a: u16 }, +} + +fn validate_struct_variant(deserialized: &StructVariant) -> Result<(), impl Display> { + if let StructVariant::Struct { a: 0 } = deserialized { + return Err("variant `Struct.a` can not be zero"); + } + Ok(()) +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[serde(validate = "validate_tuple_variant")] +enum TupleVariant { + A(u16, u16), +} + +fn validate_tuple_variant(deserialized: &TupleVariant) -> Result<(), impl Display> { + if let TupleVariant::A(0, _) = deserialized { + return Err("variant `A.0` can not be zero"); + } + Ok(()) +} + +#[test] +fn test_struct_variant() { + assert_de_tokens( + &StructVariant::Struct { a: 1 }, + &[ + Token::StructVariant { + name: "StructVariant", + variant: "Struct", + len: 1, + }, + Token::Str("a"), + Token::U16(1), + Token::StructVariantEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::StructVariant { + name: "StructVariant", + variant: "Struct", + len: 1, + }, + Token::Str("a"), + Token::U16(0), + Token::StructVariantEnd, + ], + "variant `Struct.a` can not be zero", + ); +} + +#[test] +fn test_tuple_variant() { + assert_de_tokens( + &TupleVariant::A(1, 1), + &[ + Token::TupleVariant { + name: "TupleVariant", + variant: "A", + len: 2, + }, + Token::U16(1), + Token::U16(1), + Token::TupleVariantEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::TupleVariant { + name: "TupleVariant", + variant: "A", + len: 2, + }, + Token::U16(0), + Token::U16(1), + Token::TupleVariantEnd, + ], + "variant `A.0` can not be zero", + ); +} + +#[derive(Debug, PartialEq, validator::Validate, Deserialize)] +#[serde(validate = "validator::Validate::validate")] +struct ValidatorDemo { + #[validate(email)] + mail: String, +} + +#[test] +fn test_validator_demo() { + assert_de_tokens( + &ValidatorDemo { + mail: "email@example.com".into(), + }, + &[ + Token::Struct { + name: "ValidatorDemo", + len: 1, + }, + Token::Str("mail"), + Token::Str("email@example.com"), + Token::StructEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "ValidatorDemo", + len: 1, + }, + Token::Str("mail"), + Token::Str("email.example.com"), + Token::StructEnd, + ], + "mail: Validation error: email [{\"value\": String(\"email.example.com\")}]", + ); +} From b82d4a96eb05e4fafc48360e71a0267612ac86ec Mon Sep 17 00:00:00 2001 From: junzhuo Date: Tue, 11 Feb 2025 05:19:58 -0800 Subject: [PATCH 2/9] remove the underline of `__body` --- serde_derive/src/de.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 573dfd589..579d81961 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -277,9 +277,9 @@ fn borrowed_lifetimes(cont: &Container) -> BorrowedLifetimes { fn validate_body(cont: &Container, body: Stmts) -> TokenStream { if let Some(vaildate) = cont.attrs.vaildate() { quote! { - let __body = { #body }?; - #vaildate(&__body).map_err(_serde::de::Error::custom)?; - Ok(__body) + let body = { #body }?; + #vaildate(&body).map_err(_serde::de::Error::custom)?; + Ok(body) } } else { quote! { #body } From da1d39649291e9a8cacc3dad8a63f2b2ac282a3e Mon Sep 17 00:00:00 2001 From: Junzhuo ZHOU Date: Tue, 11 Feb 2025 13:59:42 -0800 Subject: [PATCH 3/9] Update serde_derive/src/de.rs Co-authored-by: Mingun --- serde_derive/src/de.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 579d81961..4e422d834 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -275,10 +275,10 @@ fn borrowed_lifetimes(cont: &Container) -> BorrowedLifetimes { } fn validate_body(cont: &Container, body: Stmts) -> TokenStream { - if let Some(vaildate) = cont.attrs.vaildate() { + if let Some(validate) = cont.attrs.validate() { quote! { let body = { #body }?; - #vaildate(&body).map_err(_serde::de::Error::custom)?; + #validate(&body).map_err(_serde::de::Error::custom)?; Ok(body) } } else { From b5febd6666d19a0d8fd3cb8eba4a7c3b9b6b2801 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Tue, 11 Feb 2025 14:03:26 -0800 Subject: [PATCH 4/9] fix typo --- serde_derive/src/internals/attr.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 71940011b..7ae7550cf 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -169,7 +169,7 @@ pub struct Container { remote: Option, identifier: Identifier, serde_path: Option, - vaildate: Option, + validate: Option, is_packed: bool, /// Error message generated when type can't be deserialized expecting: Option, @@ -257,7 +257,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 vaildate = Attr::none(cx, VALIDATE); + let mut validate = Attr::none(cx, VALIDATE); let mut serde_path = Attr::none(cx, CRATE); let mut expecting = Attr::none(cx, EXPECTING); let mut non_exhaustive = false; @@ -491,7 +491,7 @@ impl Container { } else if meta.path == VALIDATE { // #[serde(validate = "...")] if let Some(path) = parse_lit_into_expr_path(cx, VALIDATE, &meta)? { - vaildate.set(&meta.path, path); + validate.set(&meta.path, path); } } else if meta.path == EXPECTING { // #[serde(expecting = "a message")] @@ -546,7 +546,7 @@ impl Container { remote: remote.get(), identifier: decide_identifier(cx, item, field_identifier, variant_identifier), serde_path: serde_path.get(), - vaildate: vaildate.get(), + validate: validate.get(), is_packed, expecting: expecting.get(), non_exhaustive, @@ -605,8 +605,8 @@ impl Container { self.remote.as_ref() } - pub fn vaildate(&self) -> Option<&syn::ExprPath> { - self.vaildate.as_ref() + pub fn validate(&self) -> Option<&syn::ExprPath> { + self.validate.as_ref() } pub fn is_packed(&self) -> bool { From 77f2f67ac6ddd71871c6b1e8ff51d67d2ce53f29 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Wed, 12 Feb 2025 05:11:51 -0800 Subject: [PATCH 5/9] fix serde_path --- serde_derive/src/de.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 4e422d834..bf91e15fe 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -275,10 +275,11 @@ fn borrowed_lifetimes(cont: &Container) -> BorrowedLifetimes { } fn validate_body(cont: &Container, body: Stmts) -> TokenStream { + let serde = cont.attrs.serde_path(); if let Some(validate) = cont.attrs.validate() { quote! { let body = { #body }?; - #validate(&body).map_err(_serde::de::Error::custom)?; + #validate(&body).map_err(#serde::de::Error::custom)?; Ok(body) } } else { From cb2cf61f7f1e785c26d8393d236a068630796f93 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Wed, 12 Feb 2025 07:58:53 -0800 Subject: [PATCH 6/9] support `#[serde(validator)]` --- serde_derive/src/de.rs | 25 +++++++--- serde_derive/src/internals/attr.rs | 10 ++++ serde_derive/src/internals/symbol.rs | 1 + test_suite/tests/test_validate.rs | 50 ++++++++++++++++--- .../validate/both_validator_and_validate.rs | 11 ++++ .../both_validator_and_validate.stderr | 5 ++ .../ui/validate/validator_unimplemented.rs | 9 ++++ .../validate/validator_unimplemented.stderr | 17 +++++++ 8 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 test_suite/tests/ui/validate/both_validator_and_validate.rs create mode 100644 test_suite/tests/ui/validate/both_validator_and_validate.stderr create mode 100644 test_suite/tests/ui/validate/validator_unimplemented.rs create mode 100644 test_suite/tests/ui/validate/validator_unimplemented.stderr diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index bf91e15fe..b2b226428 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -26,7 +26,7 @@ pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result BorrowedLifetimes { } } -fn validate_body(cont: &Container, body: Stmts) -> TokenStream { +fn validate_body(cont: &Container, body: Stmts) -> syn::Result { let serde = cont.attrs.serde_path(); - if let Some(validate) = cont.attrs.validate() { - quote! { + 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) - } - } else { - quote! { #body } + }), + (None, true) => Ok(quote! { + let body = { #body }?; + validator::Validate::validate(&body).map_err(#serde::de::Error::custom)?; + 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`", + )), } } diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 7ae7550cf..48a734242 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -170,6 +170,7 @@ pub struct Container { identifier: Identifier, serde_path: Option, validate: Option, + validator: bool, is_packed: bool, /// Error message generated when type can't be deserialized expecting: Option, @@ -258,6 +259,7 @@ impl Container { 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 validator = BoolAttr::none(cx, VALIDATOR); let mut serde_path = Attr::none(cx, CRATE); let mut expecting = Attr::none(cx, EXPECTING); let mut non_exhaustive = false; @@ -493,6 +495,9 @@ impl Container { if let Some(path) = parse_lit_into_expr_path(cx, VALIDATE, &meta)? { validate.set(&meta.path, path); } + } else if meta.path == VALIDATOR { + // #[serde(validator)] + validator.set_true(meta.path); } else if meta.path == EXPECTING { // #[serde(expecting = "a message")] if let Some(s) = get_lit_str(cx, EXPECTING, &meta)? { @@ -547,6 +552,7 @@ impl Container { identifier: decide_identifier(cx, item, field_identifier, variant_identifier), serde_path: serde_path.get(), validate: validate.get(), + validator: validator.get(), is_packed, expecting: expecting.get(), non_exhaustive, @@ -609,6 +615,10 @@ impl Container { self.validate.as_ref() } + pub fn validator(&self) -> bool { + self.validator + } + pub fn is_packed(&self) -> bool { self.is_packed } diff --git a/serde_derive/src/internals/symbol.rs b/serde_derive/src/internals/symbol.rs index 3813dc787..a2abad448 100644 --- a/serde_derive/src/internals/symbol.rs +++ b/serde_derive/src/internals/symbol.rs @@ -39,6 +39,7 @@ pub const TRY_FROM: Symbol = Symbol("try_from"); pub const UNTAGGED: Symbol = Symbol("untagged"); pub const VARIANT_IDENTIFIER: Symbol = Symbol("variant_identifier"); pub const VALIDATE: Symbol = Symbol("validate"); +pub const VALIDATOR: Symbol = Symbol("validator"); pub const WITH: Symbol = Symbol("with"); impl PartialEq for Ident { diff --git a/test_suite/tests/test_validate.rs b/test_suite/tests/test_validate.rs index dfe1b564f..a23989f8b 100644 --- a/test_suite/tests/test_validate.rs +++ b/test_suite/tests/test_validate.rs @@ -172,20 +172,58 @@ fn test_tuple_variant() { #[derive(Debug, PartialEq, validator::Validate, Deserialize)] #[serde(validate = "validator::Validate::validate")] -struct ValidatorDemo { +struct ValidateStruct { #[validate(email)] mail: String, } +#[derive(Debug, PartialEq, validator::Validate, Deserialize)] +#[serde(validator)] +struct ValidatorStruct { + #[validate(email)] + mail: String, +} + +#[test] +fn test_validate_struct() { + assert_de_tokens( + &ValidateStruct { + mail: "email@example.com".into(), + }, + &[ + Token::Struct { + name: "ValidateStruct", + len: 1, + }, + Token::Str("mail"), + Token::Str("email@example.com"), + Token::StructEnd, + ], + ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "ValidateStruct", + len: 1, + }, + Token::Str("mail"), + Token::Str("email.example.com"), + Token::StructEnd, + ], + "mail: Validation error: email [{\"value\": String(\"email.example.com\")}]", + ); +} + #[test] -fn test_validator_demo() { +fn test_validator_struct() { assert_de_tokens( - &ValidatorDemo { + &ValidatorStruct { mail: "email@example.com".into(), }, &[ Token::Struct { - name: "ValidatorDemo", + name: "ValidatorStruct", len: 1, }, Token::Str("mail"), @@ -194,10 +232,10 @@ fn test_validator_demo() { ], ); - assert_de_tokens_error::( + assert_de_tokens_error::( &[ Token::Struct { - name: "ValidatorDemo", + name: "ValidatorStruct", len: 1, }, Token::Str("mail"), diff --git a/test_suite/tests/ui/validate/both_validator_and_validate.rs b/test_suite/tests/ui/validate/both_validator_and_validate.rs new file mode 100644 index 000000000..66ccdce01 --- /dev/null +++ b/test_suite/tests/ui/validate/both_validator_and_validate.rs @@ -0,0 +1,11 @@ +use serde_derive::Deserialize; + +#[derive(validator::Validate, Deserialize)] +#[serde(validate = "validator::Validate::validate")] +#[serde(validator)] +struct ValidatorStruct { + #[validate(email)] + mail: String, +} + +fn main() {} diff --git a/test_suite/tests/ui/validate/both_validator_and_validate.stderr b/test_suite/tests/ui/validate/both_validator_and_validate.stderr new file mode 100644 index 000000000..330ec5fa9 --- /dev/null +++ b/test_suite/tests/ui/validate/both_validator_and_validate.stderr @@ -0,0 +1,5 @@ +error: can not define both `validator` and `validate` + --> tests/ui/validate/both_validator_and_validate.rs:4:20 + | +4 | #[serde(validate = "validator::Validate::validate")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/test_suite/tests/ui/validate/validator_unimplemented.rs b/test_suite/tests/ui/validate/validator_unimplemented.rs new file mode 100644 index 000000000..5f854045c --- /dev/null +++ b/test_suite/tests/ui/validate/validator_unimplemented.rs @@ -0,0 +1,9 @@ +use serde_derive::Deserialize; + +#[derive(Deserialize)] +#[serde(validator)] +struct ValidatorStruct { + mail: String, +} + +fn main() {} diff --git a/test_suite/tests/ui/validate/validator_unimplemented.stderr b/test_suite/tests/ui/validate/validator_unimplemented.stderr new file mode 100644 index 000000000..a161cbf1e --- /dev/null +++ b/test_suite/tests/ui/validate/validator_unimplemented.stderr @@ -0,0 +1,17 @@ +error[E0277]: the trait bound `ValidatorStruct: Validate` is not satisfied + --> tests/ui/validate/validator_unimplemented.rs:3:10 + | +3 | #[derive(Deserialize)] + | ^^^^^^^^^^^ the trait `Validate` is not implemented for `ValidatorStruct` + | + = help: the following other types implement trait `Validate`: + &BTreeMap + &HashMap + &T + BTreeSet + BinaryHeap + HashSet + LinkedList + Vec + and $N others + = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) From e611d5f12555ed50dfa4b6c605e016f2d3126f08 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Wed, 12 Feb 2025 09:34:47 -0800 Subject: [PATCH 7/9] support multi validations --- serde_derive/src/de.rs | 42 +++++++++-------- serde_derive/src/internals/attr.rs | 8 ++-- test_suite/tests/test_validate.rs | 46 ++++++++++++++++++- .../validate/both_validator_and_validate.rs | 11 ----- .../both_validator_and_validate.stderr | 5 -- .../ui/validate/validator_unimplemented.rs | 9 ---- .../validate/validator_unimplemented.stderr | 17 ------- 7 files changed, 71 insertions(+), 67 deletions(-) delete mode 100644 test_suite/tests/ui/validate/both_validator_and_validate.rs delete mode 100644 test_suite/tests/ui/validate/both_validator_and_validate.stderr delete mode 100644 test_suite/tests/ui/validate/validator_unimplemented.rs delete mode 100644 test_suite/tests/ui/validate/validator_unimplemented.stderr diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index b2b226428..2c7c95620 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -26,7 +26,7 @@ pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result BorrowedLifetimes { } } -fn validate_body(cont: &Container, body: Stmts) -> syn::Result { +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 = 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`", - )), + } } } diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 48a734242..fa4697ae6 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -169,7 +169,7 @@ pub struct Container { remote: Option, identifier: Identifier, serde_path: Option, - validate: Option, + validate: Vec, validator: bool, is_packed: bool, /// Error message generated when type can't be deserialized @@ -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); @@ -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)] @@ -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() } diff --git a/test_suite/tests/test_validate.rs b/test_suite/tests/test_validate.rs index a23989f8b..ccef50858 100644 --- a/test_suite/tests/test_validate.rs +++ b/test_suite/tests/test_validate.rs @@ -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); @@ -53,6 +61,19 @@ fn test_struct() { ], "field `a` can not be zero", ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "Struct", + len: 1, + }, + Token::Str("a"), + Token::U16(2), + Token::StructEnd, + ], + "field `a` can not be two", + ); } #[test] @@ -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( @@ -244,4 +273,17 @@ fn test_validator_struct() { ], "mail: Validation error: email [{\"value\": String(\"email.example.com\")}]", ); + + assert_de_tokens_error::( + &[ + Token::Struct { + name: "ValidatorStruct", + len: 1, + }, + Token::Str("mail"), + Token::Str("foo@example.com"), + Token::StructEnd, + ], + "name can not be 'foo'", + ); } diff --git a/test_suite/tests/ui/validate/both_validator_and_validate.rs b/test_suite/tests/ui/validate/both_validator_and_validate.rs deleted file mode 100644 index 66ccdce01..000000000 --- a/test_suite/tests/ui/validate/both_validator_and_validate.rs +++ /dev/null @@ -1,11 +0,0 @@ -use serde_derive::Deserialize; - -#[derive(validator::Validate, Deserialize)] -#[serde(validate = "validator::Validate::validate")] -#[serde(validator)] -struct ValidatorStruct { - #[validate(email)] - mail: String, -} - -fn main() {} diff --git a/test_suite/tests/ui/validate/both_validator_and_validate.stderr b/test_suite/tests/ui/validate/both_validator_and_validate.stderr deleted file mode 100644 index 330ec5fa9..000000000 --- a/test_suite/tests/ui/validate/both_validator_and_validate.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: can not define both `validator` and `validate` - --> tests/ui/validate/both_validator_and_validate.rs:4:20 - | -4 | #[serde(validate = "validator::Validate::validate")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/test_suite/tests/ui/validate/validator_unimplemented.rs b/test_suite/tests/ui/validate/validator_unimplemented.rs deleted file mode 100644 index 5f854045c..000000000 --- a/test_suite/tests/ui/validate/validator_unimplemented.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde_derive::Deserialize; - -#[derive(Deserialize)] -#[serde(validator)] -struct ValidatorStruct { - mail: String, -} - -fn main() {} diff --git a/test_suite/tests/ui/validate/validator_unimplemented.stderr b/test_suite/tests/ui/validate/validator_unimplemented.stderr deleted file mode 100644 index a161cbf1e..000000000 --- a/test_suite/tests/ui/validate/validator_unimplemented.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0277]: the trait bound `ValidatorStruct: Validate` is not satisfied - --> tests/ui/validate/validator_unimplemented.rs:3:10 - | -3 | #[derive(Deserialize)] - | ^^^^^^^^^^^ the trait `Validate` is not implemented for `ValidatorStruct` - | - = help: the following other types implement trait `Validate`: - &BTreeMap - &HashMap - &T - BTreeSet - BinaryHeap - HashSet - LinkedList - Vec - and $N others - = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) From 26580d59f615b8bae23e240a6bb6bb6c54ec4d13 Mon Sep 17 00:00:00 2001 From: junzhuo Date: Wed, 12 Feb 2025 16:38:50 -0800 Subject: [PATCH 8/9] format quote --- serde_derive/src/de.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 2c7c95620..ac27a0834 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -288,7 +288,9 @@ fn validate_body(cont: &Container, body: Stmts) -> TokenStream { .collect(); if cont.attrs.validator() { validations.push( - quote! {validator::Validate::validate(&body).map_err(#serde::de::Error::custom)?;}, + quote! { + validator::Validate::validate(&body).map_err(#serde::de::Error::custom)?; + }, ); } if validations.is_empty() { From 2409f74252a5a0ef156d4b12217113d2722bd5bb Mon Sep 17 00:00:00 2001 From: junzhuo Date: Wed, 12 Feb 2025 22:19:58 -0800 Subject: [PATCH 9/9] fix typo --- serde_derive/src/de.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index ac27a0834..ace27ddf8 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -26,7 +26,7 @@ pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result syn::Result, { #used - #vaildated_body + #validated_body } } } @@ -55,7 +55,7 @@ pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result, { - #vaildated_body + #validated_body } #fn_deserialize_in_place