Skip to main content

leptos_forms_rs/validation/
validators.rs

1//! Built-in validators module - Implementation of built-in validation functions
2//!
3//! This module provides comprehensive built-in validation functions including
4//! email validation, URL validation, length validation, pattern validation,
5//! number validation, and custom validator implementations.
6
7use crate::core::types::FieldValue;
8use regex::Regex;
9
10/// Built-in validators implementation
11pub struct Validators;
12
13impl Validators {
14    /// Check if a field is required
15    pub fn required(value: &FieldValue) -> Result<(), String> {
16        match value {
17            FieldValue::String(s) if s.trim().is_empty() => {
18                Err("This field is required".to_string())
19            }
20            FieldValue::Null => Err("This field is required".to_string()),
21            FieldValue::Array(arr) if arr.is_empty() => Err("This field is required".to_string()),
22            _ => Ok(()),
23        }
24    }
25
26    /// Validate email format
27    pub fn email(value: &FieldValue) -> Result<(), String> {
28        if let FieldValue::String(email) = value {
29            let email_regex = Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap();
30            if email_regex.is_match(email) {
31                Ok(())
32            } else {
33                Err("Invalid email format".to_string())
34            }
35        } else {
36            Err("Email must be a string".to_string())
37        }
38    }
39
40    /// Validate URL format
41    pub fn url(value: &FieldValue) -> Result<(), String> {
42        if let FieldValue::String(url) = value {
43            let url_regex = Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap();
44            if url_regex.is_match(url) {
45                Ok(())
46            } else {
47                Err("Invalid URL format".to_string())
48            }
49        } else {
50            Err("URL must be a string".to_string())
51        }
52    }
53
54    /// Check minimum length for strings
55    pub fn min_length(value: &FieldValue, min: usize) -> Result<(), String> {
56        if let FieldValue::String(s) = value {
57            if s.len() >= min {
58                Ok(())
59            } else {
60                Err(format!("Minimum length is {} characters", min))
61            }
62        } else {
63            Err("Value must be a string".to_string())
64        }
65    }
66
67    /// Check maximum length for strings
68    pub fn max_length(value: &FieldValue, max: usize) -> Result<(), String> {
69        if let FieldValue::String(s) = value {
70            if s.len() <= max {
71                Ok(())
72            } else {
73                Err(format!("Maximum length is {} characters", max))
74            }
75        } else {
76            Err("Value must be a string".to_string())
77        }
78    }
79
80    /// Check minimum value for numbers
81    pub fn min(value: &FieldValue, min: f64) -> Result<(), String> {
82        if let Some(num) = value.as_number() {
83            if num >= min {
84                Ok(())
85            } else {
86                Err(format!("Minimum value is {}", min))
87            }
88        } else {
89            Err("Value must be a number".to_string())
90        }
91    }
92
93    /// Check maximum value for numbers
94    pub fn max(value: &FieldValue, max: f64) -> Result<(), String> {
95        if let Some(num) = value.as_number() {
96            if num <= max {
97                Ok(())
98            } else {
99                Err(format!("Maximum value is {}", max))
100            }
101        } else {
102            Err("Value must be a number".to_string())
103        }
104    }
105
106    /// Validate against a regex pattern
107    pub fn pattern(value: &FieldValue, pattern: &str) -> Result<(), String> {
108        if let FieldValue::String(s) = value {
109            let regex = Regex::new(pattern).map_err(|_| "Invalid pattern".to_string())?;
110            if regex.is_match(s) {
111                Ok(())
112            } else {
113                Err("Value doesn't match required pattern".to_string())
114            }
115        } else {
116            Err("Value must be a string".to_string())
117        }
118    }
119
120    /// Validate phone number format
121    pub fn phone(value: &FieldValue) -> Result<(), String> {
122        if let FieldValue::String(phone) = value {
123            let phone_regex = Regex::new(r"^[\+]?[1-9][\d]{0,15}$").unwrap();
124            if phone_regex.is_match(phone) {
125                Ok(())
126            } else {
127                Err("Invalid phone number format".to_string())
128            }
129        } else {
130            Err("Phone number must be a string".to_string())
131        }
132    }
133
134    /// Validate postal code format (basic)
135    pub fn postal_code(value: &FieldValue) -> Result<(), String> {
136        if let FieldValue::String(code) = value {
137            let postal_regex = Regex::new(r"^\d{5}(-\d{4})?$").unwrap();
138            if postal_regex.is_match(code) {
139                Ok(())
140            } else {
141                Err("Invalid postal code format".to_string())
142            }
143        } else {
144            Err("Postal code must be a string".to_string())
145        }
146    }
147
148    /// Validate credit card number (Luhn algorithm)
149    pub fn credit_card(value: &FieldValue) -> Result<(), String> {
150        if let FieldValue::String(card) = value {
151            let digits: Vec<u32> = card
152                .chars()
153                .filter(|c| c.is_ascii_digit())
154                .map(|c| c.to_digit(10).unwrap())
155                .collect();
156
157            if digits.len() < 13 || digits.len() > 19 {
158                return Err("Invalid credit card number length".to_string());
159            }
160
161            // Luhn algorithm
162            let mut sum = 0;
163            let mut double = false;
164
165            for &digit in digits.iter().rev() {
166                if double {
167                    let doubled = digit * 2;
168                    sum += if doubled > 9 { doubled - 9 } else { doubled };
169                } else {
170                    sum += digit;
171                }
172                double = !double;
173            }
174
175            if sum % 10 == 0 {
176                Ok(())
177            } else {
178                Err("Invalid credit card number".to_string())
179            }
180        } else {
181            Err("Credit card number must be a string".to_string())
182        }
183    }
184
185    /// Validate date format (YYYY-MM-DD)
186    pub fn date(value: &FieldValue) -> Result<(), String> {
187        if let FieldValue::Date(_) = value {
188            Ok(())
189        } else if let FieldValue::String(date_str) = value {
190            let date_regex = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
191            if date_regex.is_match(date_str) {
192                Ok(())
193            } else {
194                Err("Invalid date format (YYYY-MM-DD)".to_string())
195            }
196        } else {
197            Err("Value must be a date".to_string())
198        }
199    }
200
201    /// Validate that value is a positive number
202    pub fn positive(value: &FieldValue) -> Result<(), String> {
203        if let Some(num) = value.as_number() {
204            if num > 0.0 {
205                Ok(())
206            } else {
207                Err("Value must be positive".to_string())
208            }
209        } else {
210            Err("Value must be a number".to_string())
211        }
212    }
213
214    /// Validate that value is a negative number
215    pub fn negative(value: &FieldValue) -> Result<(), String> {
216        if let Some(num) = value.as_number() {
217            if num < 0.0 {
218                Ok(())
219            } else {
220                Err("Value must be negative".to_string())
221            }
222        } else {
223            Err("Value must be a number".to_string())
224        }
225    }
226
227    /// Validate that value is an integer
228    pub fn integer(value: &FieldValue) -> Result<(), String> {
229        if let Some(num) = value.as_number() {
230            if num.fract() == 0.0 {
231                Ok(())
232            } else {
233                Err("Value must be an integer".to_string())
234            }
235        } else {
236            Err("Value must be a number".to_string())
237        }
238    }
239
240    /// Validate array length
241    pub fn array_length(value: &FieldValue, min: usize, max: usize) -> Result<(), String> {
242        if let FieldValue::Array(arr) = value {
243            if arr.len() >= min && arr.len() <= max {
244                Ok(())
245            } else {
246                Err(format!("Array must have between {} and {} items", min, max))
247            }
248        } else {
249            Err("Value must be an array".to_string())
250        }
251    }
252
253    /// Validate range for numbers
254    pub fn range(value: &FieldValue, min: f64, max: f64) -> Result<(), String> {
255        if let Some(num) = value.as_number() {
256            if num >= min && num <= max {
257                Ok(())
258            } else {
259                Err(format!("Value must be between {} and {}", min, max))
260            }
261        } else {
262            Err("Value must be a number".to_string())
263        }
264    }
265
266    /// Validate that value is not empty
267    pub fn not_empty(value: &FieldValue) -> Result<(), String> {
268        if value.is_empty() {
269            Err("Value cannot be empty".to_string())
270        } else {
271            Ok(())
272        }
273    }
274
275    /// Validate that value is empty
276    pub fn empty(value: &FieldValue) -> Result<(), String> {
277        if value.is_empty() {
278            Ok(())
279        } else {
280            Err("Value must be empty".to_string())
281        }
282    }
283}