use fastxml::schema::xsd::facets::{
FacetConstraints, FacetError, FacetValidator, WhitespaceHandling,
};
mod exclusive_bounds {
use super::*;
#[test]
fn test_min_exclusive_violation() {
let mut constraints = FacetConstraints::new();
constraints.min_exclusive = Some("10".to_string());
let validator = FacetValidator::new(&constraints);
let result = validator.validate("10");
assert!(
matches!(result, Err(FacetError::BelowMinExclusive { .. })),
"Value equal to minExclusive should fail, got: {:?}",
result
);
let result = validator.validate("5");
assert!(
matches!(result, Err(FacetError::BelowMinExclusive { .. })),
"Value below minExclusive should fail, got: {:?}",
result
);
let result = validator.validate("11");
assert!(result.is_ok(), "Value above minExclusive should pass");
}
#[test]
fn test_max_exclusive_violation() {
let mut constraints = FacetConstraints::new();
constraints.max_exclusive = Some("100".to_string());
let validator = FacetValidator::new(&constraints);
let result = validator.validate("100");
assert!(
matches!(result, Err(FacetError::AboveMaxExclusive { .. })),
"Value equal to maxExclusive should fail, got: {:?}",
result
);
let result = validator.validate("150");
assert!(
matches!(result, Err(FacetError::AboveMaxExclusive { .. })),
"Value above maxExclusive should fail, got: {:?}",
result
);
let result = validator.validate("99");
assert!(result.is_ok(), "Value below maxExclusive should pass");
}
#[test]
fn test_exclusive_bounds_combined() {
let mut constraints = FacetConstraints::new();
constraints.min_exclusive = Some("0".to_string());
constraints.max_exclusive = Some("10".to_string());
let validator = FacetValidator::new(&constraints);
assert!(
validator.validate("0").is_err(),
"Value at minExclusive should fail"
);
assert!(
validator.validate("10").is_err(),
"Value at maxExclusive should fail"
);
assert!(validator.validate("1").is_ok(), "Value 1 should pass");
assert!(validator.validate("5").is_ok(), "Value 5 should pass");
assert!(validator.validate("9").is_ok(), "Value 9 should pass");
}
#[test]
fn test_exclusive_with_decimal_values() {
let mut constraints = FacetConstraints::new();
constraints.min_exclusive = Some("0.0".to_string());
constraints.max_exclusive = Some("1.0".to_string());
let validator = FacetValidator::new(&constraints);
assert!(
validator.validate("0.0").is_err(),
"Value at minExclusive should fail"
);
assert!(
validator.validate("1.0").is_err(),
"Value at maxExclusive should fail"
);
assert!(validator.validate("0.5").is_ok(), "Value 0.5 should pass");
assert!(
validator.validate("0.001").is_ok(),
"Value just above 0 should pass"
);
assert!(
validator.validate("0.999").is_ok(),
"Value just below 1 should pass"
);
}
}
mod pattern_validation {
use super::*;
#[test]
fn test_pattern_violation() {
let mut constraints = FacetConstraints::new().with_pattern(r"[A-Z]{3}-[0-9]{4}");
constraints.compile_patterns().unwrap();
let validator = FacetValidator::new(&constraints);
let result = validator.validate("ABC-1234");
assert!(result.is_ok(), "Valid pattern should pass");
let result = validator.validate("abc-1234");
assert!(
matches!(&result, Err(FacetError::PatternMismatch { value, .. }) if value == "abc-1234"),
"Lowercase should fail, got: {:?}",
result
);
let result = validator.validate("ABC1234");
assert!(
matches!(result, Err(FacetError::PatternMismatch { .. })),
"Missing hyphen should fail"
);
let result = validator.validate("AB-1234");
assert!(
matches!(result, Err(FacetError::PatternMismatch { .. })),
"Too few letters should fail"
);
}
#[test]
fn test_multiple_patterns_all_must_match() {
let mut constraints = FacetConstraints::new()
.with_pattern(r"[a-zA-Z]+") .with_pattern(r".{5,10}"); constraints.compile_patterns().unwrap();
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("Hello").is_ok());
assert!(validator.validate("HelloWorld").is_ok());
let result = validator.validate("Hi");
assert!(
matches!(result, Err(FacetError::PatternMismatch { .. })),
"Too short should fail"
);
let result = validator.validate("Hello123");
assert!(
matches!(result, Err(FacetError::PatternMismatch { .. })),
"Contains numbers should fail"
);
}
#[test]
fn test_email_pattern() {
let mut constraints =
FacetConstraints::new().with_pattern(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}");
constraints.compile_patterns().unwrap();
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("test@example.com").is_ok());
assert!(validator.validate("user.name@domain.co.jp").is_ok());
assert!(
validator.validate("invalid-email").is_err(),
"Missing @ should fail"
);
assert!(
validator.validate("@domain.com").is_err(),
"Missing local part should fail"
);
}
#[test]
fn test_invalid_pattern_regex() {
let mut constraints = FacetConstraints::new().with_pattern(r"[invalid(");
let _ = constraints.compile_patterns();
let validator = FacetValidator::new(&constraints);
let result = validator.validate("test");
assert!(
matches!(result, Err(FacetError::InvalidPattern { .. })),
"Invalid pattern regex should return InvalidPattern error, got: {:?}",
result
);
}
}
mod digit_constraints {
use super::*;
#[test]
fn test_total_digits_violation() {
let constraints = FacetConstraints {
total_digits: Some(5),
..Default::default()
};
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("12345").is_ok());
assert!(validator.validate("1234").is_ok());
assert!(validator.validate("123.45").is_ok());
let result = validator.validate("123456");
assert!(
matches!(result, Err(FacetError::TooManyDigits { found: 6, max: 5 })),
"6 digits should fail with totalDigits=5, got: {:?}",
result
);
}
#[test]
fn test_total_digits_with_leading_zeros() {
let constraints = FacetConstraints {
total_digits: Some(3),
..Default::default()
};
let validator = FacetValidator::new(&constraints);
assert!(
validator.validate("00123").is_ok(),
"Leading zeros should not count"
);
assert!(
validator.validate("0.123").is_ok(),
"Leading zero before decimal should not count"
);
}
#[test]
fn test_fraction_digits_violation() {
let constraints = FacetConstraints {
fraction_digits: Some(2),
..Default::default()
};
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("1.23").is_ok());
assert!(validator.validate("1.2").is_ok());
assert!(validator.validate("1").is_ok());
let result = validator.validate("1.234");
assert!(
matches!(
result,
Err(FacetError::TooManyFractionDigits { found: 3, max: 2 })
),
"3 fraction digits should fail with fractionDigits=2, got: {:?}",
result
);
}
#[test]
fn test_total_and_fraction_digits_combined() {
let constraints = FacetConstraints {
total_digits: Some(5),
fraction_digits: Some(2),
..Default::default()
};
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("123.45").is_ok());
assert!(validator.validate("12.34").is_ok());
let result = validator.validate("1234.56");
assert!(
matches!(result, Err(FacetError::TooManyDigits { .. })),
"6 total digits should fail"
);
let result = validator.validate("12.345");
assert!(
matches!(result, Err(FacetError::TooManyFractionDigits { .. })),
"3 fraction digits should fail"
);
}
}
mod whitespace_handling {
use super::*;
#[test]
fn test_whitespace_preserve() {
let constraints = FacetConstraints::new()
.with_whitespace(WhitespaceHandling::Preserve)
.with_enumeration(vec!["hello world"]);
let validator = FacetValidator::new(&constraints);
assert!(
validator.validate("hello world").is_ok(),
"Exact match should pass"
);
assert!(
validator.validate("hello world").is_err(),
"Single space should fail"
);
}
#[test]
fn test_whitespace_replace() {
let constraints = FacetConstraints::new()
.with_whitespace(WhitespaceHandling::Replace)
.with_enumeration(vec!["hello world"]);
let validator = FacetValidator::new(&constraints);
assert!(
validator.validate("hello\tworld").is_ok(),
"Tab should be replaced with space"
);
assert!(
validator.validate("hello\nworld").is_ok(),
"Newline should be replaced with space"
);
}
#[test]
fn test_whitespace_collapse() {
let constraints = FacetConstraints::new()
.with_whitespace(WhitespaceHandling::Collapse)
.with_enumeration(vec!["hello world"]);
let validator = FacetValidator::new(&constraints);
assert!(
validator.validate("hello world").is_ok(),
"Multiple spaces should collapse"
);
assert!(
validator.validate(" hello world ").is_ok(),
"Leading/trailing spaces should be trimmed"
);
assert!(
validator.validate("hello\t\nworld").is_ok(),
"Mixed whitespace should collapse"
);
}
#[test]
fn test_whitespace_collapse_with_length() {
let constraints = FacetConstraints::new()
.with_whitespace(WhitespaceHandling::Collapse)
.with_min_length(5)
.with_max_length(15);
let validator = FacetValidator::new(&constraints);
let result = validator.validate(" a ");
assert!(
matches!(result, Err(FacetError::TooShort { .. })),
"Collapsed string is too short"
);
assert!(
validator.validate("hello world").is_ok(),
"Collapsed string should be valid length"
);
}
}
mod combined_facets {
use super::*;
#[test]
fn test_all_length_facets() {
let constraints = FacetConstraints::new()
.with_min_length(3)
.with_max_length(10);
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("ab").is_err()); assert!(validator.validate("abc").is_ok()); assert!(validator.validate("abcdef").is_ok()); assert!(validator.validate("abcdefghij").is_ok()); assert!(validator.validate("abcdefghijk").is_err()); }
#[test]
fn test_inclusive_and_exclusive_bounds() {
let mut constraints = FacetConstraints::new().with_min_inclusive("0");
constraints.max_exclusive = Some("100".to_string());
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("-1").is_err()); assert!(validator.validate("0").is_ok()); assert!(validator.validate("50").is_ok()); assert!(validator.validate("99").is_ok()); assert!(validator.validate("100").is_err()); }
#[test]
fn test_pattern_with_length() {
let mut constraints = FacetConstraints::new()
.with_pattern(r"[A-Z]+")
.with_min_length(2)
.with_max_length(5);
constraints.compile_patterns().unwrap();
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("A").is_err()); assert!(validator.validate("AB").is_ok()); assert!(validator.validate("ABCDE").is_ok()); assert!(validator.validate("ABCDEF").is_err()); assert!(validator.validate("abc").is_err()); }
#[test]
fn test_enumeration_with_whitespace() {
let constraints = FacetConstraints::new()
.with_whitespace(WhitespaceHandling::Collapse)
.with_enumeration(vec!["red", "green", "blue"]);
let validator = FacetValidator::new(&constraints);
assert!(validator.validate(" red ").is_ok());
assert!(validator.validate(" green ").is_ok());
assert!(validator.validate("\tblue\n").is_ok());
}
}
mod negative_values {
use super::*;
#[test]
fn test_negative_number_with_min_inclusive() {
let constraints = FacetConstraints::new().with_min_inclusive("-10");
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("-10").is_ok()); assert!(validator.validate("-5").is_ok()); assert!(validator.validate("0").is_ok()); assert!(validator.validate("-11").is_err()); assert!(validator.validate("-100").is_err()); }
#[test]
fn test_negative_number_with_max_inclusive() {
let constraints = FacetConstraints::new().with_max_inclusive("-5");
let validator = FacetValidator::new(&constraints);
assert!(validator.validate("-5").is_ok()); assert!(validator.validate("-10").is_ok()); assert!(validator.validate("-100").is_ok()); assert!(validator.validate("-4").is_err()); assert!(validator.validate("0").is_err()); }
}