fraiseql_core/validation/
validators.rs1use regex::Regex;
7
8use super::rules::ValidationRule;
9use crate::error::{FraiseQLError, Result, ValidationFieldError};
10
11pub trait Validator {
13 fn validate(&self, value: &str, field: &str) -> Result<()>;
15}
16
17pub struct PatternValidator {
19 regex: Regex,
20 message: String,
21}
22
23impl PatternValidator {
24 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 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 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
66pub struct LengthValidator {
68 min: Option<usize>,
69 max: Option<usize>,
70}
71
72impl LengthValidator {
73 pub fn new(min: Option<usize>, max: Option<usize>) -> Self {
75 Self { min, max }
76 }
77
78 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 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
121pub struct RangeValidator {
123 min: Option<i64>,
124 max: Option<i64>,
125}
126
127impl RangeValidator {
128 pub fn new(min: Option<i64>, max: Option<i64>) -> Self {
130 Self { min, max }
131 }
132
133 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 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
159pub struct EnumValidator {
161 allowed_values: std::collections::HashSet<String>,
162}
163
164impl EnumValidator {
165 pub fn new(values: Vec<String>) -> Self {
167 Self {
168 allowed_values: values.into_iter().collect(),
169 }
170 }
171
172 pub fn validate_enum(&self, value: &str) -> bool {
174 self.allowed_values.contains(value)
175 }
176
177 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
206pub 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
225pub 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, }
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}