From 6b824c9e864618a024e6da5c659d5db100c47c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABlle=20Huisman?= Date: Sat, 13 Dec 2025 16:11:13 +0100 Subject: [PATCH] feat: add validation compile tests --- Cargo.lock | 4 ++ Cargo.toml | 3 + packages/fortifier-macros/Cargo.toml | 6 +- .../fortifier-macros/src/validate/field.rs | 14 +++- .../src/validations/length.rs | 8 +++ packages/fortifier-macros/tests/derive.rs | 8 --- packages/fortifier-macros/tests/validate.rs | 8 +++ .../{derive => validate}/context_pass.rs | 0 .../{derive => validate}/enum_named_pass.rs | 0 .../{derive => validate}/enum_unit_pass.rs | 0 .../{derive => validate}/enum_unnamed_pass.rs | 0 .../struct_named_generics_pass.rs | 0 .../struct_named_lifetimes_pass.rs | 0 .../{derive => validate}/struct_named_pass.rs | 0 .../{derive => validate}/struct_unit_pass.rs | 0 .../struct_unnamed_generics_pass.rs | 0 .../struct_unnamed_lifetimes_pass.rs | 0 .../struct_unnamed_pass.rs | 0 .../fortifier-macros/tests/validations.rs | 8 +++ .../email/invalid_allow_display_text_fail.rs | 9 +++ .../invalid_allow_display_text_fail.stderr | 5 ++ .../invalid_allow_domain_literal_fail.rs | 9 +++ .../invalid_allow_domain_literal_fail.stderr | 5 ++ .../email/invalid_minimum_sub_domains_fail.rs | 9 +++ .../invalid_minimum_sub_domains_fail.stderr | 5 ++ .../tests/validations/email/types_pass.rs | 31 ++++++++ .../tests/validations/email/unknown_fail.rs | 9 +++ .../validations/email/unknown_fail.stderr | 5 ++ .../validations/length/conflict_max_fail.rs | 9 +++ .../length/conflict_max_fail.stderr | 5 ++ .../validations/length/conflict_min_fail.rs | 9 +++ .../length/conflict_min_fail.stderr | 5 ++ .../tests/validations/length/options_pass.rs | 35 +++++++++ .../tests/validations/length/types_pass.rs | 71 +++++++++++++++++++ .../tests/validations/length/unknown_fail.rs | 9 +++ .../validations/length/unknown_fail.stderr | 5 ++ .../regex/invalid_expression_fail.rs | 9 +++ .../regex/invalid_expression_fail.stderr | 39 ++++++++++ .../tests/validations/regex/types_pass.rs | 29 ++++++++ .../tests/validations/regex/unknown_fail.rs | 9 +++ .../validations/regex/unknown_fail.stderr | 5 ++ .../tests/validations/url/types_pass.rs | 28 ++++++++ .../tests/validations/url/unknown_fail.rs | 9 +++ .../tests/validations/url/unknown_fail.stderr | 5 ++ packages/fortifier/Cargo.toml | 6 +- packages/fortifier/src/validations/length.rs | 1 + 46 files changed, 419 insertions(+), 15 deletions(-) delete mode 100644 packages/fortifier-macros/tests/derive.rs create mode 100644 packages/fortifier-macros/tests/validate.rs rename packages/fortifier-macros/tests/{derive => validate}/context_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/enum_named_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/enum_unit_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/enum_unnamed_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/struct_named_generics_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/struct_named_lifetimes_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/struct_named_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/struct_unit_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/struct_unnamed_generics_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/struct_unnamed_lifetimes_pass.rs (100%) rename packages/fortifier-macros/tests/{derive => validate}/struct_unnamed_pass.rs (100%) create mode 100644 packages/fortifier-macros/tests/validations.rs create mode 100644 packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/email/types_pass.rs create mode 100644 packages/fortifier-macros/tests/validations/email/unknown_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/email/unknown_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/length/conflict_max_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/length/conflict_max_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/length/conflict_min_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/length/conflict_min_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/length/options_pass.rs create mode 100644 packages/fortifier-macros/tests/validations/length/types_pass.rs create mode 100644 packages/fortifier-macros/tests/validations/length/unknown_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/length/unknown_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/regex/types_pass.rs create mode 100644 packages/fortifier-macros/tests/validations/regex/unknown_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/regex/unknown_fail.stderr create mode 100644 packages/fortifier-macros/tests/validations/url/types_pass.rs create mode 100644 packages/fortifier-macros/tests/validations/url/unknown_fail.rs create mode 100644 packages/fortifier-macros/tests/validations/url/unknown_fail.stderr diff --git a/Cargo.lock b/Cargo.lock index d6efe7e..2f82352 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,12 +230,16 @@ name = "fortifier-macros" version = "0.0.1" dependencies = [ "convert_case", + "email_address", "fortifier", + "indexmap", "proc-macro-crate", "proc-macro2", "quote", + "regex", "syn", "trybuild", + "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7a04653..f9229b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,15 @@ repository = "https://github.com/RustForWeb/fortifier" version = "0.0.1" [workspace.dependencies] +email_address = { version = "0.2.9", default-features = false } fortifier = { path = "./packages/fortifier", version = "0.0.1" } fortifier-macros = { path = "./packages/fortifier-macros", version = "0.0.1" } +indexmap = "2.12.0" regex = "1.12.2" serde = "1.0.228" serde_json = "1.0.145" tokio = "1.48.0" +url = "2.5.7" utoipa = "5.4.0" [workspace.lints.rust] diff --git a/packages/fortifier-macros/Cargo.toml b/packages/fortifier-macros/Cargo.toml index 79ee1db..a5020f8 100644 --- a/packages/fortifier-macros/Cargo.toml +++ b/packages/fortifier-macros/Cargo.toml @@ -24,8 +24,12 @@ quote = "1.0.42" syn = "2.0.110" [dev-dependencies] -fortifier = { workspace = true, features = ["email"] } +email_address.workspace = true +fortifier = { workspace = true, features = ["all-validations", "indexmap"] } +indexmap.workspace = true +regex.workspace = true trybuild = "1.0.114" +url.workspace = true [lints] workspace = true diff --git a/packages/fortifier-macros/src/validate/field.rs b/packages/fortifier-macros/src/validate/field.rs index fd2f8ef..3015b10 100644 --- a/packages/fortifier-macros/src/validate/field.rs +++ b/packages/fortifier-macros/src/validate/field.rs @@ -47,9 +47,7 @@ impl<'a> ValidateField<'a> { ) -> Result { let error_ident = match &ident { LiteralOrIdent::Literal(literal) => format_ident!("F{literal}"), - LiteralOrIdent::Ident(ident) => { - format_ident!("{}", ident.to_string().to_case(Case::UpperCamel)) - } + LiteralOrIdent::Ident(ident) => upper_camel_ident(ident), }; let error_type_ident = format_ident!("{type_prefix}{error_ident}ValidationError"); @@ -178,3 +176,13 @@ impl<'a> ValidateField<'a> { .collect() } } + +fn upper_camel_ident(ident: &Ident) -> Ident { + let s = ident.to_string(); + + if s.starts_with("r#") { + format_ident!("{}", (&s[2..]).to_case(Case::UpperCamel)) + } else { + format_ident!("{}", s.to_case(Case::UpperCamel)) + } +} diff --git a/packages/fortifier-macros/src/validations/length.rs b/packages/fortifier-macros/src/validations/length.rs index ec32494..0fecd83 100644 --- a/packages/fortifier-macros/src/validations/length.rs +++ b/packages/fortifier-macros/src/validations/length.rs @@ -36,6 +36,14 @@ impl Validation for Length { } })?; + if result.equal.is_some() { + if result.min.is_some() { + return Err(meta.error("`equal` and `min` are conflicting parameters")); + } else if result.max.is_some() { + return Err(meta.error("`equal` and `max` are conflicting parameters")); + } + } + Ok(result) } diff --git a/packages/fortifier-macros/tests/derive.rs b/packages/fortifier-macros/tests/derive.rs deleted file mode 100644 index 0838ef5..0000000 --- a/packages/fortifier-macros/tests/derive.rs +++ /dev/null @@ -1,8 +0,0 @@ -use trybuild::TestCases; - -#[test] -fn derive() { - let t = TestCases::new(); - t.pass("tests/derive/*_pass.rs"); - t.compile_fail("tests/derive/*_fail.rs"); -} diff --git a/packages/fortifier-macros/tests/validate.rs b/packages/fortifier-macros/tests/validate.rs new file mode 100644 index 0000000..3f20274 --- /dev/null +++ b/packages/fortifier-macros/tests/validate.rs @@ -0,0 +1,8 @@ +use trybuild::TestCases; + +#[test] +fn validate() { + let t = TestCases::new(); + t.pass("tests/validate/*_pass.rs"); + t.compile_fail("tests/validate/*_fail.rs"); +} diff --git a/packages/fortifier-macros/tests/derive/context_pass.rs b/packages/fortifier-macros/tests/validate/context_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/context_pass.rs rename to packages/fortifier-macros/tests/validate/context_pass.rs diff --git a/packages/fortifier-macros/tests/derive/enum_named_pass.rs b/packages/fortifier-macros/tests/validate/enum_named_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/enum_named_pass.rs rename to packages/fortifier-macros/tests/validate/enum_named_pass.rs diff --git a/packages/fortifier-macros/tests/derive/enum_unit_pass.rs b/packages/fortifier-macros/tests/validate/enum_unit_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/enum_unit_pass.rs rename to packages/fortifier-macros/tests/validate/enum_unit_pass.rs diff --git a/packages/fortifier-macros/tests/derive/enum_unnamed_pass.rs b/packages/fortifier-macros/tests/validate/enum_unnamed_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/enum_unnamed_pass.rs rename to packages/fortifier-macros/tests/validate/enum_unnamed_pass.rs diff --git a/packages/fortifier-macros/tests/derive/struct_named_generics_pass.rs b/packages/fortifier-macros/tests/validate/struct_named_generics_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/struct_named_generics_pass.rs rename to packages/fortifier-macros/tests/validate/struct_named_generics_pass.rs diff --git a/packages/fortifier-macros/tests/derive/struct_named_lifetimes_pass.rs b/packages/fortifier-macros/tests/validate/struct_named_lifetimes_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/struct_named_lifetimes_pass.rs rename to packages/fortifier-macros/tests/validate/struct_named_lifetimes_pass.rs diff --git a/packages/fortifier-macros/tests/derive/struct_named_pass.rs b/packages/fortifier-macros/tests/validate/struct_named_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/struct_named_pass.rs rename to packages/fortifier-macros/tests/validate/struct_named_pass.rs diff --git a/packages/fortifier-macros/tests/derive/struct_unit_pass.rs b/packages/fortifier-macros/tests/validate/struct_unit_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/struct_unit_pass.rs rename to packages/fortifier-macros/tests/validate/struct_unit_pass.rs diff --git a/packages/fortifier-macros/tests/derive/struct_unnamed_generics_pass.rs b/packages/fortifier-macros/tests/validate/struct_unnamed_generics_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/struct_unnamed_generics_pass.rs rename to packages/fortifier-macros/tests/validate/struct_unnamed_generics_pass.rs diff --git a/packages/fortifier-macros/tests/derive/struct_unnamed_lifetimes_pass.rs b/packages/fortifier-macros/tests/validate/struct_unnamed_lifetimes_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/struct_unnamed_lifetimes_pass.rs rename to packages/fortifier-macros/tests/validate/struct_unnamed_lifetimes_pass.rs diff --git a/packages/fortifier-macros/tests/derive/struct_unnamed_pass.rs b/packages/fortifier-macros/tests/validate/struct_unnamed_pass.rs similarity index 100% rename from packages/fortifier-macros/tests/derive/struct_unnamed_pass.rs rename to packages/fortifier-macros/tests/validate/struct_unnamed_pass.rs diff --git a/packages/fortifier-macros/tests/validations.rs b/packages/fortifier-macros/tests/validations.rs new file mode 100644 index 0000000..935a952 --- /dev/null +++ b/packages/fortifier-macros/tests/validations.rs @@ -0,0 +1,8 @@ +use trybuild::TestCases; + +#[test] +fn validations() { + let t = TestCases::new(); + t.pass("tests/validations/*/*_pass.rs"); + t.compile_fail("tests/validations/*/*_fail.rs"); +} diff --git a/packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.rs b/packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.rs new file mode 100644 index 0000000..4e9b1be --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct EmailData<'a> { + #[validate(email(allow_display_text = 1))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.stderr b/packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.stderr new file mode 100644 index 0000000..59a4d2b --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/invalid_allow_display_text_fail.stderr @@ -0,0 +1,5 @@ +error: expected boolean literal + --> tests/validations/email/invalid_allow_display_text_fail.rs:5:43 + | +5 | #[validate(email(allow_display_text = 1))] + | ^ diff --git a/packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.rs b/packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.rs new file mode 100644 index 0000000..f7a06cc --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct EmailData<'a> { + #[validate(email(allow_domain_literal = 1))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.stderr b/packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.stderr new file mode 100644 index 0000000..a3c7ea9 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/invalid_allow_domain_literal_fail.stderr @@ -0,0 +1,5 @@ +error: expected boolean literal + --> tests/validations/email/invalid_allow_domain_literal_fail.rs:5:45 + | +5 | #[validate(email(allow_domain_literal = 1))] + | ^ diff --git a/packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.rs b/packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.rs new file mode 100644 index 0000000..b858e5a --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct EmailData<'a> { + #[validate(email(minimum_sub_domains = -1))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.stderr b/packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.stderr new file mode 100644 index 0000000..c2a35c1 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/invalid_minimum_sub_domains_fail.stderr @@ -0,0 +1,5 @@ +error: invalid digit found in string + --> tests/validations/email/invalid_minimum_sub_domains_fail.rs:5:44 + | +5 | #[validate(email(minimum_sub_domains = -1))] + | ^ diff --git a/packages/fortifier-macros/tests/validations/email/types_pass.rs b/packages/fortifier-macros/tests/validations/email/types_pass.rs new file mode 100644 index 0000000..5ac8c9a --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/types_pass.rs @@ -0,0 +1,31 @@ +use email_address::EmailAddress; +use fortifier::{EmailError, Validate, ValidationErrors}; + +#[derive(Validate)] +struct EmailData<'a> { + #[validate(email)] + r#str: &'a str, + #[validate(email)] + string: String, + #[validate(email)] + email_address: EmailAddress, +} + +fn main() { + let data = EmailData { + r#str: "admin", + string: "admin@".to_owned(), + email_address: EmailAddress::new_unchecked("Admin "), + }; + + assert_eq!( + data.validate_sync(), + Err(ValidationErrors::from_iter([ + EmailDataValidationError::Str(EmailError::from(email_address::Error::MissingSeparator)), + EmailDataValidationError::String(EmailError::from(email_address::Error::DomainEmpty)), + EmailDataValidationError::EmailAddress(EmailError::from( + email_address::Error::UnsupportedDisplayName + )), + ])) + ); +} diff --git a/packages/fortifier-macros/tests/validations/email/unknown_fail.rs b/packages/fortifier-macros/tests/validations/email/unknown_fail.rs new file mode 100644 index 0000000..8e92e97 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/unknown_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct EmailData<'a> { + #[validate(email(unknown = true))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/email/unknown_fail.stderr b/packages/fortifier-macros/tests/validations/email/unknown_fail.stderr new file mode 100644 index 0000000..7606e2c --- /dev/null +++ b/packages/fortifier-macros/tests/validations/email/unknown_fail.stderr @@ -0,0 +1,5 @@ +error: unknown parameter + --> tests/validations/email/unknown_fail.rs:5:22 + | +5 | #[validate(email(unknown = true))] + | ^^^^^^^ diff --git a/packages/fortifier-macros/tests/validations/length/conflict_max_fail.rs b/packages/fortifier-macros/tests/validations/length/conflict_max_fail.rs new file mode 100644 index 0000000..bb400ae --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/conflict_max_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct LengthData<'a> { + #[validate(length(equal = 1, max = 2))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/length/conflict_max_fail.stderr b/packages/fortifier-macros/tests/validations/length/conflict_max_fail.stderr new file mode 100644 index 0000000..925770a --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/conflict_max_fail.stderr @@ -0,0 +1,5 @@ +error: `equal` and `max` are conflicting parameters + --> tests/validations/length/conflict_max_fail.rs:5:16 + | +5 | #[validate(length(equal = 1, max = 2))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/fortifier-macros/tests/validations/length/conflict_min_fail.rs b/packages/fortifier-macros/tests/validations/length/conflict_min_fail.rs new file mode 100644 index 0000000..b0c1408 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/conflict_min_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct LengthData<'a> { + #[validate(length(equal = 1, min = 2))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/length/conflict_min_fail.stderr b/packages/fortifier-macros/tests/validations/length/conflict_min_fail.stderr new file mode 100644 index 0000000..6b2cb6a --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/conflict_min_fail.stderr @@ -0,0 +1,5 @@ +error: `equal` and `min` are conflicting parameters + --> tests/validations/length/conflict_min_fail.rs:5:16 + | +5 | #[validate(length(equal = 1, min = 2))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/fortifier-macros/tests/validations/length/options_pass.rs b/packages/fortifier-macros/tests/validations/length/options_pass.rs new file mode 100644 index 0000000..79e19a4 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/options_pass.rs @@ -0,0 +1,35 @@ +use fortifier::{LengthError, Validate, ValidationErrors}; + +#[derive(Validate)] +struct LengthData<'a> { + #[validate(length(equal = 2))] + equal: &'a str, + #[validate(length(min = 1))] + min: &'a str, + #[validate(length(max = 4))] + max: &'a str, + #[validate(length(min = 1, max = 4))] + min_max: &'a str, +} + +fn main() { + let data = LengthData { + equal: "a", + min: "", + max: "abcde", + min_max: "abcdef", + }; + + assert_eq!( + data.validate_sync(), + Err(ValidationErrors::from_iter([ + LengthDataValidationError::Equal(LengthError::Equal { + equal: 2, + length: 1 + }), + LengthDataValidationError::Min(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::Max(LengthError::Max { max: 4, length: 5 }), + LengthDataValidationError::MinMax(LengthError::Max { max: 4, length: 6 }) + ])) + ); +} diff --git a/packages/fortifier-macros/tests/validations/length/types_pass.rs b/packages/fortifier-macros/tests/validations/length/types_pass.rs new file mode 100644 index 0000000..4072e1d --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/types_pass.rs @@ -0,0 +1,71 @@ +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, LinkedList, VecDeque}; + +use fortifier::{LengthError, Validate, ValidationErrors}; +use indexmap::{IndexMap, IndexSet}; + +#[derive(Validate)] +struct LengthData<'a> { + #[validate(length(min = 1))] + r#str: &'a str, + #[validate(length(min = 1))] + string: String, + #[validate(length(min = 1))] + array: [usize; 0], + #[validate(length(min = 1))] + slice: &'a [usize], + #[validate(length(min = 1))] + b_tree_map: BTreeMap, + #[validate(length(min = 1))] + b_tree_set: BTreeSet, + #[validate(length(min = 1))] + hash_map: HashMap, + #[validate(length(min = 1))] + hash_set: HashSet, + #[validate(length(min = 1))] + index_map: IndexMap, + #[validate(length(min = 1))] + index_set: IndexSet, + #[validate(length(min = 1))] + linked_list: LinkedList, + #[validate(length(min = 1))] + vec: Vec, + #[validate(length(min = 1))] + vec_deque: VecDeque, +} + +fn main() { + let data = LengthData { + r#str: "", + string: "".to_owned(), + array: [], + slice: &[], + b_tree_map: BTreeMap::default(), + b_tree_set: BTreeSet::default(), + hash_map: HashMap::default(), + hash_set: HashSet::default(), + index_map: IndexMap::default(), + index_set: IndexSet::default(), + linked_list: LinkedList::default(), + vec: Vec::default(), + vec_deque: VecDeque::default(), + }; + + assert_eq!( + data.validate_sync(), + Err(ValidationErrors::from_iter([ + LengthDataValidationError::Str(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::String(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::Array(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::Slice(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::BTreeMap(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::BTreeSet(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::HashMap(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::HashSet(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::IndexMap(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::IndexSet(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::LinkedList(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::Vec(LengthError::Min { min: 1, length: 0 }), + LengthDataValidationError::VecDeque(LengthError::Min { min: 1, length: 0 }), + ])) + ); +} diff --git a/packages/fortifier-macros/tests/validations/length/unknown_fail.rs b/packages/fortifier-macros/tests/validations/length/unknown_fail.rs new file mode 100644 index 0000000..3c73344 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/unknown_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct LengthData<'a> { + #[validate(length(unknown = 2))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/length/unknown_fail.stderr b/packages/fortifier-macros/tests/validations/length/unknown_fail.stderr new file mode 100644 index 0000000..a319b1f --- /dev/null +++ b/packages/fortifier-macros/tests/validations/length/unknown_fail.stderr @@ -0,0 +1,5 @@ +error: unknown parameter + --> tests/validations/length/unknown_fail.rs:5:23 + | +5 | #[validate(length(unknown = 2))] + | ^^^^^^^ diff --git a/packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.rs b/packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.rs new file mode 100644 index 0000000..16e6d77 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct RegexData<'a> { + #[validate(regex = "abc")] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.stderr b/packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.stderr new file mode 100644 index 0000000..a823615 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/regex/invalid_expression_fail.stderr @@ -0,0 +1,39 @@ +error[E0277]: the trait bound `str: AsRegex` is not satisfied + --> tests/validations/regex/invalid_expression_fail.rs:5:24 + | +3 | #[derive(Validate)] + | -------- required by a bound introduced by this call +4 | struct RegexData<'a> { +5 | #[validate(regex = "abc")] + | ^^^^^ the trait `AsRegex` is not implemented for `str` + | + = help: the following other types implement trait `AsRegex`: + &T + LazyLock + regex::regex::string::Regex + = note: required for `&str` to implement `AsRegex` +note: required by a bound in `validate_regex` + --> $WORKSPACE/packages/fortifier/src/validations/regex.rs + | + | fn validate_regex(&self, regex: impl AsRegex) -> Result<(), RegexError>; + | ^^^^^^^ required by this bound in `ValidateRegex::validate_regex` + +error[E0277]: the trait bound `&str: AsRegex` is not satisfied + --> tests/validations/regex/invalid_expression_fail.rs:5:24 + | +3 | #[derive(Validate)] + | -------- required by a bound introduced by this call +4 | struct RegexData<'a> { +5 | #[validate(regex = "abc")] + | ^^^^^ the trait `Sized` is not implemented for `str` + | + = help: the following other types implement trait `AsRegex`: + &T + LazyLock + regex::regex::string::Regex + = note: required for `&str` to implement `AsRegex` +note: required by a bound in `validate_regex` + --> $WORKSPACE/packages/fortifier/src/validations/regex.rs + | + | fn validate_regex(&self, regex: impl AsRegex) -> Result<(), RegexError>; + | ^^^^^^^ required by this bound in `ValidateRegex::validate_regex` diff --git a/packages/fortifier-macros/tests/validations/regex/types_pass.rs b/packages/fortifier-macros/tests/validations/regex/types_pass.rs new file mode 100644 index 0000000..8cee71a --- /dev/null +++ b/packages/fortifier-macros/tests/validations/regex/types_pass.rs @@ -0,0 +1,29 @@ +use std::sync::LazyLock; + +use fortifier::{RegexError, Validate, ValidationErrors}; +use regex::Regex; + +static REGEX: LazyLock = LazyLock::new(|| Regex::new(r"[A-Z]{2}").expect("valid regex")); + +#[derive(Validate)] +struct RegexData<'a> { + #[validate(regex = ®EX)] + r#str: &'a str, + #[validate(regex = ®EX)] + string: String, +} + +fn main() { + let data = RegexData { + r#str: "A", + string: "abc".to_owned(), + }; + + assert_eq!( + data.validate_sync(), + Err(ValidationErrors::from_iter([ + RegexDataValidationError::Str(RegexError::default()), + RegexDataValidationError::String(RegexError::default()) + ])) + ); +} diff --git a/packages/fortifier-macros/tests/validations/regex/unknown_fail.rs b/packages/fortifier-macros/tests/validations/regex/unknown_fail.rs new file mode 100644 index 0000000..2383771 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/regex/unknown_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct UrlData<'a> { + #[validate(regex(unknown))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/regex/unknown_fail.stderr b/packages/fortifier-macros/tests/validations/regex/unknown_fail.stderr new file mode 100644 index 0000000..ae6bc64 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/regex/unknown_fail.stderr @@ -0,0 +1,5 @@ +error: unknown parameter + --> tests/validations/regex/unknown_fail.rs:5:22 + | +5 | #[validate(regex(unknown))] + | ^^^^^^^ diff --git a/packages/fortifier-macros/tests/validations/url/types_pass.rs b/packages/fortifier-macros/tests/validations/url/types_pass.rs new file mode 100644 index 0000000..74efcab --- /dev/null +++ b/packages/fortifier-macros/tests/validations/url/types_pass.rs @@ -0,0 +1,28 @@ +use fortifier::{UrlError, Validate, ValidationErrors}; +use url::{ParseError, Url}; + +#[derive(Validate)] +struct UrlData<'a> { + #[validate(url)] + r#str: &'a str, + #[validate(url)] + string: String, + #[validate(url)] + url: Url, +} + +fn main() { + let data = UrlData { + r#str: "http://", + string: "localhost/test".to_owned(), + url: Url::parse("http://localhost").expect("valid url"), + }; + + assert_eq!( + data.validate_sync(), + Err(ValidationErrors::from_iter([ + UrlDataValidationError::Str(UrlError::from(ParseError::EmptyHost)), + UrlDataValidationError::String(UrlError::from(ParseError::RelativeUrlWithoutBase)) + ])) + ); +} diff --git a/packages/fortifier-macros/tests/validations/url/unknown_fail.rs b/packages/fortifier-macros/tests/validations/url/unknown_fail.rs new file mode 100644 index 0000000..50ab574 --- /dev/null +++ b/packages/fortifier-macros/tests/validations/url/unknown_fail.rs @@ -0,0 +1,9 @@ +use fortifier::Validate; + +#[derive(Validate)] +struct UrlData<'a> { + #[validate(url(unknown))] + value: &'a str, +} + +fn main() {} diff --git a/packages/fortifier-macros/tests/validations/url/unknown_fail.stderr b/packages/fortifier-macros/tests/validations/url/unknown_fail.stderr new file mode 100644 index 0000000..097007c --- /dev/null +++ b/packages/fortifier-macros/tests/validations/url/unknown_fail.stderr @@ -0,0 +1,5 @@ +error: expected `,` + --> tests/validations/url/unknown_fail.rs:5:19 + | +5 | #[validate(url(unknown))] + | ^ diff --git a/packages/fortifier/Cargo.toml b/packages/fortifier/Cargo.toml index c13d41b..a54b8f0 100644 --- a/packages/fortifier/Cargo.toml +++ b/packages/fortifier/Cargo.toml @@ -21,12 +21,12 @@ url = ["dep:url"] utoipa = ["dep:utoipa", "fortifier-macros?/utoipa"] [dependencies] -email_address = { version = "0.2.9", default-features = false, optional = true } +email_address = { workspace = true, default-features = false, optional = true } fortifier-macros = { workspace = true, optional = true } -indexmap = { version = "2.12.0", optional = true } +indexmap = { workspace = true, optional = true } regex = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } -url = { version = "2.5.7", optional = true } +url = { workspace = true, optional = true } utoipa = { workspace = true, optional = true } [dev-dependencies] diff --git a/packages/fortifier/src/validations/length.rs b/packages/fortifier/src/validations/length.rs index 17a3c01..51ba18a 100644 --- a/packages/fortifier/src/validations/length.rs +++ b/packages/fortifier/src/validations/length.rs @@ -160,6 +160,7 @@ macro_rules! validate_with_len { } validate_with_len!([T], T); +validate_with_len!(&[T], T); validate_with_len!(BTreeSet, T); validate_with_len!(BTreeMap, K, V); validate_with_len!(HashSet, T, S);