Skip to content

Commit

Permalink
Merge pull request #28 from yassun7010/add_struct_custom_validator
Browse files Browse the repository at this point in the history
feat: add struct custom validator.
  • Loading branch information
yassun7010 authored Jan 8, 2024
2 parents 2947f05 + f06d7f5 commit 953a73b
Show file tree
Hide file tree
Showing 18 changed files with 569 additions and 199 deletions.
9 changes: 9 additions & 0 deletions serde_valid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,3 +593,12 @@ where
}

pub use serde_valid_derive::Validate;

pub mod helpers {
pub fn wrap_closure_validation<T>(
data: &T,
f: impl FnOnce(&T) -> Result<(), crate::validation::Error>,
) -> Result<(), crate::validation::Error> {
f(data)
}
}
82 changes: 82 additions & 0 deletions serde_valid/tests/struct_custom_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use serde_valid::Validate;

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

#[derive(Validate)]
#[validate(custom(sample_struct_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_closure_is_ok() {
fn sample_struct_validation(_val: i32) -> Result<(), 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 unnamed_struct_custom_is_ok() {
fn sample_struct_validation(_val: &TestStruct) -> Result<(), serde_valid::validation::Error> {
Ok(())
}

#[derive(Validate)]
#[validate(custom(sample_struct_validation))]
struct TestStruct(i32);

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

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

#[derive(Validate)]
#[validate(custom(|s| sample_struct_validation(s.0)))]
struct TestStruct(i32);

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

#[test]
fn unnamed_struct_custom_closure_is_err() {
fn sample_struct_validation(_val: i32) -> Result<(), serde_valid::validation::Error> {
Err(serde_valid::validation::Error::Custom(
"Struct Validation Error.".to_owned(),
))
}

#[derive(Validate)]
#[validate(custom(|s| sample_struct_validation(s.0)))]
struct TestStruct(i32);

let s = TestStruct(5);
assert_eq!(s.0, 5);
assert!(s.validate().is_err());
}
18 changes: 13 additions & 5 deletions serde_valid_derive/src/derive/named_struct_derive.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::error::object_errors_tokens;
use crate::rule::collect_rules_from_named_struct;
use crate::rule::{collect_rules_from_named_struct, collect_struct_custom_from_named_struct};
use crate::serde::rename::{collect_serde_rename_map, RenameMap};
use crate::types::{Field, NamedField};
use crate::validate::{extract_meta_validator, FieldValidators};
use crate::validate::{extract_field_validator, FieldValidators};
use proc_macro2::TokenStream;
use quote::quote;
use std::borrow::Cow;
Expand All @@ -26,8 +26,15 @@ pub fn expand_named_struct_derive(
(HashSet::new(), quote!())
}
};
let struct_validations = match collect_struct_custom_from_named_struct(&input.attrs) {
Ok(validations) => TokenStream::from_iter(validations),
Err(rule_errors) => {
errors.extend(rule_errors);
quote!()
}
};

