1use std::fmt;
26
27use crate::{
28 error::{FraiseQLError, Result},
29 validation::rules::ValidationRule,
30};
31
32#[derive(Debug, Clone)]
34pub struct CompositeError {
35 pub operator: CompositeOperator,
37 pub errors: Vec<String>,
39 pub field: String,
41}
42
43impl fmt::Display for CompositeError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "{}", self.operator)?;
46 if !self.errors.is_empty() {
47 write!(f, ": {}", self.errors.join("; "))?;
48 }
49 Ok(())
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum CompositeOperator {
56 All,
58 Any,
60 Not,
62 Optional,
64}
65
66impl fmt::Display for CompositeOperator {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::All => write!(f, "All validators must pass"),
70 Self::Any => write!(f, "At least one validator must pass"),
71 Self::Not => write!(f, "Validator must fail"),
72 Self::Optional => write!(f, "Optional validation"),
73 }
74 }
75}
76
77pub fn validate_all(
92 rules: &[ValidationRule],
93 field_value: &str,
94 field_name: &str,
95 is_present: bool,
96) -> Result<()> {
97 let mut errors = Vec::new();
98
99 for rule in rules {
100 if let Err(e) = validate_single_rule(rule, field_value, field_name, is_present) {
101 errors.push(format!("{}", e));
102 }
103 }
104
105 if !errors.is_empty() {
106 return Err(FraiseQLError::Validation {
107 message: format!(
108 "All validators must pass for '{}': {}",
109 field_name,
110 errors.join("; ")
111 ),
112 path: Some(field_name.to_string()),
113 });
114 }
115
116 Ok(())
117}
118
119pub fn validate_any(
134 rules: &[ValidationRule],
135 field_value: &str,
136 field_name: &str,
137 is_present: bool,
138) -> Result<()> {
139 let mut errors = Vec::new();
140 let mut passed_count = 0;
141
142 for rule in rules {
143 match validate_single_rule(rule, field_value, field_name, is_present) {
144 Ok(()) => {
145 passed_count += 1;
146 },
147 Err(e) => {
148 errors.push(format!("{}", e));
149 },
150 }
151 }
152
153 if passed_count == 0 {
154 return Err(FraiseQLError::Validation {
155 message: format!(
156 "At least one validator must pass for '{}': {}",
157 field_name,
158 errors.join("; ")
159 ),
160 path: Some(field_name.to_string()),
161 });
162 }
163
164 Ok(())
165}
166
167pub fn validate_not(
182 rule: &ValidationRule,
183 field_value: &str,
184 field_name: &str,
185 is_present: bool,
186) -> Result<()> {
187 match validate_single_rule(rule, field_value, field_name, is_present) {
188 Ok(()) => Err(FraiseQLError::Validation {
189 message: format!("Validator for '{}' must fail but passed", field_name),
190 path: Some(field_name.to_string()),
191 }),
192 Err(_) => Ok(()), }
194}
195
196pub fn validate_optional(
211 rule: &ValidationRule,
212 field_value: &str,
213 field_name: &str,
214 is_present: bool,
215) -> Result<()> {
216 if !is_present {
217 return Ok(());
218 }
219
220 validate_single_rule(rule, field_value, field_name, is_present)
221}
222
223fn validate_single_rule(
228 rule: &ValidationRule,
229 field_value: &str,
230 field_name: &str,
231 _is_present: bool,
232) -> Result<()> {
233 match rule {
234 ValidationRule::Required => {
235 if field_value.is_empty() {
236 return Err(FraiseQLError::Validation {
237 message: format!("Field '{}' is required", field_name),
238 path: Some(field_name.to_string()),
239 });
240 }
241 Ok(())
242 },
243 ValidationRule::Pattern { pattern, message } => {
244 if let Ok(regex) = regex::Regex::new(pattern) {
245 if !regex.is_match(field_value) {
246 return Err(FraiseQLError::Validation {
247 message: message.clone().unwrap_or_else(|| {
248 format!("'{}' must match pattern: {}", field_name, pattern)
249 }),
250 path: Some(field_name.to_string()),
251 });
252 }
253 }
254 Ok(())
255 },
256 ValidationRule::Length { min, max } => {
257 let len = field_value.len();
258 if let Some(min_len) = min {
259 if len < *min_len {
260 return Err(FraiseQLError::Validation {
261 message: format!(
262 "'{}' must be at least {} characters",
263 field_name, min_len
264 ),
265 path: Some(field_name.to_string()),
266 });
267 }
268 }
269 if let Some(max_len) = max {
270 if len > *max_len {
271 return Err(FraiseQLError::Validation {
272 message: format!("'{}' must be at most {} characters", field_name, max_len),
273 path: Some(field_name.to_string()),
274 });
275 }
276 }
277 Ok(())
278 },
279 ValidationRule::Enum { values } => {
280 if !values.contains(&field_value.to_string()) {
281 return Err(FraiseQLError::Validation {
282 message: format!("'{}' must be one of: {}", field_name, values.join(", ")),
283 path: Some(field_name.to_string()),
284 });
285 }
286 Ok(())
287 },
288 _ => Ok(()),
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296 use crate::validation::rules::ValidationRule;
297
298 #[test]
299 fn test_validate_all_passes() {
300 let rules = vec![
301 ValidationRule::Required,
302 ValidationRule::Length {
303 min: Some(5),
304 max: None,
305 },
306 ];
307 let result = validate_all(&rules, "hello123", "password", true);
308 assert!(result.is_ok());
309 }
310
311 #[test]
312 fn test_validate_all_fails_first() {
313 let rules = vec![
314 ValidationRule::Required,
315 ValidationRule::Length {
316 min: Some(10),
317 max: None,
318 },
319 ];
320 let result = validate_all(&rules, "short", "password", true);
321 assert!(result.is_err());
322 if let Err(FraiseQLError::Validation { message, .. }) = result {
323 assert!(message.contains("All validators must pass"));
324 }
325 }
326
327 #[test]
328 fn test_validate_all_fails_second() {
329 let rules = vec![
330 ValidationRule::Required,
331 ValidationRule::Pattern {
332 pattern: "^[a-z]+$".to_string(),
333 message: None,
334 },
335 ];
336 let result = validate_all(&rules, "Hello123", "username", true);
337 assert!(result.is_err());
338 }
339
340 #[test]
341 fn test_validate_all_multiple_failures() {
342 let rules = vec![
343 ValidationRule::Required,
344 ValidationRule::Length {
345 min: Some(10),
346 max: None,
347 },
348 ValidationRule::Pattern {
349 pattern: "^[a-z]+$".to_string(),
350 message: None,
351 },
352 ];
353 let result = validate_all(&rules, "Hi", "field", true);
354 assert!(result.is_err());
355 if let Err(FraiseQLError::Validation { message, .. }) = result {
356 assert!(message.contains("All validators must pass"));
357 }
358 }
359
360 #[test]
361 fn test_validate_any_passes_first() {
362 let rules = vec![
363 ValidationRule::Pattern {
364 pattern: "^[a-z]+$".to_string(),
365 message: None,
366 },
367 ValidationRule::Length {
368 min: Some(20),
369 max: None,
370 },
371 ];
372 let result = validate_any(&rules, "abc", "field", true);
373 assert!(result.is_ok());
374 }
375
376 #[test]
377 fn test_validate_any_passes_second() {
378 let rules = vec![
379 ValidationRule::Pattern {
380 pattern: "^[a-z]+$".to_string(),
381 message: None,
382 },
383 ValidationRule::Length {
384 min: Some(2),
385 max: None,
386 },
387 ];
388 let result = validate_any(&rules, "Hi", "field", true);
389 assert!(result.is_ok());
390 }
391
392 #[test]
393 fn test_validate_any_fails_all() {
394 let rules = vec![
395 ValidationRule::Pattern {
396 pattern: "^[a-z]+$".to_string(),
397 message: None,
398 },
399 ValidationRule::Length {
400 min: Some(20),
401 max: None,
402 },
403 ];
404 let result = validate_any(&rules, "Hi", "field", true);
405 assert!(result.is_err());
406 if let Err(FraiseQLError::Validation { message, .. }) = result {
407 assert!(message.contains("At least one validator must pass"));
408 }
409 }
410
411 #[test]
412 fn test_validate_any_multiple_passes() {
413 let rules = vec![
414 ValidationRule::Pattern {
415 pattern: "^[a-z]+$".to_string(),
416 message: None,
417 },
418 ValidationRule::Length {
419 min: Some(2),
420 max: None,
421 },
422 ValidationRule::Enum {
423 values: vec!["hello".to_string(), "world".to_string()],
424 },
425 ];
426 let result = validate_any(&rules, "hello", "field", true);
427 assert!(result.is_ok());
428 }
429
430 #[test]
431 fn test_validate_not_passes_when_rule_fails() {
432 let rule = ValidationRule::Pattern {
433 pattern: "^[0-9]+$".to_string(),
434 message: None,
435 };
436 let result = validate_not(&rule, "abc", "field", true);
437 assert!(result.is_ok());
438 }
439
440 #[test]
441 fn test_validate_not_fails_when_rule_passes() {
442 let rule = ValidationRule::Pattern {
443 pattern: "^[a-z]+$".to_string(),
444 message: None,
445 };
446 let result = validate_not(&rule, "abc", "field", true);
447 assert!(result.is_err());
448 }
449
450 #[test]
451 fn test_validate_optional_skips_absent() {
452 let rule = ValidationRule::Length {
453 min: Some(100),
454 max: None,
455 };
456 let result = validate_optional(&rule, "", "field", false);
457 assert!(result.is_ok());
458 }
459
460 #[test]
461 fn test_validate_optional_applies_present() {
462 let rule = ValidationRule::Length {
463 min: Some(5),
464 max: None,
465 };
466 let result = validate_optional(&rule, "hello", "field", true);
467 assert!(result.is_ok());
468 }
469
470 #[test]
471 fn test_validate_optional_fails_present() {
472 let rule = ValidationRule::Length {
473 min: Some(10),
474 max: None,
475 };
476 let result = validate_optional(&rule, "hi", "field", true);
477 assert!(result.is_err());
478 }
479
480 #[test]
481 fn test_composite_operator_display() {
482 assert_eq!(CompositeOperator::All.to_string(), "All validators must pass");
483 assert_eq!(CompositeOperator::Any.to_string(), "At least one validator must pass");
484 assert_eq!(CompositeOperator::Not.to_string(), "Validator must fail");
485 assert_eq!(CompositeOperator::Optional.to_string(), "Optional validation");
486 }
487
488 #[test]
489 fn test_nested_all_and_pattern() {
490 let rules = vec![
491 ValidationRule::Required,
492 ValidationRule::Length {
493 min: Some(8),
494 max: Some(20),
495 },
496 ValidationRule::Pattern {
497 pattern: "^[A-Za-z0-9]+$".to_string(),
498 message: Some("Username must be alphanumeric".to_string()),
499 },
500 ];
501 let result = validate_all(&rules, "User1234", "username", true);
502 assert!(result.is_ok());
503 }
504
505 #[test]
506 fn test_nested_all_fails_on_length() {
507 let rules = vec![
508 ValidationRule::Required,
509 ValidationRule::Length {
510 min: Some(8),
511 max: Some(20),
512 },
513 ValidationRule::Pattern {
514 pattern: "^[A-Za-z0-9]+$".to_string(),
515 message: Some("Username must be alphanumeric".to_string()),
516 },
517 ];
518 let result = validate_all(&rules, "Hi", "username", true);
519 assert!(result.is_err());
520 }
521
522 #[test]
523 fn test_strong_password_pattern_all() {
524 let rules = vec![
526 ValidationRule::Required,
527 ValidationRule::Length {
528 min: Some(8),
529 max: None,
530 },
531 ValidationRule::Pattern {
532 pattern: "^(?=.*[A-Z])".to_string(), message: Some("Must contain at least one uppercase letter".to_string()),
534 },
535 ];
536 let result = validate_all(&rules, "Password123", "password", true);
537 assert!(result.is_ok());
538 }
539
540 #[test]
541 fn test_enum_or_pattern_any() {
542 let rules = vec![
543 ValidationRule::Enum {
544 values: vec!["admin".to_string(), "user".to_string()],
545 },
546 ValidationRule::Pattern {
547 pattern: "^guest_[0-9]+$".to_string(),
548 message: None,
549 },
550 ];
551 let result = validate_any(&rules, "guest_123", "role", true);
552 assert!(result.is_ok());
553 }
554
555 #[test]
556 fn test_not_numeric_for_string_field() {
557 let rule = ValidationRule::Pattern {
558 pattern: "^[0-9]+$".to_string(),
559 message: None,
560 };
561 let result = validate_not(&rule, "abc123", "code", true);
562 assert!(result.is_ok());
564 }
565
566 #[test]
567 fn test_composite_error_display() {
568 let error = CompositeError {
569 operator: CompositeOperator::All,
570 errors: vec!["error1".to_string(), "error2".to_string()],
571 field: "field".to_string(),
572 };
573 let display_str = error.to_string();
574 assert!(display_str.contains("All validators must pass"));
575 assert!(display_str.contains("error1"));
576 assert!(display_str.contains("error2"));
577 }
578
579 #[test]
580 fn test_multiple_validators_with_required() {
581 let rules = vec![ValidationRule::Required];
582 let result = validate_all(&rules, "test", "field", true);
583 assert!(result.is_ok());
584 }
585
586 #[test]
587 fn test_empty_rules_all() {
588 let rules: Vec<ValidationRule> = vec![];
589 let result = validate_all(&rules, "test", "field", true);
590 assert!(result.is_ok());
591 }
592
593 #[test]
594 fn test_empty_rules_any() {
595 let rules: Vec<ValidationRule> = vec![];
596 let result = validate_any(&rules, "test", "field", true);
597 assert!(result.is_err());
599 }
600
601 #[test]
602 fn test_length_min_max() {
603 let rule = ValidationRule::Length {
604 min: Some(5),
605 max: Some(10),
606 };
607 let result = validate_single_rule(&rule, "hello", "password", true);
608 assert!(result.is_ok());
609
610 let result = validate_single_rule(&rule, "hi", "password", true);
611 assert!(result.is_err());
612
613 let result = validate_single_rule(&rule, "this_is_too_long", "password", true);
614 assert!(result.is_err());
615 }
616}