Skip to main content

karbon_framework/validation/constraints/string/
regex.rs

1use crate::validation::constraints::{Constraint, ConstraintResult, ConstraintViolation};
2
3/// Validates that a value matches a regular expression pattern.
4///
5/// Equivalent to Symfony's `Regex` constraint.
6pub struct Regex {
7    pub pattern: regex::Regex,
8    pub message: String,
9    pub should_match: bool,
10}
11
12impl Regex {
13    pub fn new(pattern: &str) -> Self {
14        Self {
15            pattern: regex::Regex::new(pattern).expect("Invalid regex pattern"),
16            message: "This value is not valid.".to_string(),
17            should_match: true,
18        }
19    }
20
21    pub fn with_message(mut self, message: impl Into<String>) -> Self {
22        self.message = message.into();
23        self
24    }
25
26    /// If set to false, the constraint will fail when the value matches the pattern.
27    pub fn should_match(mut self, should_match: bool) -> Self {
28        self.should_match = should_match;
29        self
30    }
31}
32
33impl Constraint for Regex {
34    fn validate(&self, value: &str) -> ConstraintResult {
35        let matches = self.pattern.is_match(value);
36
37        if matches != self.should_match {
38            return Err(ConstraintViolation::new(
39                self.name(),
40                &self.message,
41                value,
42            ));
43        }
44        Ok(())
45    }
46
47    fn name(&self) -> &'static str {
48        "Regex"
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_matching_pattern() {
58        let constraint = Regex::new(r"^\d{5}$"); // French postal code
59        assert!(constraint.validate("75001").is_ok());
60        assert!(constraint.validate("12345").is_ok());
61        assert!(constraint.validate("1234").is_err());
62        assert!(constraint.validate("123456").is_err());
63        assert!(constraint.validate("abcde").is_err());
64    }
65
66    #[test]
67    fn test_not_matching() {
68        let constraint = Regex::new(r"<script").should_match(false);
69        assert!(constraint.validate("hello world").is_ok());
70        assert!(constraint.validate("<script>alert('xss')</script>").is_err());
71    }
72
73    #[test]
74    fn test_complex_pattern() {
75        let constraint = Regex::new(r"^[A-Z]{2}-\d{3}$");
76        assert!(constraint.validate("FR-123").is_ok());
77        assert!(constraint.validate("US-456").is_ok());
78        assert!(constraint.validate("fr-123").is_err());
79        assert!(constraint.validate("FRA-123").is_err());
80    }
81
82    #[test]
83    fn test_custom_message() {
84        let constraint = Regex::new(r"^\d+$").with_message("Seuls les chiffres sont autorisés.");
85        let err = constraint.validate("abc").unwrap_err();
86        assert_eq!(err.message, "Seuls les chiffres sont autorisés.");
87    }
88}