let validates = match collect_named_fields_validators_list(fields, &rename_map) {
let field_validates = match collect_named_fields_validators_list(fields, &rename_map) {
Ok(field_validators) => TokenStream::from_iter(field_validators.iter().map(|validator| {
if validator.is_empty() && rule_fields.contains(validator.ident()) {
validator.get_field_variable_token()
Expand All @@ -50,7 +57,8 @@ pub fn expand_named_struct_derive(
let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new();
let mut __property_vec_errors_map = ::serde_valid::validation::PropertyVecErrorsMap::new();

#validates
#field_validates
#struct_validations
#rules

if __rule_vec_errors.is_empty() && __property_vec_errors_map.is_empty() {
Expand Down Expand Up @@ -105,7 +113,7 @@ fn collect_named_field_validators<'a>(
.iter()
.filter_map(|attribute| {
if attribute.path().is_ident("validate") {
match extract_meta_validator(&named_field, attribute, rename_map) {
match extract_field_validator(&named_field, attribute, rename_map) {
Ok(validator) => Some(validator),
Err(validator_error) => {
errors.extend(validator_error);
Expand Down
19 changes: 14 additions & 5 deletions serde_valid_derive/src/derive/unnamed_struct_derive.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::error::{array_errors_tokens, new_type_errors_tokens};
use crate::rule::collect_rules_from_unnamed_struct;
use crate::rule::{collect_rules_from_unnamed_struct, collect_struct_custom_from_named_struct};
use crate::types::{Field, UnnamedField};
use crate::validate::{extract_meta_validator, FieldValidators};
use crate::validate::{extract_field_validator, FieldValidators};
use proc_macro2::TokenStream;
use quote::quote;
use std::borrow::Cow;
Expand All @@ -25,7 +25,15 @@ pub fn expand_unnamed_struct_derive(
}
};

let validates = match collect_unnamed_fields_validators_list(fields) {
let struct_validations = match collect_struct_custom_from_named_struct(&input.attrs) {
Ok(validations) => TokenStream::from_iter(validations),
Err(rule_errors) => {
errors.extend(rule_errors);
quote!()
}
};

let field_validates = match collect_unnamed_fields_validators_list(fields) {
Ok(field_validators) => TokenStream::from_iter(field_validators.iter().map(|validator| {
if validator.is_empty() && rule_fields.contains(validator.ident()) {
validator.get_field_variable_token()
Expand All @@ -52,7 +60,8 @@ pub fn expand_unnamed_struct_derive(
let mut __rule_vec_errors = ::serde_valid::validation::VecErrors::new();
let mut __item_vec_errors_map = ::serde_valid::validation::ItemVecErrorsMap::new();

#validates
#field_validates
#struct_validations
#rules

if __rule_vec_errors.is_empty() && __item_vec_errors_map.is_empty() {
Expand Down Expand Up @@ -105,7 +114,7 @@ fn collect_unnamed_field_validators(
.iter()
.filter_map(|attribute| {
if attribute.path().is_ident("validate") {
match extract_meta_validator(&unnamed_field, attribute, &HashMap::new()) {
match extract_field_validator(&unnamed_field, attribute, &HashMap::new()) {
Ok(validator) => Some(validator),
Err(validator_errors) => {
errors.extend(validator_errors);
Expand Down
81 changes: 55 additions & 26 deletions serde_valid_derive/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::validate::{
MetaListValidation, MetaNameValueCustomMessage, MetaNameValueValidation, MetaPathCustomMessage,
MetaPathValidation,
MetaListFieldValidation, MetaListStructValidation, MetaNameValueCustomMessage,
MetaNameValueFieldValidation, MetaNameValueStructValidation, MetaPathCustomMessage,
MetaPathFieldValidation, MetaPathStructValidation,
};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
Expand Down Expand Up @@ -140,7 +141,12 @@ pub fn new_type_errors_tokens() -> TokenStream {
quote!(::serde_valid::validation::Errors::NewType(
__rule_vec_errors
.into_iter()
.chain(__item_vec_errors_map.remove(&0).unwrap().into_iter())
.chain(
__item_vec_errors_map
.remove(&0)
.unwrap_or(vec![])
.into_iter()
)
.collect()
))
}
Expand Down Expand Up @@ -181,9 +187,9 @@ impl Error {
Self::new(path.span(), "`rule` function needs arguments.")
}

pub fn rule_args_parse_error(metalist: &syn::MetaList, error: &syn::Error) -> Self {
pub fn rule_args_parse_error(meta_list: &syn::MetaList, error: &syn::Error) -> Self {
Self::new(
metalist.span(),
meta_list.span(),
format!("#[rule(???)] parse error: {error}"),
)
}
Expand Down Expand Up @@ -222,10 +228,7 @@ impl Error {
}

pub fn validate_meta_name_value_not_support(name_value: &syn::MetaNameValue) -> Self {
Self::new(
name_value.span(),
"#[validate = ???] format does not support.",
)
Self::new(name_value.span(), "#[validate = ???] does not support.")
}

pub fn meta_path_validation_need_value(path: &syn::Path, validation_type: &str) -> Self {
Expand Down Expand Up @@ -286,10 +289,37 @@ impl Error {
)
}

pub fn validate_type_required_error(attribute: &syn::Attribute) -> Self {
let filterd_candidates: Vec<&str> = (MetaPathValidation::iter().map(|x| x.name()))
.chain(MetaNameValueValidation::iter().map(|x| x.name()))
.chain(MetaListValidation::iter().map(|x| x.name()))
pub fn field_validation_type_required(attribute: &syn::Attribute) -> Self {
let filterd_candidates: Vec<&str> = (MetaPathFieldValidation::iter().map(|x| x.name()))
.chain(MetaListFieldValidation::iter().map(|x| x.name()))
.chain(MetaNameValueFieldValidation::iter().map(|x| x.name()))
.collect::<Vec<_>>();

Self::new(
attribute.meta.span(),
format!("#[validate(???)] needs validation type. Is it one of the following?\n{filterd_candidates:#?}"),
)
}

pub fn field_validation_type_unknown(path: &syn::Path, unknown: &str) -> Self {
let candidates = &(MetaPathFieldValidation::iter().map(|x| x.name()))
.chain(MetaListFieldValidation::iter().map(|x| x.name()))
.chain(MetaNameValueFieldValidation::iter().map(|x| x.name()))
.collect::<Vec<_>>();

let filterd_candidates =
did_you_mean(unknown, candidates).unwrap_or_else(|| candidates.to_vec());

Self::new(
path.span(),
format!("`{unknown}` is unknown validation type. Is it one of the following?\n{filterd_candidates:#?}"),
)
}

pub fn struct_validation_type_required(attribute: &syn::Attribute) -> Self {
let filterd_candidates: Vec<&str> = (MetaPathStructValidation::iter().map(|x| x.name()))
.chain(MetaListStructValidation::iter().map(|x| x.name()))
.chain(MetaNameValueStructValidation::iter().map(|x| x.name()))
.collect::<Vec<_>>();

Self::new(
Expand All @@ -298,10 +328,10 @@ impl Error {
)
}

pub fn unknown_validation_type(path: &syn::Path, unknown: &str) -> Self {
let candidates = &(MetaPathValidation::iter().map(|x| x.name()))
.chain(MetaListValidation::iter().map(|x| x.name()))
.chain(MetaNameValueValidation::iter().map(|x| x.name()))
pub fn struct_validation_type_unknown(path: &syn::Path, unknown: &str) -> Self {
let candidates = &(MetaPathStructValidation::iter().map(|x| x.name()))
.chain(MetaListStructValidation::iter().map(|x| x.name()))
.chain(MetaNameValueStructValidation::iter().map(|x| x.name()))
.collect::<Vec<_>>();

let filterd_candidates =
Expand All @@ -328,9 +358,9 @@ impl Error {
)
}

pub fn validate_enumerate_parse_error(metalist: &syn::MetaList, error: &syn::Error) -> Self {
pub fn validate_enumerate_parse_error(meta_list: &syn::MetaList, error: &syn::Error) -> Self {
Self::new(
metalist.span(),
meta_list.span(),
format!("#[validate(enumerate(???))] parse error: {error}"),
)
}
Expand All @@ -339,14 +369,17 @@ impl Error {
Self::new(path.span(), "#[validate(enumerate(???))] needs items.")
}

pub fn validate_custom_need_function(span: impl Spanned) -> Self {
Self::new(span.span(), "#[validate(custom(???))] needs function.")
pub fn validate_custom_need_function_or_closure(span: impl Spanned) -> Self {
Self::new(
span.span(),
"#[validate(custom(???))] needs function or closure.",
)
}

pub fn validate_custom_tail_error(nested: &crate::types::NestedMeta) -> Self {
Self::new(
nested.span(),
"#[validate(custom(???)] supports only 1 item.",
"#[validate(custom(???))] supports only 1 item.",
)
}

Expand Down Expand Up @@ -426,10 +459,6 @@ impl Error {
Self::new(lit.span(), "Allow str literal only.")
}

pub fn literal_not_support(lit: &syn::Lit) -> Self {
Self::new(lit.span(), "Literal not support.")
}

pub fn closure_not_support(closure: &syn::ExprClosure) -> Self {
Self::new(
closure.or1_token.span(),
Expand Down
2 changes: 2 additions & 0 deletions serde_valid_derive/src/rule.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod named_struct_rule;
mod struct_custom;
mod unnamed_struct_rule;

pub use named_struct_rule::collect_rules_from_named_struct;
pub use struct_custom::collect_struct_custom_from_named_struct;
pub use unnamed_struct_rule::collect_rules_from_unnamed_struct;
16 changes: 8 additions & 8 deletions serde_valid_derive/src/rule/named_struct_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@ pub fn collect_rules_from_named_struct(
}

fn collect_rule(
metalist: &syn::MetaList,
meta_list: &syn::MetaList,
) -> Result<(HashSet<syn::Ident>, TokenStream), crate::Errors> {
let mut errors = vec![];

let nested = metalist
let nested = meta_list
.parse_args_with(CommaSeparatedNestedMetas::parse_terminated)
.map_err(|error| vec![crate::Error::rule_args_parse_error(metalist, &error)])?;
.map_err(|error| vec![crate::Error::rule_args_parse_error(meta_list, &error)])?;

match nested.len() {
0 => Err(vec![crate::Error::rule_allow_function_call_or_closure(
metalist,
meta_list,
)])?,
2.. => nested
.iter()
Expand Down Expand Up @@ -83,14 +83,14 @@ fn collect_rule(
}

fn extract_rule_from_meta_list(
metalist: &syn::MetaList,
meta_list: &syn::MetaList,
) -> Result<(HashSet<syn::Ident>, TokenStream), crate::Errors> {
let mut errors = vec![];

let rule_fn_name = &metalist.path;
let nested = metalist
let rule_fn_name = &meta_list.path;
let nested = meta_list
.parse_args_with(crate::types::CommaSeparatedNestedMetas::parse_terminated)
.map_err(|error| vec![crate::Error::rule_args_parse_error(metalist, &error)])?;
.map_err(|error| vec![crate::Error::rule_args_parse_error(meta_list, &error)])?;

if nested.is_empty() {
errors.push(crate::Error::rule_need_arguments(rule_fn_name));
Expand Down
Loading

0 comments on commit 953a73b

Please sign in to comment.