Skip to main content

ratatui_form/validation/
rules.rs

1//! Built-in validation rules.
2
3use crate::validation::Validator;
4
5/// Validates that a field is not empty.
6pub struct Required;
7
8impl Validator for Required {
9    fn validate(&self, value: &str) -> Result<(), String> {
10        if value.trim().is_empty() {
11            Err("This field is required".to_string())
12        } else {
13            Ok(())
14        }
15    }
16}
17
18/// Validates that a field contains a valid email address.
19pub struct Email;
20
21impl Validator for Email {
22    fn validate(&self, value: &str) -> Result<(), String> {
23        if value.is_empty() {
24            return Ok(()); // Empty is OK, use Required for that
25        }
26
27        // Simple email validation
28        let parts: Vec<&str> = value.split('@').collect();
29        if parts.len() != 2 {
30            return Err("Invalid email address".to_string());
31        }
32
33        let (local, domain) = (parts[0], parts[1]);
34
35        if local.is_empty() || domain.is_empty() {
36            return Err("Invalid email address".to_string());
37        }
38
39        if !domain.contains('.') {
40            return Err("Invalid email address".to_string());
41        }
42
43        let domain_parts: Vec<&str> = domain.split('.').collect();
44        if domain_parts.iter().any(|p| p.is_empty()) {
45            return Err("Invalid email address".to_string());
46        }
47
48        Ok(())
49    }
50}
51
52/// Validates minimum string length.
53pub struct MinLength(pub usize);
54
55impl Validator for MinLength {
56    fn validate(&self, value: &str) -> Result<(), String> {
57        if value.is_empty() {
58            return Ok(()); // Empty is OK, use Required for that
59        }
60
61        if value.len() < self.0 {
62            Err(format!("Must be at least {} characters", self.0))
63        } else {
64            Ok(())
65        }
66    }
67}
68
69/// Validates maximum string length.
70pub struct MaxLength(pub usize);
71
72impl Validator for MaxLength {
73    fn validate(&self, value: &str) -> Result<(), String> {
74        if value.len() > self.0 {
75            Err(format!("Must be at most {} characters", self.0))
76        } else {
77            Ok(())
78        }
79    }
80}
81
82/// Validates against a regex pattern.
83pub struct Pattern {
84    regex: regex::Regex,
85    message: String,
86}
87
88impl Pattern {
89    /// Creates a new pattern validator.
90    ///
91    /// # Panics
92    /// Panics if the pattern is not a valid regex.
93    pub fn new(pattern: &str, message: impl Into<String>) -> Self {
94        Self {
95            regex: regex::Regex::new(pattern).expect("Invalid regex pattern"),
96            message: message.into(),
97        }
98    }
99
100    /// Creates a US ZIP code validator (5 digits or 5+4 format).
101    pub fn zip_code() -> Self {
102        Self::new(r"^\d{5}(-\d{4})?$", "Invalid ZIP code format")
103    }
104
105    /// Creates a US phone number validator.
106    pub fn phone() -> Self {
107        Self::new(
108            r"^(\+1[-.\s]?)?(\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}$",
109            "Invalid phone number format",
110        )
111    }
112
113    /// Creates a date validator (YYYY-MM-DD format).
114    pub fn date() -> Self {
115        Self::new(r"^\d{4}-\d{2}-\d{2}$", "Invalid date format (use YYYY-MM-DD)")
116    }
117}
118
119impl Validator for Pattern {
120    fn validate(&self, value: &str) -> Result<(), String> {
121        if value.is_empty() {
122            return Ok(()); // Empty is OK, use Required for that
123        }
124
125        if self.regex.is_match(value) {
126            Ok(())
127        } else {
128            Err(self.message.clone())
129        }
130    }
131}