Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion validator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,4 @@ pub use traits::{Contains, HasLen, Validate, ValidateArgs};
pub use types::{ValidationError, ValidationErrors, ValidationErrorsKind};

#[cfg(feature = "derive")]
pub use validator_derive::Validate;
pub use validator_derive::Validate;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undesirable whitespace change

239 changes: 143 additions & 96 deletions validator_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ pub fn derive_validation(input: proc_macro::TokenStream) -> proc_macro::TokenStr

fn impl_validate(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
// Collecting the validators
let mut fields_validations = collect_field_validations(ast);
let mut struct_validations = find_struct_validations(&ast.attrs);
let (mut struct_validations, nest_all_fields) = collect_struct_validations(&ast.attrs);
let mut fields_validations = collect_field_validations(ast, nest_all_fields);
let (arg_type, has_arg) =
construct_validator_argument_type(&mut fields_validations, &mut struct_validations);
let (validations, nested_validations) = quote_field_validations(fields_validations);
Expand Down Expand Up @@ -120,13 +120,16 @@ fn collect_fields(ast: &syn::DeriveInput) -> Vec<syn::Field> {
}
}

fn collect_field_validations(ast: &syn::DeriveInput) -> Vec<FieldInformation> {
fn collect_field_validations(ast: &syn::DeriveInput, nest_all_fields: bool) -> Vec<FieldInformation> {
let mut fields = collect_fields(ast);

let field_types = find_fields_type(&fields);
fields.drain(..).fold(vec![], |mut acc, field| {
let key = field.ident.clone().unwrap().to_string();
let (name, validations) = find_validators_for_field(&field, &field_types);
let (name, mut validations) = find_validators_for_field(&field, &field_types);
if nest_all_fields && validations.len() == 0 {
validations.push(FieldValidation::new(Validator::Nested));
}
acc.push(FieldInformation::new(
field,
field_types.get(&key).unwrap().clone(),
Expand Down Expand Up @@ -208,112 +211,153 @@ fn quote_field_validations(
}

/// Find if a struct has some schema validation and returns the info if so
fn find_struct_validation(attr: &syn::Attribute) -> SchemaValidation {
fn find_schema_validation(path: &syn::Path, nested: &syn::punctuated::Punctuated<syn::NestedMeta, syn::token::Comma>) -> SchemaValidation {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a huge change but actually its just renaming the function and pulling the first 2 lines of the if_chain into the calling function.

let error = |span: Span, msg: &str| -> ! {
abort!(span, "Invalid schema level validation: {}", msg);
};

if_chain! {
if let Ok(syn::Meta::List(syn::MetaList { ref nested, .. })) = attr.parse_meta();
if let syn::NestedMeta::Meta(syn::Meta::List(syn::MetaList { ref path, ref nested, .. })) = nested[0];

then {
Copy link
Author

@davidlang42 davidlang42 Aug 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything from here down (in this function) is just whitespace (no code change)

let ident = path.get_ident().unwrap();
if ident != "schema" {
error(attr.span(), "Only `schema` is allowed as validator on a struct")
}
let ident = path.get_ident().unwrap();
if ident != "schema" {
error(path.span(), "Only `schema` is allowed as validator on a struct")
}

let mut function = String::new();
let mut skip_on_field_errors = true;
let mut code = None;
let mut message = None;
let mut args = None;

for arg in nested {
if_chain! {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. })) = *arg;

then {
let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"function" => {
function = match lit_to_string(lit) {
Some(s) => s,
None => error(lit.span(), "invalid argument type for `function` \
: only a string is allowed"),
};
},
"skip_on_field_errors" => {
skip_on_field_errors = match lit_to_bool(lit) {
Some(s) => s,
None => error(lit.span(), "invalid argument type for `skip_on_field_errors` \
: only a bool is allowed"),
};
},
"code" => {
code = match lit_to_string(lit) {
Some(s) => Some(s),
None => error(lit.span(), "invalid argument type for `code` \
: only a string is allowed"),
};
},
"message" => {
message = match lit_to_string(lit) {
Some(s) => Some(s),
None => error(lit.span(), "invalid argument type for `message` \
: only a string is allowed"),
};
},
"arg" => {
match lit_to_string(lit) {
Some(s) => {
match syn::parse_str::<syn::Type>(s.as_str()) {
Ok(arg_type) => {
assert_custom_arg_type(&lit.span(), &arg_type);
args = Some(CustomArgument::new(lit.span(), arg_type));
}
Err(_) => {
let mut msg = "invalid argument type for `arg` of `schema` validator: The string has to be a single type.".to_string();
msg.push_str("\n(Tip: You can combine multiple types into one tuple.)");
error(lit.span(), msg.as_str());
}
}
let mut function = String::new();
let mut skip_on_field_errors = true;
let mut code = None;
let mut message = None;
let mut args = None;

for arg in nested {
if_chain! {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { ref path, ref lit, .. })) = *arg;

then {
let ident = path.get_ident().unwrap();
match ident.to_string().as_ref() {
"function" => {
function = match lit_to_string(lit) {
Some(s) => s,
None => error(lit.span(), "invalid argument type for `function` \
: only a string is allowed"),
};
},
"skip_on_field_errors" => {
skip_on_field_errors = match lit_to_bool(lit) {
Some(s) => s,
None => error(lit.span(), "invalid argument type for `skip_on_field_errors` \
: only a bool is allowed"),
};
},
"code" => {
code = match lit_to_string(lit) {
Some(s) => Some(s),
None => error(lit.span(), "invalid argument type for `code` \
: only a string is allowed"),
};
},
"message" => {
message = match lit_to_string(lit) {
Some(s) => Some(s),
None => error(lit.span(), "invalid argument type for `message` \
: only a string is allowed"),
};
},
"arg" => {
match lit_to_string(lit) {
Some(s) => {
match syn::parse_str::<syn::Type>(s.as_str()) {
Ok(arg_type) => {
assert_custom_arg_type(&lit.span(), &arg_type);
args = Some(CustomArgument::new(lit.span(), arg_type));
}
None => error(lit.span(), "invalid argument type for `arg` of `custom` validator: expected a string")
};
},
_ => error(lit.span(), "Unknown argument")
}
} else {
error(arg.span(), "Unexpected args")
}
Err(_) => {
let mut msg = "invalid argument type for `arg` of `schema` validator: The string has to be a single type.".to_string();
msg.push_str("\n(Tip: You can combine multiple types into one tuple.)");
error(lit.span(), msg.as_str());
}
}
}
None => error(lit.span(), "invalid argument type for `arg` of `custom` validator: expected a string")
};
},
_ => error(lit.span(), "Unknown argument")
}
} else {
error(arg.span(), "Unexpected args")
}
}
}

if function.is_empty() {
error(path.span(), "`function` is required");
}
if function.is_empty() {
error(path.span(), "`function` is required");
}

SchemaValidation {
function,
args,
skip_on_field_errors,
code,
message,
}
} else {
error(attr.span(), "Unexpected struct validator")
}
SchemaValidation {
function,
args,
skip_on_field_errors,
code,
message,
}
}

/// Finds all struct schema validations
fn find_struct_validations(struct_attrs: &[syn::Attribute]) -> Vec<SchemaValidation> {
struct_attrs
.iter()
.filter(|attribute| attribute.path == parse_quote!(validate))
.map(find_struct_validation)
.collect()
fn collect_struct_validations(struct_attrs: &[syn::Attribute]) -> (Vec<SchemaValidation>, bool) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual change, most code is just for handling the many cases of meta types (similarly to collect_field_validations)

let mut nest_all_fields = false;
let mut validators = Vec::new();

for attr in struct_attrs {
if attr.path != parse_quote!(validate) {
continue;
}

match attr.parse_meta() {
Ok(syn::Meta::List(syn::MetaList { ref nested, .. })) => {
let meta_items = nested.iter().collect::<Vec<_>>();
for meta_item in meta_items {
match *meta_item {
syn::NestedMeta::Meta(ref item) => match *item {
syn::Meta::Path(ref name) => {
match name.get_ident().unwrap().to_string().as_ref() {
"nest_all_fields" => nest_all_fields = true,
_ => {
let mut ident = proc_macro2::TokenStream::new();
name.to_tokens(&mut ident);
abort!(name.span(), "Unexpected validator: {}", ident)
}
}
}
syn::Meta::List(syn::MetaList { ref path, ref nested, .. }) => {
validators.push(find_schema_validation(path, nested))
}
syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) => {
abort!(path.span(), "unexpected name value validator: {:?}", path.get_ident())
}
},
_ => unreachable!("Found a non Meta while looking for validators"),
};
}
}
Ok(syn::Meta::Path(_)) => abort!(attr.span(), "Unexpected attribute with arguments"),
Ok(syn::Meta::NameValue(_)) => abort!(attr.span(), "Unexpected name=value argument"),
Err(e) => {
let error_string = format!("{:?}", e);
if error_string == "Error(\"expected literal\")" {
abort!(attr.span(),
"This attributes for this struct seem to be misformed, please validate the syntax with the documentation"
);
} else {
abort!(
attr.span(),
"Unable to parse this attribute for this struct with the error: {:?}",
e
);
}
}
}
}

(validators, nest_all_fields)
}

/// Find the types (as string) for each field of the struct
Expand Down Expand Up @@ -444,6 +488,9 @@ fn find_validators_for_field(
validators.push(FieldValidation::new(Validator::Required));
validators.push(FieldValidation::new(Validator::Nested));
}
"always_valid" => {
validators.push(FieldValidation::new(Validator::AlwaysValid));
}
_ => {
let mut ident = proc_macro2::TokenStream::new();
name.to_tokens(&mut ident);
Expand Down
1 change: 1 addition & 0 deletions validator_derive/src/quoting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ pub fn quote_validator(
Validator::DoesNotContain(_) => {
validations.push(quote_does_not_contain_validation(field_quoter, validation))
}
Validator::AlwaysValid => { }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error[E0599]: no method named `validate` found for struct `Test` in the current
--> $DIR/validate_not_impl_with_args.rs:15:10
|
8 | struct Test {
| ----------- method `validate` not found for this
| ----------- method `validate` not found for this struct
...
15 | test.validate();
| ^^^^^^^^ method not found in `Test`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use validator::Validate;

#[derive(Validate)]
#[validate(nest_all_fields)]
struct Test {
nested: Nested,
}

struct Nested {
value: String,
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error[E0599]: no method named `validate` found for struct `Nested` in the current scope
--> tests/compile-fail/nest_all_fields/no_nested_validations.rs:3:10
|
3 | #[derive(Validate)]
| ^^^^^^^^ method not found in `Nested`
...
9 | struct Nested {
| ------------- method `validate` not found for this struct
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `validate`, perhaps you need to implement it:
candidate #1: `Validate`
= note: this error originates in the derive macro `Validate` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ error[E0599]: no method named `validate` found for struct `Nested` in the curren
| ^^^^^^^^ method not found in `Nested`
...
9 | struct Nested {
| ------------- method `validate` not found for this
| ------------- method `validate` not found for this struct
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `validate`, perhaps you need to implement it:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use validator::Validate;

#[derive(Validate)]
#[validate(nest_all_fields)]
struct Test {
#[validate(always_valid)]
nested: Nested,
}

struct Nested {
value: String,
}

fn main() {}
2 changes: 2 additions & 0 deletions validator_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub enum Validator {
Required,
RequiredNested,
DoesNotContain(String),
AlwaysValid
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -91,6 +92,7 @@ impl Validator {
Validator::Required => "required",
Validator::RequiredNested => "required_nested",
Validator::DoesNotContain(_) => "does_not_contain",
Validator::AlwaysValid => "always_valid"
}
}

Expand Down