Skip to main content

leptos_forms_rs/validation/
rules.rs

1//! Validator rules module - Validator enum and rule definitions
2//!
3//! This module provides the Validator enum and related types for defining
4//! validation rules, along with rule validation logic and validator configuration.
5
6use crate::core::types::FieldValue;
7use regex::Regex;
8use serde::{Deserialize, Serialize};
9
10/// Type alias for field validators
11pub type FieldValidator = Box<dyn Fn(&FieldValue) -> Result<(), String> + Send + Sync>;
12
13/// Validator types for field validation
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub enum Validator {
16    Required,
17    Email,
18    Url,
19    MinLength(usize),
20    MaxLength(usize),
21    Min(f64),
22    Max(f64),
23    Pattern(String),
24    Range(f64, f64),
25    Custom(String),
26}
27
28impl Validator {
29    /// Create a required validator
30    pub fn required() -> Self {
31        Self::Required
32    }
33
34    /// Create an email validator
35    pub fn email() -> Self {
36        Self::Email
37    }
38
39    /// Create a URL validator
40    pub fn url() -> Self {
41        Self::Url
42    }
43
44    /// Create a minimum length validator
45    pub fn min_length(min: usize) -> Self {
46        Self::MinLength(min)
47    }
48
49    /// Create a maximum length validator
50    pub fn max_length(max: usize) -> Self {
51        Self::MaxLength(max)
52    }
53
54    /// Create a minimum value validator
55    pub fn min(min: f64) -> Self {
56        Self::Min(min)
57    }
58
59    /// Create a maximum value validator
60    pub fn max(max: f64) -> Self {
61        Self::Max(max)
62    }
63
64    /// Create a pattern validator
65    pub fn pattern(pattern: String) -> Self {
66        Self::Pattern(pattern)
67    }
68
69    /// Create a range validator
70    pub fn range(min: f64, max: f64) -> Self {
71        Self::Range(min, max)
72    }
73
74    /// Create a custom validator
75    pub fn custom(name: String) -> Self {
76        Self::Custom(name)
77    }
78
79    /// Validate a value against this validator
80    pub fn validate(&self, value: &FieldValue) -> Result<(), String> {
81        match self {
82            Validator::Required => {
83                if value.is_empty() {
84                    Err("This field is required".to_string())
85                } else {
86                    Ok(())
87                }
88            }
89            Validator::Email => {
90                if let FieldValue::String(email) = value {
91                    let email_regex = Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap();
92                    if email_regex.is_match(email) {
93                        Ok(())
94                    } else {
95                        Err("Invalid email format".to_string())
96                    }
97                } else {
98                    Err("Email must be a string".to_string())
99                }
100            }
101            Validator::Url => {
102                if let FieldValue::String(url) = value {
103                    let url_regex = Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap();
104                    if url_regex.is_match(url) {
105                        Ok(())
106                    } else {
107                        Err("Invalid URL format".to_string())
108                    }
109                } else {
110                    Err("URL must be a string".to_string())
111                }
112            }
113            Validator::MinLength(min_len) => {
114                if let FieldValue::String(s) = value {
115                    if s.len() >= *min_len {
116                        Ok(())
117                    } else {
118                        Err(format!("Minimum length is {} characters", min_len))
119                    }
120                } else {
121                    Err("Value must be a string".to_string())
122                }
123            }
124            Validator::MaxLength(max_len) => {
125                if let FieldValue::String(s) = value {
126                    if s.len() <= *max_len {
127                        Ok(())
128                    } else {
129                        Err(format!("Maximum length is {} characters", max_len))
130                    }
131                } else {
132                    Err("Value must be a string".to_string())
133                }
134            }
135            Validator::Min(min_val) => {
136                if let Some(num) = value.as_number() {
137                    if num >= *min_val {
138                        Ok(())
139                    } else {
140                        Err(format!("Minimum value is {}", min_val))
141                    }
142                } else {
143                    Err("Value must be a number".to_string())
144                }
145            }
146            Validator::Max(max_val) => {
147                if let Some(num) = value.as_number() {
148                    if num <= *max_val {
149                        Ok(())
150                    } else {
151                        Err(format!("Maximum value is {}", max_val))
152                    }
153                } else {
154                    Err("Value must be a number".to_string())
155                }
156            }
157            Validator::Pattern(pattern) => {
158                if let FieldValue::String(s) = value {
159                    let regex = Regex::new(pattern).map_err(|_| "Invalid pattern".to_string())?;
160                    if regex.is_match(s) {
161                        Ok(())
162                    } else {
163                        Err("Value doesn't match required pattern".to_string())
164                    }
165                } else {
166                    Err("Value must be a string".to_string())
167                }
168            }
169            Validator::Range(min, max) => {
170                if let Some(num) = value.as_number() {
171                    if num >= *min && num <= *max {
172                        Ok(())
173                    } else {
174                        Err(format!("Value must be between {} and {}", min, max))
175                    }
176                } else {
177                    Err("Value must be a number".to_string())
178                }
179            }
180            Validator::Custom(_name) => {
181                // Custom validators are handled by the validation engine
182                Err("Custom validator not implemented".to_string())
183            }
184        }
185    }
186
187    /// Get a human-readable description of this validator
188    pub fn description(&self) -> String {
189        match self {
190            Validator::Required => "Required field".to_string(),
191            Validator::Email => "Valid email address".to_string(),
192            Validator::Url => "Valid URL".to_string(),
193            Validator::MinLength(min) => format!("Minimum {} characters", min),
194            Validator::MaxLength(max) => format!("Maximum {} characters", max),
195            Validator::Min(min) => format!("Minimum value {}", min),
196            Validator::Max(max) => format!("Maximum value {}", max),
197            Validator::Pattern(pattern) => format!("Must match pattern: {}", pattern),
198            Validator::Range(min, max) => format!("Value between {} and {}", min, max),
199            Validator::Custom(name) => format!("Custom validation: {}", name),
200        }
201    }
202
203    /// Check if this validator is a custom validator
204    pub fn is_custom(&self) -> bool {
205        matches!(self, Validator::Custom(_))
206    }
207
208    /// Get the custom validator name if this is a custom validator
209    pub fn custom_name(&self) -> Option<&str> {
210        if let Validator::Custom(name) = self {
211            Some(name)
212        } else {
213            None
214        }
215    }
216}
217
218impl std::fmt::Display for Validator {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        write!(f, "{}", self.description())
221    }
222}