use reinhardt_core::validators::{
AndValidator, DateValidator, EmailValidator, IPAddressValidator, MaxLengthValidator,
MaxValueValidator, MinLengthValidator, MinValueValidator, OrValidator, RangeValidator,
SlugValidator, UUIDValidator, UrlValidator, ValidationError, Validator,
};
use rstest::rstest;
#[rstest]
fn and_validator_min_and_max_length_accepts_valid_input() {
let validator = AndValidator::new(vec![
Box::new(MinLengthValidator::new(3)),
Box::new(MaxLengthValidator::new(10)),
]);
let result = validator.validate("hello");
assert!(result.is_ok());
}
#[rstest]
fn and_validator_rejects_too_short_input() {
let validator = AndValidator::new(vec![
Box::new(MinLengthValidator::new(3)),
Box::new(MaxLengthValidator::new(10)),
]);
let result = validator.validate("ab");
assert!(result.is_err());
assert!(matches!(
result,
Err(ValidationError::TooShort { length: 2, min: 3 })
));
}
#[rstest]
fn and_validator_rejects_too_long_input() {
let validator = AndValidator::new(vec![
Box::new(MinLengthValidator::new(3)),
Box::new(MaxLengthValidator::new(10)),
]);
let result = validator.validate("this string is too long");
assert!(result.is_err());
assert!(matches!(
result,
Err(ValidationError::TooLong { max: 10, .. })
));
}
#[rstest]
fn and_validator_boundary_values() {
let validator = AndValidator::new(vec![
Box::new(MinLengthValidator::new(3)),
Box::new(MaxLengthValidator::new(10)),
]);
let at_min = validator.validate("abc");
let at_max = validator.validate("1234567890");
assert!(at_min.is_ok()); assert!(at_max.is_ok()); }
#[rstest]
fn and_validator_with_builder_pattern() {
let validator = AndValidator::new(vec![Box::new(MinLengthValidator::new(3))])
.with_validator(Box::new(MaxLengthValidator::new(10)));
let valid = validator.validate("hello");
let too_short = validator.validate("ab");
let too_long = validator.validate("this is way too long");
assert!(valid.is_ok());
assert!(too_short.is_err());
assert!(too_long.is_err());
}
#[rstest]
fn or_validator_email_or_url_accepts_valid_email() {
let validator = OrValidator::new(vec![
Box::new(EmailValidator::new()),
Box::new(UrlValidator::new()),
]);
let result = validator.validate("user@example.com");
assert!(result.is_ok());
}
#[rstest]
fn or_validator_email_or_url_accepts_valid_url() {
let validator = OrValidator::new(vec![
Box::new(EmailValidator::new()),
Box::new(UrlValidator::new()),
]);
let result = validator.validate("https://example.com");
assert!(result.is_ok());
}
#[rstest]
fn or_validator_email_or_url_rejects_invalid_input() {
let validator = OrValidator::new(vec![
Box::new(EmailValidator::new()),
Box::new(UrlValidator::new()),
]);
let result = validator.validate("not-email-nor-url");
assert!(result.is_err());
assert!(matches!(
result,
Err(ValidationError::CompositeValidationFailed(_))
));
}
#[rstest]
fn or_validator_with_error_collection_reports_all_failures() {
let validator = OrValidator::new(vec![
Box::new(MinLengthValidator::new(100)),
Box::new(MinLengthValidator::new(200)),
])
.with_error_collection(true);
let result = validator.validate("short");
assert!(result.is_err());
assert!(matches!(
result,
Err(ValidationError::AllValidatorsFailed { .. })
));
}
#[rstest]
#[case("test@example.com")]
#[case("user.name@example.com")]
#[case("user+tag@example.co.uk")]
#[case("user_name@example.com")]
#[case("a@b.co")]
#[case("123@example.com")]
fn email_validator_accepts_valid_emails(#[case] email: &str) {
let validator = EmailValidator::new();
let result = validator.validate(email);
assert!(result.is_ok(), "Expected '{}' to be valid", email);
}
#[rstest]
#[case("invalid-email")]
#[case("@example.com")]
#[case("user@")]
#[case("user..name@example.com")]
#[case(".user@example.com")]
#[case("user@example")]
#[case("user name@example.com")]
#[case("user@@example.com")]
fn email_validator_rejects_invalid_emails(#[case] email: &str) {
let validator = EmailValidator::new();
let result = validator.validate(email);
assert!(result.is_err(), "Expected '{}' to be invalid", email);
}
#[rstest]
fn email_validator_returns_invalid_email_error_variant() {
let validator = EmailValidator::new();
let result = validator.validate("not-an-email");
assert!(matches!(result, Err(ValidationError::InvalidEmail(_))));
}
#[rstest]
#[case("192.168.1.1")]
#[case("10.0.0.1")]
#[case("127.0.0.1")]
#[case("::1")]
#[case("2001:db8::1")]
fn ip_validator_default_accepts_both_versions(#[case] ip: &str) {
let validator = IPAddressValidator::new();
let result = validator.validate(ip);
assert!(result.is_ok(), "Expected '{}' to be valid", ip);
}
#[rstest]
#[case("192.168.1.1")]
#[case("10.0.0.1")]
#[case("255.255.255.255")]
fn ip_validator_ipv4_only_accepts_ipv4(#[case] ip: &str) {
let validator = IPAddressValidator::ipv4_only();
let result = validator.validate(ip);
assert!(result.is_ok(), "Expected '{}' to be valid IPv4", ip);
}
#[rstest]
#[case("::1")]
#[case("2001:db8::1")]
#[case("fe80::1")]
fn ip_validator_ipv4_only_rejects_ipv6(#[case] ip: &str) {
let validator = IPAddressValidator::ipv4_only();
let result = validator.validate(ip);
assert!(
result.is_err(),
"Expected '{}' to be rejected by ipv4_only",
ip
);
assert!(matches!(result, Err(ValidationError::InvalidIPAddress(_))));
}
#[rstest]
#[case("::1")]
#[case("2001:db8::1")]
#[case("fe80::1")]
fn ip_validator_ipv6_only_accepts_ipv6(#[case] ip: &str) {
let validator = IPAddressValidator::ipv6_only();
let result = validator.validate(ip);
assert!(result.is_ok(), "Expected '{}' to be valid IPv6", ip);
}
#[rstest]
#[case("192.168.1.1")]
#[case("10.0.0.1")]
fn ip_validator_ipv6_only_rejects_ipv4(#[case] ip: &str) {
let validator = IPAddressValidator::ipv6_only();
let result = validator.validate(ip);
assert!(
result.is_err(),
"Expected '{}' to be rejected by ipv6_only",
ip
);
assert!(matches!(result, Err(ValidationError::InvalidIPAddress(_))));
}
#[rstest]
fn ip_validator_rejects_invalid_addresses() {
let validator = IPAddressValidator::new();
let invalid_text = validator.validate("invalid-ip");
let out_of_range = validator.validate("256.1.1.1");
let empty = validator.validate("");
assert!(invalid_text.is_err());
assert!(out_of_range.is_err());
assert!(empty.is_err());
}
#[rstest]
#[case(10, true)]
#[case(15, true)]
#[case(100, true)]
#[case(5, false)]
#[case(0, false)]
fn min_value_validator_i32(#[case] value: i32, #[case] expected_ok: bool) {
let validator = MinValueValidator::new(10);
let result = validator.validate(&value);
assert_eq!(result.is_ok(), expected_ok);
}
#[rstest]
#[case(20, true)]
#[case(15, true)]
#[case(0, true)]
#[case(25, false)]
#[case(100, false)]
fn max_value_validator_i32(#[case] value: i32, #[case] expected_ok: bool) {
let validator = MaxValueValidator::new(20);
let result = validator.validate(&value);
assert_eq!(result.is_ok(), expected_ok);
}
#[rstest]
fn min_value_validator_returns_too_small_error() {
let validator = MinValueValidator::new(10);
let result = validator.validate(&5);
match result {
Err(ValidationError::TooSmall { value, min }) => {
assert_eq!(value, "5");
assert_eq!(min, "10");
}
_ => panic!("Expected TooSmall error"),
}
}
#[rstest]
fn max_value_validator_returns_too_large_error() {
let validator = MaxValueValidator::new(20);
let result = validator.validate(&25);
match result {
Err(ValidationError::TooLarge { value, max }) => {
assert_eq!(value, "25");
assert_eq!(max, "20");
}
_ => panic!("Expected TooLarge error"),
}
}
#[rstest]
fn range_validator_within_range() {
let validator = RangeValidator::new(10, 20);
let at_min = validator.validate(&10);
let in_middle = validator.validate(&15);
let at_max = validator.validate(&20);
assert!(at_min.is_ok());
assert!(in_middle.is_ok());
assert!(at_max.is_ok());
}
#[rstest]
fn range_validator_outside_range() {
let validator = RangeValidator::new(10, 20);
let below = validator.validate(&5);
let above = validator.validate(&25);
assert!(below.is_err());
assert!(above.is_err());
}
#[rstest]
fn numeric_validators_with_f64() {
let min_validator = MinValueValidator::new(0.0f64);
let max_validator = MaxValueValidator::new(1.0f64);
let min_ok = min_validator.validate(&0.5f64);
let min_fail = min_validator.validate(&-0.1f64);
let max_ok = max_validator.validate(&0.5f64);
let max_fail = max_validator.validate(&1.1f64);
assert!(min_ok.is_ok());
assert!(min_fail.is_err());
assert!(max_ok.is_ok());
assert!(max_fail.is_err());
}
#[rstest]
fn min_length_validator_with_str_and_string() {
let validator = MinLengthValidator::new(3);
let s = String::from("hello");
let s2 = String::from("ab");
let str_ok = validator.validate("hello");
let str_err = validator.validate("ab");
let string_ok = validator.validate(&s);
let string_err = validator.validate(&s2);
assert!(str_ok.is_ok());
assert!(str_err.is_err());
assert!(string_ok.is_ok());
assert!(string_err.is_err());
}
#[rstest]
fn max_length_validator_with_str_and_string() {
let validator = MaxLengthValidator::new(5);
let s = String::from("hi");
let s2 = String::from("toolong");
let str_ok = validator.validate("hello");
let str_err = validator.validate("toolong");
let string_ok = validator.validate(&s);
let string_err = validator.validate(&s2);
assert!(str_ok.is_ok());
assert!(str_err.is_err());
assert!(string_ok.is_ok());
assert!(string_err.is_err());
}
#[rstest]
fn min_length_returns_too_short_error_with_details() {
let validator = MinLengthValidator::new(10);
let result = validator.validate("short");
match result {
Err(ValidationError::TooShort { length, min }) => {
assert_eq!(length, 5);
assert_eq!(min, 10);
}
_ => panic!("Expected TooShort error"),
}
}
#[rstest]
fn max_length_returns_too_long_error_with_details() {
let validator = MaxLengthValidator::new(3);
let result = validator.validate("toolong");
match result {
Err(ValidationError::TooLong { length, max }) => {
assert_eq!(length, 7);
assert_eq!(max, 3);
}
_ => panic!("Expected TooLong error"),
}
}
#[rstest]
#[case("my-valid-slug", true)]
#[case("my_slug_123", true)]
#[case("simple", true)]
#[case("invalid slug", false)]
#[case("invalid!slug", false)]
#[case("", false)]
fn slug_validator(#[case] input: &str, #[case] expected_ok: bool) {
let validator = SlugValidator::new();
let result = validator.validate(input);
assert_eq!(
result.is_ok(),
expected_ok,
"Slug '{}' expected {}",
input,
if expected_ok { "valid" } else { "invalid" }
);
}
#[rstest]
fn slug_validator_returns_invalid_slug_error() {
let validator = SlugValidator::new();
let result = validator.validate("invalid slug!");
assert!(matches!(result, Err(ValidationError::InvalidSlug(_))));
}
#[rstest]
#[case("550e8400-e29b-41d4-a716-446655440000", true)]
#[case("6ba7b810-9dad-11d1-80b4-00c04fd430c8", true)]
#[case("not-a-uuid", false)]
#[case("550e8400-e29b-41d4-a716", false)]
#[case("", false)]
fn uuid_validator(#[case] input: &str, #[case] expected_ok: bool) {
let validator = UUIDValidator::new();
let result = validator.validate(input);
assert_eq!(
result.is_ok(),
expected_ok,
"UUID '{}' expected {}",
input,
if expected_ok { "valid" } else { "invalid" }
);
}
#[rstest]
fn uuid_validator_returns_invalid_uuid_error() {
let validator = UUIDValidator::new();
let result = validator.validate("not-a-uuid");
assert!(matches!(result, Err(ValidationError::InvalidUUID(_))));
}
#[rstest]
#[case("2024-01-15", true)]
#[case("2024-12-31", true)]
#[case("2024-02-29", true)] #[case("not-a-date", false)]
#[case("2024-13-01", false)] #[case("2024-01-32", false)] fn date_validator(#[case] input: &str, #[case] expected_ok: bool) {
let validator = DateValidator::new();
let result = validator.validate(input);
assert_eq!(
result.is_ok(),
expected_ok,
"Date '{}' expected {}",
input,
if expected_ok { "valid" } else { "invalid" }
);
}
#[rstest]
fn date_validator_returns_invalid_date_error() {
let validator = DateValidator::new();
let result = validator.validate("not-a-date");
assert!(matches!(result, Err(ValidationError::InvalidDate(_))));
}
#[rstest]
fn date_validator_custom_format() {
let validator = DateValidator::new().with_format("%d/%m/%Y");
let custom_format_ok = validator.validate("15/01/2024");
let default_format_err = validator.validate("2024-01-15");
assert!(custom_format_ok.is_ok());
assert!(default_format_err.is_err());
}
#[rstest]
fn email_validator_custom_message() {
let custom_msg = "Please enter a valid email address";
let validator = EmailValidator::new().with_message(custom_msg);
let result = validator.validate("invalid");
match result {
Err(ValidationError::Custom(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected Custom error with custom message"),
}
}
#[rstest]
fn ip_address_validator_custom_message() {
let custom_msg = "Invalid IP address format";
let validator = IPAddressValidator::new().with_message(custom_msg);
let result = validator.validate("invalid-ip");
match result {
Err(ValidationError::InvalidIPAddress(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected InvalidIPAddress error with custom message"),
}
}
#[rstest]
fn min_length_validator_custom_message() {
let custom_msg = "Username must be at least 5 characters";
let validator = MinLengthValidator::new(5).with_message(custom_msg);
let result = validator.validate("hi");
match result {
Err(ValidationError::Custom(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected Custom error with custom message"),
}
}
#[rstest]
fn max_length_validator_custom_message() {
let custom_msg = "Username must be at most 10 characters";
let validator = MaxLengthValidator::new(10).with_message(custom_msg);
let result = validator.validate("this is way too long for the field");
match result {
Err(ValidationError::Custom(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected Custom error with custom message"),
}
}
#[rstest]
fn min_value_validator_custom_message() {
let custom_msg = "Age must be at least 18";
let validator = MinValueValidator::new(18).with_message(custom_msg);
let result = validator.validate(&10);
match result {
Err(ValidationError::Custom(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected Custom error with custom message"),
}
}
#[rstest]
fn max_value_validator_custom_message() {
let custom_msg = "Quantity must be at most 100";
let validator = MaxValueValidator::new(100).with_message(custom_msg);
let result = validator.validate(&200);
match result {
Err(ValidationError::Custom(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected Custom error with custom message"),
}
}
#[rstest]
fn slug_validator_custom_message() {
let custom_msg = "Invalid URL slug format";
let validator = SlugValidator::new().with_message(custom_msg);
let result = validator.validate("invalid slug!");
match result {
Err(ValidationError::Custom(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected Custom error with custom message"),
}
}
#[rstest]
fn date_validator_custom_message() {
let custom_msg = "Please use YYYY-MM-DD format";
let validator = DateValidator::new().with_message(custom_msg);
let result = validator.validate("not-a-date");
match result {
Err(ValidationError::Custom(msg)) => {
assert_eq!(msg, custom_msg);
}
_ => panic!("Expected Custom error with custom message"),
}
}
#[rstest]
fn validation_error_invalid_email_display() {
let error = ValidationError::InvalidEmail("bad@".into());
let display = error.to_string();
assert_eq!(display, "Invalid email: bad@");
}
#[rstest]
fn validation_error_too_short_display() {
let error = ValidationError::TooShort { length: 3, min: 5 };
let display = error.to_string();
assert_eq!(display, "Length too short: 3 (minimum: 5)");
}
#[rstest]
fn validation_error_too_long_display() {
let error = ValidationError::TooLong {
length: 20,
max: 10,
};
let display = error.to_string();
assert_eq!(display, "Length too long: 20 (maximum: 10)");
}
#[rstest]
fn validation_error_too_small_display() {
let error = ValidationError::TooSmall {
value: "5".into(),
min: "10".into(),
};
let display = error.to_string();
assert_eq!(display, "Value too small: 5 (minimum: 10)");
}
#[rstest]
fn validation_error_too_large_display() {
let error = ValidationError::TooLarge {
value: "100".into(),
max: "50".into(),
};
let display = error.to_string();
assert_eq!(display, "Value too large: 100 (maximum: 50)");
}
#[rstest]
fn validation_error_invalid_ip_address_display() {
let error = ValidationError::InvalidIPAddress("bad-ip".into());
let display = error.to_string();
assert_eq!(display, "Invalid IP address: bad-ip");
}
#[rstest]
fn validation_error_invalid_slug_display() {
let error = ValidationError::InvalidSlug("bad slug".into());
let display = error.to_string();
assert_eq!(display, "Invalid slug: bad slug");
}
#[rstest]
fn validation_error_invalid_uuid_display() {
let error = ValidationError::InvalidUUID("bad-uuid".into());
let display = error.to_string();
assert_eq!(display, "Invalid UUID: bad-uuid");
}
#[rstest]
fn validation_error_custom_display() {
let error = ValidationError::Custom("custom message".into());
let display = error.to_string();
assert_eq!(display, "Custom validation error: custom message");
}
#[rstest]
fn validation_error_clone_and_eq() {
let error = ValidationError::InvalidEmail("test@".into());
let cloned = error.clone();
assert_eq!(error, cloned);
}
#[rstest]
fn validation_error_composite_failed_display() {
let error = ValidationError::CompositeValidationFailed("All validators failed".into());
let display = error.to_string();
assert_eq!(display, "Validation failed: All validators failed");
}
#[rstest]
fn validation_error_all_validators_failed_display() {
let error = ValidationError::AllValidatorsFailed {
errors: "error1; error2".into(),
};
let display = error.to_string();
assert_eq!(display, "All validators failed: error1; error2");
}
#[rstest]
fn nested_and_in_or_composition() {
let short_range = AndValidator::new(vec![
Box::new(MinLengthValidator::new(3)),
Box::new(MaxLengthValidator::new(10)),
]);
let or_validator = OrValidator::new(vec![
Box::new(short_range),
Box::new(MinLengthValidator::new(20)),
]);
let short_valid = or_validator.validate("hello"); let long_valid = or_validator.validate("this is a very long string indeed"); let fails_both = or_validator.validate("ab");
assert!(short_valid.is_ok());
assert!(long_valid.is_ok());
assert!(fails_both.is_err());
}
#[rstest]
fn and_validator_with_mixed_validator_types() {
let validator = AndValidator::new(vec![
Box::new(MinLengthValidator::new(3)),
Box::new(MaxLengthValidator::new(50)),
Box::new(SlugValidator::new()),
]);
let valid = validator.validate("valid-slug");
let too_short = validator.validate("ab");
let not_a_slug = validator.validate("invalid slug with spaces");
assert!(valid.is_ok());
assert!(too_short.is_err()); assert!(not_a_slug.is_err()); }