Skip to main content

fraiseql_core/validation/
validators.rs

1//! Basic field validators for input validation.
2//!
3//! Provides simple validators for patterns, lengths, numeric ranges, and enums.
4//! These validators are combined to create comprehensive input validation rules.
5
6use regex::Regex;
7
8use super::rules::ValidationRule;
9use crate::error::{FraiseQLError, Result, ValidationFieldError};
10
11/// Basic validator trait for field validation.
12pub trait Validator {
13    /// Validate a value and return an error if validation fails.
14    fn validate(&self, value: &str, field: &str) -> Result<()>;
15}
16
17/// Pattern validator using regular expressions.
18pub struct PatternValidator {
19    regex:   Regex,
20    message: String,
21}
22
23impl PatternValidator {
24    /// Create a new pattern validator.
25    ///
26    /// # Errors
27    /// Returns error if the regex pattern is invalid.
28    pub fn new(pattern: impl Into<String>, message: impl Into<String>) -> Result<Self> {
29        let pattern_str = pattern.into();
30        let regex = Regex::new(&pattern_str)
31            .map_err(|e| FraiseQLError::validation(format!("Invalid regex pattern: {}", e)))?;
32        Ok(Self {
33            regex,
34            message: message.into(),
35        })
36    }
37
38    /// Create a new pattern validator with default message.
39    pub fn new_default_message(pattern: impl Into<String>) -> Result<Self> {
40        let pattern_str = pattern.into();
41        Self::new(pattern_str.clone(), format!("Value must match pattern: {}", pattern_str))
42    }
43
44    /// Validate that a value matches the pattern.
45    pub fn validate_pattern(&self, value: &str) -> bool {
46        self.regex.is_match(value)
47    }
48}
49
50impl Validator for PatternValidator {
51    fn validate(&self, value: &str, field: &str) -> Result<()> {
52        if self.validate_pattern(value) {
53            Ok(())
54        } else {
55            Err(FraiseQLError::Validation {
56                message: format!(
57                    "Field validation failed: {}",
58                    ValidationFieldError::new(field, "pattern", &self.message)
59                ),
60                path:    Some(field.to_string()),
61            })
62        }
63    }
64}
65
66/// String length validator.
67pub struct LengthValidator {
68    min: Option<usize>,
69    max: Option<usize>,
70}
71
72impl LengthValidator {
73    /// Create a new length validator.
74    pub fn new(min: Option<usize>, max: Option<usize>) -> Self {
75        Self { min, max }
76    }
77
78    /// Validate that a string is within the specified length bounds.
79    pub fn validate_length(&self, value: &str) -> bool {
80        let len = value.len();
81        if let Some(min) = self.min {
82            if len < min {
83                return false;
84            }
85        }
86        if let Some(max) = self.max {
87            if len > max {
88                return false;
89            }
90        }
91        true
92    }
93
94    /// Get a descriptive error message for length validation failure.
95    pub fn error_message(&self) -> String {
96        match (self.min, self.max) {
97            (Some(m), Some(x)) => format!("Length must be between {} and {}", m, x),
98            (Some(m), None) => format!("Length must be at least {}", m),
99            (None, Some(x)) => format!("Length must be at most {}", x),
100            (None, None) => "Length validation failed".to_string(),
101        }
102    }
103}
104
105impl Validator for LengthValidator {
106    fn validate(&self, value: &str, field: &str) -> Result<()> {
107        if self.validate_length(value) {
108            Ok(())
109        } else {
110            Err(FraiseQLError::Validation {
111                message: format!(
112                    "Field validation failed: {}",
113                    ValidationFieldError::new(field, "length", self.error_message())
114                ),
115                path:    Some(field.to_string()),
116            })
117        }
118    }
119}
120
121/// Numeric range validator.
122pub struct RangeValidator {
123    min: Option<i64>,
124    max: Option<i64>,
125}
126
127impl RangeValidator {
128    /// Create a new range validator.
129    pub fn new(min: Option<i64>, max: Option<i64>) -> Self {
130        Self { min, max }
131    }
132
133    /// Validate that a number is within the specified range.
134    pub fn validate_range(&self, value: i64) -> bool {
135        if let Some(min) = self.min {
136            if value < min {
137                return false;
138            }
139        }
140        if let Some(max) = self.max {
141            if value > max {
142                return false;
143            }
144        }
145        true
146    }
147
148    /// Get a descriptive error message for range validation failure.
149    pub fn error_message(&self) -> String {
150        match (self.min, self.max) {
151            (Some(m), Some(x)) => format!("Value must be between {} and {}", m, x),
152            (Some(m), None) => format!("Value must be at least {}", m),
153            (None, Some(x)) => format!("Value must be at most {}", x),
154            (None, None) => "Range validation failed".to_string(),
155        }
156    }
157}
158
159/// Enum validator - allows only specified values.
160pub struct EnumValidator {
161    allowed_values: std::collections::HashSet<String>,
162}
163
164impl EnumValidator {
165    /// Create a new enum validator.
166    pub fn new(values: Vec<String>) -> Self {
167        Self {
168            allowed_values: values.into_iter().collect(),
169        }
170    }
171
172    /// Validate that a value is in the allowed set.
173    pub fn validate_enum(&self, value: &str) -> bool {
174        self.allowed_values.contains(value)
175    }
176
177    /// Get the list of allowed values.
178    pub fn allowed_values(&self) -> Vec<&str> {
179        self.allowed_values.iter().map(|s| s.as_str()).collect()
180    }
181}
182
183impl Validator for EnumValidator {
184    fn validate(&self, value: &str, field: &str) -> Result<()> {
185        if self.validate_enum(value) {
186            Ok(())
187        } else {
188            let mut allowed_vec: Vec<_> = self.allowed_values.iter().cloned().collect();
189            allowed_vec.sort();
190            let allowed = allowed_vec.join(", ");
191            Err(FraiseQLError::Validation {
192                message: format!(
193                    "Field validation failed: {}",
194                    ValidationFieldError::new(
195                        field,
196                        "enum",
197                        format!("Must be one of: {}", allowed)
198                    )
199                ),
200                path:    Some(field.to_string()),
201            })
202        }
203    }
204}
205
206/// Required field validator.
207pub struct RequiredValidator;
208
209impl Validator for RequiredValidator {
210    fn validate(&self, value: &str, field: &str) -> Result<()> {
211        if value.is_empty() {
212            Err(FraiseQLError::Validation {
213                message: format!(
214                    "Field validation failed: {}",
215                    ValidationFieldError::new(field, "required", "Field is required")
216                ),
217                path:    Some(field.to_string()),
218            })
219        } else {
220            Ok(())
221        }
222    }
223}
224
225/// Create a validator from a ValidationRule.
226pub fn create_validator_from_rule(rule: &ValidationRule) -> Option<Box<dyn Validator>> {
227    match rule {
228        ValidationRule::Pattern { pattern, message } => {
229            let msg = message.clone().unwrap_or_else(|| "Pattern mismatch".to_string());
230            PatternValidator::new(pattern.clone(), msg)
231                .ok()
232                .map(|v| Box::new(v) as Box<dyn Validator>)
233        },
234        ValidationRule::Length { min, max } => {
235            Some(Box::new(LengthValidator::new(*min, *max)) as Box<dyn Validator>)
236        },
237        ValidationRule::Enum { values } => {
238            Some(Box::new(EnumValidator::new(values.clone())) as Box<dyn Validator>)
239        },
240        ValidationRule::Required => Some(Box::new(RequiredValidator) as Box<dyn Validator>),
241        _ => None, // Other validators handled separately
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_pattern_validator() {
251        let validator = PatternValidator::new_default_message("^[a-z]+$").unwrap();
252        assert!(validator.validate_pattern("hello"));
253        assert!(!validator.validate_pattern("Hello"));
254        assert!(!validator.validate_pattern("hello123"));
255    }
256
257    #[test]
258    fn test_pattern_validator_validation() {
259        let validator = PatternValidator::new_default_message("^[a-z]+$").unwrap();
260        assert!(validator.validate("hello", "name").is_ok());
261        assert!(validator.validate("Hello", "name").is_err());
262    }
263
264    #[test]
265    fn test_length_validator() {
266        let validator = LengthValidator::new(Some(3), Some(10));
267        assert!(validator.validate_length("hello"));
268        assert!(!validator.validate_length("ab"));
269        assert!(!validator.validate_length("this is too long"));
270    }
271
272    #[test]
273    fn test_length_validator_error_message() {
274        let validator = LengthValidator::new(Some(5), Some(10));
275        let msg = validator.error_message();
276        assert!(msg.contains("5"));
277        assert!(msg.contains("10"));
278    }
279
280    #[test]
281    fn test_range_validator() {
282        let validator = RangeValidator::new(Some(0), Some(100));
283        assert!(validator.validate_range(50));
284        assert!(!validator.validate_range(-1));
285        assert!(!validator.validate_range(101));
286    }
287
288    #[test]
289    fn test_enum_validator() {
290        let validator = EnumValidator::new(vec![
291            "active".to_string(),
292            "inactive".to_string(),
293            "pending".to_string(),
294        ]);
295        assert!(validator.validate_enum("active"));
296        assert!(!validator.validate_enum("unknown"));
297    }
298
299    #[test]
300    fn test_required_validator() {
301        let validator = RequiredValidator;
302        assert!(validator.validate("hello", "name").is_ok());
303        assert!(validator.validate("", "name").is_err());
304    }
305
306    #[test]
307    fn test_create_validator_from_rule() {
308        let rule = ValidationRule::Pattern {
309            pattern: "^test".to_string(),
310            message: None,
311        };
312        let validator = create_validator_from_rule(&rule);
313        assert!(validator.is_some());
314    }
315}