Skip to content
Merged
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
1 change: 1 addition & 0 deletions packages/fortifier-macros/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod r#enum;
mod field;
mod fields;
mod r#struct;
mod r#type;
mod r#union;

use proc_macro2::TokenStream;
Expand Down
2 changes: 1 addition & 1 deletion packages/fortifier-macros/src/validate/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use proc_macro2::TokenStream;
use quote::quote;

pub fn enum_attributes() -> TokenStream {
#[allow(unused_mut)]
let mut attributes: Vec<TokenStream> = vec![];

#[cfg(feature = "serde")]
Expand All @@ -12,7 +13,6 @@ pub fn enum_attributes() -> TokenStream {
attributes.push(quote! {
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(
// TODO: Tag?
tag = "path",
rename_all = "camelCase",
rename_all_fields = "camelCase"
Expand Down
11 changes: 10 additions & 1 deletion packages/fortifier-macros/src/validate/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use quote::{ToTokens, format_ident, quote};
use syn::{Field, Ident, Result, Visibility};

use crate::{
validate::attributes::enum_attributes,
validate::{attributes::enum_attributes, r#type::should_validate_type},
validation::{Execution, Validation},
validations::{Custom, Email, Length, Regex, Url},
};
Expand Down Expand Up @@ -60,6 +60,7 @@ impl<'a> ValidateField<'a> {
error_type_ident,
validations: vec![],
};
let mut skip = false;

for attr in &field.attrs {
if attr.path().is_ident("validate") {
Expand All @@ -83,6 +84,10 @@ impl<'a> ValidateField<'a> {
} else if meta.path.is_ident("url") {
result.validations.push(Box::new(Url::parse(&meta)?));

Ok(())
} else if meta.path.is_ident("skip") {
skip = true;

Ok(())
} else {
Err(meta.error("unknown parameter"))
Expand All @@ -91,6 +96,10 @@ impl<'a> ValidateField<'a> {
}
}

if !skip && should_validate_type(&field.ty) {
// TODO: Nested validation
}

Ok(result)
}

Expand Down
191 changes: 191 additions & 0 deletions packages/fortifier-macros/src/validate/type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use syn::{GenericArgument, Path, PathArguments, Type, TypeParamBound};

const PRIMITIVE_AND_BUILT_IN_TYPES: [&str; 18] = [
"bool", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
"f32", "f64", "char", "str", "String",
];

const CONTAINER_TYPES: [&str; 20] = [
"Arc",
"BTreeMap",
"BTreeSet",
"HashMap",
"HashSet",
"LinkedList",
"Option",
"Rc",
"Vec",
"VecDeque",
"std::collections::BTreeMap",
"std::collections::BTreeSet",
"std::collections::HashMap",
"std::collections::HashSet",
"std::collections::LinkedList",
"std::collections::VecDeque",
"std::option::Option",
"std::rc::Rc",
"std::sync::Arc",
"std::vec::Vec",
];

fn path_to_string(path: &Path) -> String {
// TODO: This is probably slow, replace with comparisons.
path.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}

fn is_validate_path(path: &Path) -> bool {
let path_string = path_to_string(path);
path_string == "Validate" || path_string == "fortifier::Validate"
}

fn should_validate_generic_argument(arg: &GenericArgument) -> bool {
match arg {
GenericArgument::Lifetime(_) => true,
GenericArgument::Type(r#type) => should_validate_type(r#type),
GenericArgument::Const(_expr) => todo!(),
GenericArgument::AssocType(_assoc_type) => todo!(),
GenericArgument::AssocConst(_assoc_const) => todo!(),
GenericArgument::Constraint(_constraint) => todo!(),
_ => true,
}
}

fn should_validate_path(path: &Path) -> bool {
if let Some(ident) = path.get_ident() {
return !PRIMITIVE_AND_BUILT_IN_TYPES.contains(&ident.to_string().as_str());
}
let path_string = path_to_string(path);

if CONTAINER_TYPES.contains(&path_string.as_str())
&& let Some(segment) = path.segments.last()
&& let PathArguments::AngleBracketed(arguments) = &segment.arguments
&& !arguments.args.iter().all(should_validate_generic_argument)
{
return false;
}

true
}

pub fn should_validate_type(r#type: &Type) -> bool {
match r#type {
Type::Array(r#type) => should_validate_type(&r#type.elem),
Type::BareFn(_) => false,
Type::Group(r#type) => should_validate_type(&r#type.elem),
Type::ImplTrait(r#type) => r#type.bounds.iter().any(
|bound| matches!(bound, TypeParamBound::Trait(bound) if is_validate_path(&bound.path)),
),
Type::Infer(_) => true,
Type::Macro(_) => true,
Type::Never(_) => false,
Type::Paren(r#type) => should_validate_type(&r#type.elem),
Type::Path(r#type) => should_validate_path(&r#type.path),
Type::Ptr(r#type) => should_validate_type(&r#type.elem),
Type::Reference(r#type) => should_validate_type(&r#type.elem),
Type::Slice(r#type) => should_validate_type(&r#type.elem),
Type::TraitObject(r#type) => r#type.bounds.iter().any(
|bound| matches!(bound, TypeParamBound::Trait(bound) if is_validate_path(&bound.path)),
),
Type::Tuple(r#type) => {
!r#type.elems.is_empty() && r#type.elems.iter().all(should_validate_type)
}
Type::Verbatim(_) => false,
_ => false,
}
}

#[cfg(test)]
mod tests {
use proc_macro2::TokenStream;
use quote::quote;

use super::should_validate_type;

fn validate(tokens: TokenStream) -> bool {
should_validate_type(&syn::parse2(tokens).expect("Type should be valid."))
}

#[test]
fn should_validate() {
assert!(validate(quote!(&T)));
assert!(validate(quote!(T)));

assert!(validate(quote!((T, T))));
assert!(validate(quote!((A, B, C))));

assert!(validate(quote!([T])));
assert!(validate(quote!([T; 3])));
assert!(validate(quote!([&T])));
assert!(validate(quote!([&T; 3])));
assert!(validate(quote!(&[T])));
assert!(validate(quote!(&[T; 3])));

assert!(validate(quote!(Arc<T>)));
assert!(validate(quote!(BTreeSet<T>)));
assert!(validate(quote!(BTreeMap<K, V>)));
assert!(validate(quote!(HashSet<T>)));
assert!(validate(quote!(HashMap<K, V>)));
assert!(validate(quote!(LinkedList<T>)));
assert!(validate(quote!(Option<T>)));
assert!(validate(quote!(Option<Option<T>>)));
assert!(validate(quote!(Rc<T>)));
assert!(validate(quote!(Vec<T>)));
assert!(validate(quote!(VecDeque<T>)));

assert!(validate(quote!(impl Validate)));
assert!(validate(quote!(impl fortifier::Validate)));
assert!(validate(quote!(dyn Validate)));
assert!(validate(quote!(dyn ::fortifier::Validate)));
}

#[test]
fn should_not_validate() {
assert!(!validate(quote!(bool)));
assert!(!validate(quote!(i8)));
assert!(!validate(quote!(i16)));
assert!(!validate(quote!(i32)));
assert!(!validate(quote!(i64)));
assert!(!validate(quote!(i128)));
assert!(!validate(quote!(isize)));
assert!(!validate(quote!(u8)));
assert!(!validate(quote!(u16)));
assert!(!validate(quote!(u32)));
assert!(!validate(quote!(u64)));
assert!(!validate(quote!(u128)));
assert!(!validate(quote!(usize)));
assert!(!validate(quote!(f32)));
assert!(!validate(quote!(f64)));
assert!(!validate(quote!(char)));
assert!(!validate(quote!(&str)));
assert!(!validate(quote!(String)));

assert!(!validate(quote!(())));
assert!(!validate(quote!((bool, bool))));
assert!(!validate(quote!((usize, usize, usize))));
assert!(!validate(quote!((usize, &str))));

assert!(!validate(quote!([isize])));
assert!(!validate(quote!([&str; 3])));
assert!(!validate(quote!(&[isize])));
assert!(!validate(quote!(&[&str; 3])));

assert!(!validate(quote!(Arc<&str>)));
assert!(!validate(quote!(BTreeSet<usize>)));
assert!(!validate(quote!(BTreeMap<usize, &str>)));
assert!(!validate(quote!(HashSet<&str>)));
assert!(!validate(quote!(HashMap<&str, &str>)));
assert!(!validate(quote!(LinkedList<char>)));
assert!(!validate(quote!(Option<char>)));
assert!(!validate(quote!(Option<Option<String>>)));
assert!(!validate(quote!(Rc<&str>)));
assert!(!validate(quote!(Vec<usize>)));
assert!(!validate(quote!(VecDeque<String>)));

assert!(!validate(quote!(impl Serialize)));
assert!(!validate(quote!(dyn Serialize)));
}
}
6 changes: 2 additions & 4 deletions packages/fortifier-macros/tests/derive/enum_named_pass.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::error::Error;

use fortifier::Validate;
use fortifier::{Validate, ValidationErrors};

#[derive(Validate)]
enum ChangeEmailAddressRelation {
Expand All @@ -19,7 +17,7 @@ enum ChangeEmailAddressRelation {
},
}

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<ChangeEmailAddressRelationValidationError>> {
let data = ChangeEmailAddressRelation::Create {
email_address: "john@doe.com".to_owned(),
};
Expand Down
6 changes: 2 additions & 4 deletions packages/fortifier-macros/tests/derive/enum_unit_pass.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::error::Error;

use fortifier::Validate;
use fortifier::{Validate, ValidationErrors};

#[derive(Validate)]
enum ChangeEmailAddressRelation {
Expand All @@ -9,7 +7,7 @@ enum ChangeEmailAddressRelation {
Delete,
}

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<ChangeEmailAddressRelationValidationError>> {
let data = ChangeEmailAddressRelation::Create;

data.validate_sync()?;
Expand Down
6 changes: 2 additions & 4 deletions packages/fortifier-macros/tests/derive/enum_unnamed_pass.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::error::Error;

use fortifier::Validate;
use fortifier::{Validate, ValidationErrors};

#[derive(Validate)]
enum ChangeEmailAddressRelation {
Expand All @@ -9,7 +7,7 @@ enum ChangeEmailAddressRelation {
Delete(String),
}

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<ChangeEmailAddressRelationValidationError>> {
let data = ChangeEmailAddressRelation::Create("john@doe.com".to_owned());

data.validate_sync()?;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::error::Error;

use fortifier::{Validate, ValidateEmail, ValidateLength};
use fortifier::{Validate, ValidateEmail, ValidateLength, ValidationErrors};

#[derive(Validate)]
struct CreateUser<E: ValidateEmail, N: ValidateLength<usize>> {
Expand All @@ -11,7 +9,7 @@ struct CreateUser<E: ValidateEmail, N: ValidateLength<usize>> {
name: N,
}

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<CreateUserValidationError>> {
let data = CreateUser {
email: "john@doe.com",
name: "John Doe",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::error::Error;

use fortifier::Validate;
use fortifier::{Validate, ValidationErrors};

#[derive(Validate)]
struct CreateUser<'a, 'b> {
Expand All @@ -11,7 +9,7 @@ struct CreateUser<'a, 'b> {
name: &'b str,
}

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<CreateUserValidationError>> {
let data = CreateUser {
email: "john@doe.com",
name: "John Doe",
Expand Down
6 changes: 2 additions & 4 deletions packages/fortifier-macros/tests/derive/struct_named_pass.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::error::Error;

use fortifier::Validate;
use fortifier::{Validate, ValidationErrors};

#[derive(Validate)]
struct CreateUser {
Expand All @@ -11,7 +9,7 @@ struct CreateUser {
name: String,
}

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<CreateUserValidationError>> {
let data = CreateUser {
email: "john@doe.com".to_owned(),
name: "John Doe".to_owned(),
Expand Down
6 changes: 3 additions & 3 deletions packages/fortifier-macros/tests/derive/struct_unit_pass.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::error::Error;
use std::convert::Infallible;

use fortifier::Validate;
use fortifier::{Validate, ValidationErrors};

#[derive(Validate)]
struct CreateUser;

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<Infallible>> {
let data = CreateUser;

data.validate_sync()?;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use std::error::Error;

use fortifier::{Validate, ValidateLength};
use fortifier::{Validate, ValidateLength, ValidationErrors};

#[derive(Validate)]
struct CreateUser<N: ValidateLength<usize>>(#[validate(length(min = 1, max = 256))] N);

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<CreateUserValidationError>> {
let data = CreateUser("John Doe");

data.validate_sync()?;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use std::error::Error;

use fortifier::Validate;
use fortifier::{Validate, ValidationErrors};

#[derive(Validate)]
struct CreateUser<'a>(#[validate(length(min = 1, max = 256))] &'a str);

fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), ValidationErrors<CreateUserValidationError>> {
let data = CreateUser("John Doe");

data.validate_sync()?;
Expand Down
Loading
Loading