diff --git a/Cargo.toml b/Cargo.toml index 9f6e1ed..79b979a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ regex = "^1.6" paste = "^1.0" serde_json = "^1.0" serde = "^1.0" +itertools = "0.13.0" diff --git a/serde_valid/Cargo.toml b/serde_valid/Cargo.toml index 4128b08..bfe8067 100644 --- a/serde_valid/Cargo.toml +++ b/serde_valid/Cargo.toml @@ -14,13 +14,13 @@ version.workspace = true [dependencies] fluent = { package = "fluent", version = "^0.16.0", optional = true } indexmap = { version = "^2.0", features = ["serde"] } -itertools = "^0.13" +itertools.workspace = true num-traits = "^0.2" once_cell = "^1.7" -paste = { workspace = true } -regex = { workspace = true } +paste.workspace = true +regex.workspace = true serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } +serde_json.workspace = true serde_toml = { package = "toml", version = "^0.8", optional = true } serde_valid_derive = { path = "../serde_valid_derive" } serde_valid_literal = { path = "../serde_valid_literal" } diff --git a/serde_valid/tests/array_test.rs b/serde_valid/tests/array_test.rs index c98c794..3e00eda 100644 --- a/serde_valid/tests/array_test.rs +++ b/serde_valid/tests/array_test.rs @@ -3,7 +3,7 @@ use serde_valid::Validate; #[test] fn items_err_message() { - fn rule_sample(_a: &i32) -> Result<(), serde_valid::validation::Error> { + fn rule_sample(_a: i32) -> Result<(), serde_valid::validation::Error> { Err(serde_valid::validation::Error::Custom( "Rule error.".to_owned(), )) @@ -18,7 +18,7 @@ fn items_err_message() { } #[derive(Validate)] - #[rule(rule_sample(val))] + #[validate(custom(|s| rule_sample(s.val)))] struct TestChildStruct { #[validate(minimum = 1)] #[validate(maximum = 10)] diff --git a/serde_valid/tests/complex_test.rs b/serde_valid/tests/complex_test.rs index 082e57c..e517845 100644 --- a/serde_valid/tests/complex_test.rs +++ b/serde_valid/tests/complex_test.rs @@ -1,11 +1,11 @@ use serde_valid::Validate; -fn sample_rule(_val: &i32) -> Result<(), serde_valid::validation::Error> { +fn sample_rule(_val: i32) -> Result<(), serde_valid::validation::Error> { Ok(()) } #[derive(Debug, Validate)] -#[rule(sample_rule(int_value))] +#[validate(custom(|s| sample_rule(s.int_value)))] struct TestStruct<'a> { // Generic validator #[validate(enumerate(5, 10, 15))] diff --git a/serde_valid/tests/enum_test.rs b/serde_valid/tests/enum_test.rs index 6ee2f73..ada124d 100644 --- a/serde_valid/tests/enum_test.rs +++ b/serde_valid/tests/enum_test.rs @@ -2,13 +2,13 @@ use serde_json::json; use serde_valid::Validate; #[test] -fn enum_named_variant_validation_is_ok() { - fn ok_rule(_a: &TestStruct, _b: &TestStruct) -> Result<(), serde_valid::validation::Error> { +fn enum_named_enum_validation_is_ok() { + fn ok_enum(_value: &TestEnum) -> Result<(), serde_valid::validation::Error> { Ok(()) } #[derive(Validate)] + #[validate(custom(ok_enum))] enum TestEnum { - #[rule(ok_rule(a, b))] Named { #[validate] a: TestStruct, @@ -70,16 +70,16 @@ fn enum_newtype_variant_validation_is_ok() { } #[test] -fn enum_named_variant_validation_is_err() { - fn err_rule(_a: &TestStruct, _b: &TestStruct) -> Result<(), serde_valid::validation::Error> { +fn enum_named_enum_validation_is_err() { + fn err_rule(_data: &TestEnum) -> Result<(), serde_valid::validation::Error> { Err(serde_valid::validation::Error::Custom( "Rule error.".to_owned(), )) } #[derive(Validate)] + #[validate(custom(err_rule))] enum TestEnum { - #[rule(err_rule(a, b))] Named { #[validate] a: TestStruct, diff --git a/serde_valid/tests/specific_test.rs b/serde_valid/tests/specific_test.rs index d877519..65e3bae 100644 --- a/serde_valid/tests/specific_test.rs +++ b/serde_valid/tests/specific_test.rs @@ -3,13 +3,13 @@ use serde_valid::Validate; #[test] fn test_raw_type_field() { #[derive(Validate)] - #[rule(sample_rule(r#type))] + #[validate(custom(|s| sample_rule(s.r#type)))] struct MyStruct { #[validate(maximum = 10)] pub r#type: i32, } - fn sample_rule(_type: &i32) -> Result<(), serde_valid::validation::Error> { + fn sample_rule(_type: i32) -> Result<(), serde_valid::validation::Error> { Ok(()) } diff --git a/serde_valid_derive/Cargo.toml b/serde_valid_derive/Cargo.toml index d5e2897..c0d7ecd 100644 --- a/serde_valid_derive/Cargo.toml +++ b/serde_valid_derive/Cargo.toml @@ -16,7 +16,8 @@ version.workspace = true proc-macro = true [dependencies] -paste = { workspace = true } +itertools.workspace = true +paste.workspace = true proc-macro-error = "^1.0" proc-macro2 = "^1.0" quote = "^1.0" diff --git a/serde_valid_derive/src/attribute.rs b/serde_valid_derive/src/attribute.rs index 683a6c3..5c77386 100644 --- a/serde_valid_derive/src/attribute.rs +++ b/serde_valid_derive/src/attribute.rs @@ -4,6 +4,7 @@ pub mod common; pub mod field_validate; pub mod rule; pub mod struct_validate; +pub mod variant_validate; pub type Validator = TokenStream; diff --git a/serde_valid_derive/src/attribute/rule/named_struct_rule.rs b/serde_valid_derive/src/attribute/rule/named_struct_rule.rs index 493d0ee..01269a8 100644 --- a/serde_valid_derive/src/attribute/rule/named_struct_rule.rs +++ b/serde_valid_derive/src/attribute/rule/named_struct_rule.rs @@ -4,17 +4,29 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::spanned::Spanned; -use crate::types::{CommaSeparatedNestedMetas, CommaSeparatedTokenStreams}; +use crate::{ + output_stream::OutputStream, + types::{CommaSeparatedNestedMetas, CommaSeparatedTokenStreams}, + warning::Warning, +}; pub fn collect_rules_from_named_struct( + ident: &syn::Ident, attributes: &[syn::Attribute], -) -> Result<(HashSet, TokenStream), crate::Errors> { +) -> Result<(HashSet, OutputStream), crate::Errors> { let mut errors = vec![]; let mut rule_fields = HashSet::new(); + let mut warnings = vec![]; let rules = attributes .iter() .filter(|attribute| attribute.path().is_ident("rule")) + .inspect(|attribute| { + warnings.push(Warning::new_rule_deprecated( + ident, + attribute.bracket_token.span.span(), + )); + }) .filter_map(|attribute| match &attribute.meta { syn::Meta::List(list) => match collect_rule(list) { Ok((field_ident, stream)) => { @@ -36,7 +48,13 @@ pub fn collect_rules_from_named_struct( .collect::>(); if errors.is_empty() { - Ok((rule_fields, TokenStream::from_iter(rules))) + Ok(( + rule_fields, + OutputStream { + output: TokenStream::from_iter(rules), + warnings, + }, + )) } else { Err(errors) } diff --git a/serde_valid_derive/src/attribute/rule/unnamed_struct_rule.rs b/serde_valid_derive/src/attribute/rule/unnamed_struct_rule.rs index 80e603e..2219c18 100644 --- a/serde_valid_derive/src/attribute/rule/unnamed_struct_rule.rs +++ b/serde_valid_derive/src/attribute/rule/unnamed_struct_rule.rs @@ -4,17 +4,29 @@ use proc_macro2::TokenStream; use quote::quote; use syn::spanned::Spanned; -use crate::types::{CommaSeparatedNestedMetas, CommaSeparatedTokenStreams, NestedMeta}; +use crate::{ + output_stream::OutputStream, + types::{CommaSeparatedNestedMetas, CommaSeparatedTokenStreams, NestedMeta}, + warning::Warning, +}; pub fn collect_rules_from_unnamed_struct( + ident: &syn::Ident, attributes: &[syn::Attribute], -) -> Result<(HashSet, TokenStream), crate::Errors> { +) -> Result<(HashSet, OutputStream), crate::Errors> { let mut errors = vec![]; let mut rule_fields = HashSet::new(); + let mut warnings = vec![]; let rules = attributes .iter() .filter(|attribute| attribute.path().is_ident("rule")) + .inspect(|attribute| { + warnings.push(Warning::new_rule_deprecated( + ident, + attribute.bracket_token.span.span(), + )); + }) .filter_map(|attribute| match &attribute.meta { syn::Meta::List(list) => match collect_rule(list) { Ok((field_ident, stream)) => { @@ -36,7 +48,13 @@ pub fn collect_rules_from_unnamed_struct( .collect::>(); if errors.is_empty() { - Ok((rule_fields, TokenStream::from_iter(rules))) + Ok(( + rule_fields, + OutputStream { + output: TokenStream::from_iter(rules), + warnings, + }, + )) } else { Err(errors) } diff --git a/serde_valid_derive/src/attribute/variant_validate.rs b/serde_valid_derive/src/attribute/variant_validate.rs new file mode 100644 index 0000000..cf2b4e1 --- /dev/null +++ b/serde_valid_derive/src/attribute/variant_validate.rs @@ -0,0 +1,35 @@ +mod generic; +mod meta; + +use crate::attribute::Validator; + +use self::meta::extract_variant_validator; + +pub fn collect_variant_custom_from_named_variant( + attributes: &[syn::Attribute], +) -> Result { + let mut errors = vec![]; + + let validations = attributes + .iter() + .filter_map(|attribute| { + if attribute.path().is_ident("validate") { + match extract_variant_validator(attribute) { + Ok(validator) => Some(validator), + Err(validator_error) => { + errors.extend(validator_error); + None + } + } + } else { + None + } + }) + .collect::>(); + + if errors.is_empty() { + Ok(Validator::from_iter(validations)) + } else { + Err(errors) + } +} diff --git a/serde_valid_derive/src/attribute/variant_validate/generic.rs b/serde_valid_derive/src/attribute/variant_validate/generic.rs new file mode 100644 index 0000000..49e6c40 --- /dev/null +++ b/serde_valid_derive/src/attribute/variant_validate/generic.rs @@ -0,0 +1,3 @@ +mod custom; + +pub use custom::extract_generic_variant_custom_validator; diff --git a/serde_valid_derive/src/attribute/variant_validate/generic/custom.rs b/serde_valid_derive/src/attribute/variant_validate/generic/custom.rs new file mode 100644 index 0000000..46d0b8f --- /dev/null +++ b/serde_valid_derive/src/attribute/variant_validate/generic/custom.rs @@ -0,0 +1,82 @@ +use crate::attribute::common::message_format::MessageFormat; +use crate::attribute::Validator; +use crate::types::CommaSeparatedNestedMetas; +use quote::quote; + +pub fn extract_generic_variant_custom_validator( + meta_list: &syn::MetaList, + _message_format: MessageFormat, +) -> Result { + let mut errors = vec![]; + + let nested = meta_list + .parse_args_with(CommaSeparatedNestedMetas::parse_terminated) + .map_err(|error| vec![crate::Error::rule_args_parse_error(meta_list, &error)])?; + + match nested.len() { + 0 => Err(vec![ + crate::Error::validate_custom_need_function_or_closure(meta_list), + ])?, + 2.. => nested + .iter() + .skip(1) + .for_each(|error| errors.push(crate::Error::rule_allow_single_function(error))), + _ => {} + } + + let rule = match &nested[0] { + crate::types::NestedMeta::Meta(syn::Meta::Path(path)) => { + extract_variant_custom_from_meta_path(path) + } + crate::types::NestedMeta::Meta(syn::Meta::List(list)) => { + extract_variant_custom_from_meta_list(list) + } + crate::types::NestedMeta::Closure(closure) => extract_variant_custom_from_closure(closure), + _ => Err(vec![ + crate::Error::validate_custom_need_function_or_closure(&nested[0]), + ]), + }; + + match rule { + Ok(_) => { + if errors.is_empty() { + rule + } else { + Err(errors) + } + } + Err(rule_errors) => Err(errors.into_iter().chain(rule_errors).collect()), + } +} + +fn extract_variant_custom_from_meta_path( + meta_path: &syn::Path, +) -> Result { + let rule_fn_name = &meta_path; + + Ok(quote!( + if let Err(__error) = #rule_fn_name(self) { + __rule_vec_errors.push(__error); + }; + )) +} + +fn extract_variant_custom_from_meta_list( + meta_list: &syn::MetaList, +) -> Result { + Ok(quote!( + if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #meta_list) { + __rule_vec_errors.push(__error); + }; + )) +} + +fn extract_variant_custom_from_closure( + closure: &syn::ExprClosure, +) -> Result { + Ok(quote!( + if let Err(__error) = serde_valid::helpers::wrap_closure_validation(self, #closure) { + __rule_vec_errors.push(__error); + }; + )) +} diff --git a/serde_valid_derive/src/attribute/variant_validate/meta.rs b/serde_valid_derive/src/attribute/variant_validate/meta.rs new file mode 100644 index 0000000..972b326 --- /dev/null +++ b/serde_valid_derive/src/attribute/variant_validate/meta.rs @@ -0,0 +1,141 @@ +mod meta_list; +mod meta_name_value; +mod meta_path; + +use crate::{ + attribute::{ + common::message_format::{default_message_format, extract_custom_message_format}, + MetaListStructValidation, MetaNameValueStructValidation, MetaPathStructValidation, + Validator, + }, + types::SingleIdentPath, +}; +use quote::quote; +use std::str::FromStr; + +use self::{ + meta_list::extract_variant_validator_from_meta_list, + meta_name_value::extract_variant_validator_from_meta_name_value, + meta_path::extract_variant_validator_from_meta_path, +}; + +pub fn extract_variant_validator(attribute: &syn::Attribute) -> Result { + match &attribute.meta { + syn::Meta::Path(_) => Ok(quote!()), + syn::Meta::List(list) => inner_extract_variant_validator(attribute, list), + syn::Meta::NameValue(name_value) => { + Err(vec![crate::Error::validate_meta_name_value_not_supported( + name_value, + )]) + } + } +} + +fn inner_extract_variant_validator( + attribute: &syn::Attribute, + meta_list: &syn::MetaList, +) -> Result { + let mut errors = vec![]; + let nested = meta_list + .parse_args_with(crate::types::CommaSeparatedMetas::parse_terminated) + .map_err(|error| { + vec![crate::Error::validate_attribute_parse_error( + attribute, &error, + )] + })?; + + let message_format = match nested.len() { + 0 => Err(vec![crate::Error::struct_validation_type_required( + attribute, + )])?, + 1 => None, + 2 => match extract_custom_message_format(&nested[1]) { + Ok(custom_message) => { + if nested[0].path().is_ident("custom") { + errors.push( + crate::Error::validate_custom_does_not_support_custom_message(&nested[1]), + ); + None + } else { + Some(custom_message) + } + } + Err(message_fn_errors) => { + errors.extend(message_fn_errors); + None + } + }, + _ => { + for meta in nested.iter().skip(2) { + errors.push(crate::Error::too_many_list_items(meta)); + } + None + } + } + .unwrap_or_else(default_message_format); + + let meta = &nested[0]; + let validation_path = match meta { + syn::Meta::Path(path) => path, + syn::Meta::List(list) => &list.path, + syn::Meta::NameValue(name_value) => &name_value.path, + }; + + let validation_name = SingleIdentPath::new(validation_path).ident().to_string(); + let validator = match ( + MetaPathStructValidation::from_str(&validation_name), + MetaListStructValidation::from_str(&validation_name), + MetaNameValueStructValidation::from_str(&validation_name), + meta, + ) { + (Ok(validation_type), _, _, syn::Meta::Path(validation)) => { + extract_variant_validator_from_meta_path(validation_type, validation, message_format) + } + + (_, Ok(validation_type), _, syn::Meta::List(validation)) => { + extract_variant_validator_from_meta_list(validation_type, validation, message_format) + } + + (_, _, Ok(validation_type), syn::Meta::NameValue(validation)) => { + extract_variant_validator_from_meta_name_value( + validation_type, + validation, + message_format, + ) + } + + (Ok(_), _, _, _) => Err(vec![crate::Error::meta_path_validation_need_value( + validation_path, + &validation_name, + )]), + + (_, Ok(_), _, _) => Err(vec![crate::Error::meta_list_validation_need_value( + validation_path, + &validation_name, + )]), + + (_, _, Ok(_), _) => Err(vec![crate::Error::meta_name_value_validation_need_value( + validation_path, + &validation_name, + )]), + + _ => Err(vec![crate::Error::struct_validation_type_unknown( + validation_path, + &validation_name, + )]), + }; + + match validator { + Ok(validator) => { + if errors.is_empty() { + Ok(validator) + } else { + Err(errors) + } + } + Err(validator_errors) => { + errors.extend(validator_errors); + Err(errors) + } + } +} diff --git a/serde_valid_derive/src/attribute/variant_validate/meta/meta_list.rs b/serde_valid_derive/src/attribute/variant_validate/meta/meta_list.rs new file mode 100644 index 0000000..6608771 --- /dev/null +++ b/serde_valid_derive/src/attribute/variant_validate/meta/meta_list.rs @@ -0,0 +1,17 @@ +use crate::attribute::{ + common::message_format::MessageFormat, + variant_validate::generic::extract_generic_variant_custom_validator, MetaListStructValidation, + Validator, +}; + +pub fn extract_variant_validator_from_meta_list( + validation_type: MetaListStructValidation, + validation: &syn::MetaList, + message_format: MessageFormat, +) -> Result { + match validation_type { + MetaListStructValidation::Custom => { + extract_generic_variant_custom_validator(validation, message_format) + } + } +} diff --git a/serde_valid_derive/src/attribute/variant_validate/meta/meta_name_value.rs b/serde_valid_derive/src/attribute/variant_validate/meta/meta_name_value.rs new file mode 100644 index 0000000..3b52bd3 --- /dev/null +++ b/serde_valid_derive/src/attribute/variant_validate/meta/meta_name_value.rs @@ -0,0 +1,12 @@ +use crate::attribute::{ + common::message_format::MessageFormat, MetaNameValueStructValidation, Validator, +}; + +#[inline] +pub fn extract_variant_validator_from_meta_name_value( + validation_type: MetaNameValueStructValidation, + _validation: &syn::MetaNameValue, + _message_format: MessageFormat, +) -> Result { + match validation_type {} +} diff --git a/serde_valid_derive/src/attribute/variant_validate/meta/meta_path.rs b/serde_valid_derive/src/attribute/variant_validate/meta/meta_path.rs new file mode 100644 index 0000000..bec8f8c --- /dev/null +++ b/serde_valid_derive/src/attribute/variant_validate/meta/meta_path.rs @@ -0,0 +1,12 @@ +use crate::attribute::{ + common::message_format::MessageFormat, MetaPathStructValidation, Validator, +}; + +#[inline] +pub fn extract_variant_validator_from_meta_path( + validation_type: MetaPathStructValidation, + _validation: &syn::Path, + _message_format: MessageFormat, +) -> Result { + match validation_type {} +} diff --git a/serde_valid_derive/src/derive/enum_derive.rs b/serde_valid_derive/src/derive/enum_derive.rs index c25a0c8..8d1df1c 100644 --- a/serde_valid_derive/src/derive/enum_derive.rs +++ b/serde_valid_derive/src/derive/enum_derive.rs @@ -1,14 +1,15 @@ use super::named_struct_derive::collect_named_fields_validators_list; use super::unnamed_struct_derive::collect_unnamed_fields_validators_list; use crate::attribute::rule::{collect_rules_from_named_struct, collect_rules_from_unnamed_struct}; +use crate::attribute::variant_validate::collect_variant_custom_from_named_variant; use crate::error::{array_errors_tokens, new_type_errors_tokens, object_errors_tokens}; +use crate::output_stream::OutputStream; use crate::serde::rename::collect_serde_rename_map; use crate::types::CommaSeparatedTokenStreams; use proc_macro2::TokenStream; use quote::quote; use std::collections::HashSet; use std::iter::FromIterator; - pub type Variants = syn::punctuated::Punctuated; pub fn expand_enum_validate_derive( @@ -20,14 +21,20 @@ pub fn expand_enum_validate_derive( let mut errors = vec![]; - let validations_and_rules = - TokenStream::from_iter(variants.iter().map(|variant| match &variant.fields { + let validations = variants + .into_iter() + .map(|variant| match &variant.fields { syn::Fields::Named(named_fields) => { - match expand_enum_variant_named_fields(ident, variant, named_fields) { + match expand_enum_variant_named_fields_validation( + ident, + input, + variant, + named_fields, + ) { Ok(variant_varidates_and_rules) => variant_varidates_and_rules, Err(variant_errors) => { errors.extend(variant_errors); - quote!() + OutputStream::new() } } } @@ -37,17 +44,32 @@ pub fn expand_enum_validate_derive( Ok(variant_varidates_and_rules) => variant_varidates_and_rules, Err(variant_errors) => { errors.extend(variant_errors); - quote!() + OutputStream::new() } } } - syn::Fields::Unit => quote!(), - })); + syn::Fields::Unit => OutputStream::new(), + }) + .collect::>(); + + let validations_and_rules = TokenStream::from_iter( + validations + .iter() + .map(|variant| variant.output.clone()) + .collect::>(), + ); + let warnings = validations + .into_iter() + .flat_map(|variant| variant.warnings) + .enumerate() + .map(|(index, warning)| warning.add_index(index)) + .collect::>(); if errors.is_empty() { Ok(quote!( impl #impl_generics ::serde_valid::Validate for #ident #type_generics #where_clause { fn validate(&self) -> std::result::Result<(), ::serde_valid::validation::Errors> { + #( #warnings )* #validations_and_rules Ok(()) @@ -59,22 +81,37 @@ pub fn expand_enum_validate_derive( } } -fn expand_enum_variant_named_fields( +fn expand_enum_variant_named_fields_validation( ident: &syn::Ident, + input: &syn::DeriveInput, variant: &syn::Variant, named_fields: &syn::FieldsNamed, -) -> Result { +) -> Result { let mut errors = vec![]; let variant_ident = &variant.ident; let mut fields_idents = CommaSeparatedTokenStreams::new(); let rename_map = collect_serde_rename_map(named_fields); - let (rule_fields, rules) = match collect_rules_from_named_struct(&variant.attrs) { + let ( + rule_fields, + OutputStream { + output: rules, + warnings, + }, + ) = match collect_rules_from_named_struct(&variant.ident, &variant.attrs) { Ok(field_rules) => field_rules, Err(variant_errors) => { errors.extend(variant_errors); - (HashSet::new(), quote!()) + (HashSet::new(), OutputStream::new()) + } + }; + + let enum_validates = match collect_variant_custom_from_named_variant(&input.attrs) { + Ok(validations) => TokenStream::from_iter(validations), + Err(rule_errors) => { + errors.extend(rule_errors); + quote!() } }; @@ -105,19 +142,23 @@ fn expand_enum_variant_named_fields( let variant_errors = object_errors_tokens(); if errors.is_empty() { - Ok(quote!( - if let #ident::#variant_ident{#fields_idents} = &self { - let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new(); - let mut __property_vec_errors_map = ::serde_valid::validation::PropertyVecErrorsMap::new(); - - #validates - #rules - - if !(__rule_vec_errors.is_empty() && __property_vec_errors_map.is_empty()) { - Err(#variant_errors)? + Ok(OutputStream { + output: quote!( + if let #ident::#variant_ident{#fields_idents} = &self { + let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new(); + let mut __property_vec_errors_map = ::serde_valid::validation::PropertyVecErrorsMap::new(); + + #enum_validates + #validates + #rules + + if !(__rule_vec_errors.is_empty() && __property_vec_errors_map.is_empty()) { + Err(#variant_errors)? + } } - } - )) + ), + warnings, + }) } else { Err(errors) } @@ -127,17 +168,23 @@ fn expand_enum_variant_unnamed_fields_varidation( ident: &syn::Ident, variant: &syn::Variant, unnamed_fields: &syn::FieldsUnnamed, -) -> Result { +) -> Result { let mut errors = vec![]; let variant_ident = &variant.ident; let mut fields_idents = CommaSeparatedTokenStreams::new(); - let (rule_fields, rules) = match collect_rules_from_unnamed_struct(&variant.attrs) { + let ( + rule_fields, + OutputStream { + output: rules, + warnings, + }, + ) = match collect_rules_from_unnamed_struct(&variant.ident, &variant.attrs) { Ok(field_rules) => field_rules, Err(variant_errors) => { errors.extend(variant_errors); - (HashSet::new(), quote!()) + (HashSet::new(), OutputStream::new()) } }; @@ -172,19 +219,22 @@ fn expand_enum_variant_unnamed_fields_varidation( }; if errors.is_empty() { - Ok(quote!( - if let #ident::#variant_ident(#fields_idents) = &self { - let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new(); - let mut __item_vec_errors_map = ::serde_valid::validation::ItemVecErrorsMap::new(); + Ok(OutputStream { + output: quote!( + if let #ident::#variant_ident(#fields_idents) = &self { + let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new(); + let mut __item_vec_errors_map = ::serde_valid::validation::ItemVecErrorsMap::new(); - #validates - #rules + #validates + #rules - if !(__rule_vec_errors.is_empty() && __item_vec_errors_map.is_empty()) { - Err(#variant_errors)? + if !(__rule_vec_errors.is_empty() && __item_vec_errors_map.is_empty()) { + Err(#variant_errors)? + } } - } - )) + ), + warnings, + }) } else { Err(errors) } diff --git a/serde_valid_derive/src/derive/named_struct_derive.rs b/serde_valid_derive/src/derive/named_struct_derive.rs index 616a5d2..ded49ce 100644 --- a/serde_valid_derive/src/derive/named_struct_derive.rs +++ b/serde_valid_derive/src/derive/named_struct_derive.rs @@ -2,6 +2,7 @@ use crate::attribute::field_validate::{extract_field_validator, FieldValidators} use crate::attribute::rule::collect_rules_from_named_struct; use crate::attribute::struct_validate::collect_struct_custom_from_named_struct; use crate::error::object_errors_tokens; +use crate::output_stream::OutputStream; use crate::serde::rename::{collect_serde_rename_map, RenameMap}; use crate::types::{Field, NamedField}; use proc_macro2::TokenStream; @@ -20,11 +21,17 @@ pub fn expand_named_struct_derive( let mut errors = vec![]; - let (rule_fields, rules) = match collect_rules_from_named_struct(&input.attrs) { - Ok((rule_fields, rules)) => (rule_fields, TokenStream::from_iter(rules)), + let ( + rule_fields, + OutputStream { + output: rules, + warnings, + }, + ) = match collect_rules_from_named_struct(&input.ident, &input.attrs) { + Ok((rule_fields, rules)) => (rule_fields, rules), Err(rule_errors) => { errors.extend(rule_errors); - (HashSet::new(), quote!()) + (HashSet::new(), OutputStream::new()) } }; let struct_validations = match collect_struct_custom_from_named_struct(&input.attrs) { @@ -51,10 +58,17 @@ pub fn expand_named_struct_derive( let fields_errors = object_errors_tokens(); + let warnings = warnings + .into_iter() + .enumerate() + .map(|(index, warning)| warning.add_index(index)) + .collect::>(); + if errors.is_empty() { Ok(quote!( impl #impl_generics ::serde_valid::Validate for #ident #type_generics #where_clause { fn validate(&self) -> std::result::Result<(), ::serde_valid::validation::Errors> { + #(#warnings)* let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new(); let mut __property_vec_errors_map = ::serde_valid::validation::PropertyVecErrorsMap::new(); diff --git a/serde_valid_derive/src/derive/unnamed_struct_derive.rs b/serde_valid_derive/src/derive/unnamed_struct_derive.rs index a07423f..f0e0bc6 100644 --- a/serde_valid_derive/src/derive/unnamed_struct_derive.rs +++ b/serde_valid_derive/src/derive/unnamed_struct_derive.rs @@ -2,6 +2,7 @@ use crate::attribute::field_validate::{extract_field_validator, FieldValidators} use crate::attribute::rule::collect_rules_from_unnamed_struct; use crate::attribute::struct_validate::collect_struct_custom_from_named_struct; use crate::error::{array_errors_tokens, new_type_errors_tokens}; +use crate::output_stream::OutputStream; use crate::types::{Field, UnnamedField}; use proc_macro2::TokenStream; use quote::quote; @@ -18,11 +19,17 @@ pub fn expand_unnamed_struct_derive( let mut errors = vec![]; - let (rule_fields, rules) = match collect_rules_from_unnamed_struct(&input.attrs) { - Ok((rule_fields, rules)) => (rule_fields, TokenStream::from_iter(rules)), + let ( + rule_fields, + OutputStream { + output: rules, + warnings, + }, + ) = match collect_rules_from_unnamed_struct(&input.ident, &input.attrs) { + Ok((rule_fields, rules)) => (rule_fields, rules), Err(rule_errors) => { errors.extend(rule_errors); - (HashSet::new(), quote!()) + (HashSet::new(), OutputStream::new()) } }; @@ -54,8 +61,15 @@ pub fn expand_unnamed_struct_derive( new_type_errors_tokens() }; + let warnings = warnings + .into_iter() + .enumerate() + .map(|(index, warning)| warning.add_index(index)) + .collect::>(); + if errors.is_empty() { Ok(quote!( + #(#warnings)* impl #impl_generics ::serde_valid::Validate for #ident #type_generics #where_clause { fn validate(&self) -> std::result::Result<(), ::serde_valid::validation::Errors> { let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new(); diff --git a/serde_valid_derive/src/lib.rs b/serde_valid_derive/src/lib.rs index eaf8dbe..69c1180 100644 --- a/serde_valid_derive/src/lib.rs +++ b/serde_valid_derive/src/lib.rs @@ -2,8 +2,10 @@ mod attribute; mod derive; mod error; +mod output_stream; mod serde; mod types; +mod warning; use derive::expand_derive; use error::to_compile_errors; diff --git a/serde_valid_derive/src/output_stream.rs b/serde_valid_derive/src/output_stream.rs new file mode 100644 index 0000000..478c23c --- /dev/null +++ b/serde_valid_derive/src/output_stream.rs @@ -0,0 +1,23 @@ +use crate::warning::Warning; +use proc_macro2::TokenStream; + +#[derive(Debug, Clone)] +pub struct OutputStream { + pub output: TokenStream, + pub warnings: Vec, +} + +impl OutputStream { + pub fn new() -> Self { + Self { + output: TokenStream::new(), + warnings: vec![], + } + } + + #[allow(unused)] + pub fn extend_warnings(&mut self, warnings: Vec) -> &mut Self { + self.warnings.extend(warnings); + self + } +} diff --git a/serde_valid_derive/src/warning.rs b/serde_valid_derive/src/warning.rs new file mode 100644 index 0000000..cb22d3d --- /dev/null +++ b/serde_valid_derive/src/warning.rs @@ -0,0 +1,92 @@ +use std::{hash::Hash, str::FromStr}; + +use proc_macro2::Span; +use proc_macro2::TokenStream; +use quote::{quote_spanned, ToTokens}; + +#[derive(Debug, Clone)] +pub enum Warning { + Deprecated { + ident: syn::Ident, + note: String, + span: Span, + }, +} + +impl Hash for Warning { + fn hash(&self, state: &mut H) { + match self { + Self::Deprecated { ident, note, .. } => { + ident.hash(state); + note.hash(state); + } + } + } +} + +impl std::cmp::PartialEq for Warning { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Self::Deprecated { + ident: ident1, + note: note1, + .. + }, + Self::Deprecated { + ident: ident2, + note: note2, + .. + }, + ) => ident1 == ident2 && note1 == note2, + } + } +} + +impl std::cmp::Eq for Warning {} + +impl Warning { + pub fn new_rule_deprecated(ident: &syn::Ident, span: Span) -> Self { + Self::Deprecated { + ident: ident.clone(), + note: "#[rule(...)] is deprecated, use #[validate(custom(...)))] instead".to_string(), + span, + } + } + + pub fn add_index(&self, index: usize) -> Self { + match self { + Self::Deprecated { ident, note, span } => Self::Deprecated { + ident: syn::Ident::new(&format!("{}_{}", ident, index), ident.span()), + note: note.clone(), + span: *span, + }, + } + } +} + +impl ToTokens for Warning { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + Self::Deprecated { ident, note, span } => { + let func_name = TokenStream::from_str(&format!( + "__{}_warning", + ident.to_string().to_lowercase() + )) + .unwrap(); + + quote_spanned!(*span => + #[deprecated(note = #note)] + #[allow(clippy::let_unit_value)] + fn #func_name() { + #[deprecated(note = #note)] + #[allow(non_upper_case_globals)] + const _deprecated: () = (); + let _ = _deprecated; + } + ) + .to_tokens(tokens) + } + } + } +}