1use serde::{Deserialize, Serialize};
31
32#[derive(Debug, Clone, Serialize, Deserialize, Default)]
34pub struct StringValidatorConfig {
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub min_length: Option<usize>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub max_length: Option<usize>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub pattern: Option<String>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub pattern_message: Option<String>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, Default)]
54pub struct NumericValidatorConfig {
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub min: Option<f64>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub max: Option<f64>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub message: Option<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, Default)]
70pub struct EmailValidatorConfig {
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub min_length: Option<usize>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub max_length: Option<usize>,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub message: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, Default)]
86pub struct UrlValidatorConfig {
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub message: Option<String>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize, Default)]
94pub struct IpAddressValidatorConfig {
95 #[serde(default = "default_true")]
97 pub allow_ipv4: bool,
98
99 #[serde(default = "default_true")]
101 pub allow_ipv6: bool,
102
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub message: Option<String>,
106}
107
108fn default_true() -> bool {
109 true
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, Default)]
114pub struct PhoneNumberValidatorConfig {
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub country_codes: Option<Vec<String>>,
118
119 #[serde(default)]
121 pub allow_extensions: bool,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub message: Option<String>,
126}
127
128#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
130#[serde(rename_all = "lowercase")]
131pub enum ColorFormatConfig {
132 Hex,
134 Rgb,
136 Rgba,
138 Hsl,
140 Hsla,
142 Any,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, Default)]
148pub struct ColorValidatorConfig {
149 #[serde(skip_serializing_if = "Option::is_none")]
151 pub allowed_formats: Option<Vec<ColorFormatConfig>>,
152
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub message: Option<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, Default)]
160pub struct UuidValidatorConfig {
161 #[serde(skip_serializing_if = "Option::is_none")]
163 pub message: Option<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, Default)]
168pub struct SlugValidatorConfig {
169 #[serde(default)]
171 pub allow_unicode: bool,
172
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub message: Option<String>,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, Default)]
180pub struct DateTimeValidatorConfig {
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub format: Option<String>,
184
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub message: Option<String>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize, Default)]
192pub struct FileTypeValidatorConfig {
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub allowed_extensions: Option<Vec<String>>,
196
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub allowed_mime_types: Option<Vec<String>>,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub message: Option<String>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, Default)]
208pub struct FileSizeValidatorConfig {
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub max_size: Option<u64>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub min_size: Option<u64>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub message: Option<String>,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize, Default)]
224pub struct ImageDimensionValidatorConfig {
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub min_width: Option<u32>,
228
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub max_width: Option<u32>,
232
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub min_height: Option<u32>,
236
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub max_height: Option<u32>,
240
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub message: Option<String>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
251#[serde(tag = "type", rename_all = "snake_case")]
252pub enum ValidatorConfig {
253 String(StringValidatorConfig),
255
256 Numeric(NumericValidatorConfig),
258
259 Email(EmailValidatorConfig),
261
262 Url(UrlValidatorConfig),
264
265 IpAddress(IpAddressValidatorConfig),
267
268 PhoneNumber(PhoneNumberValidatorConfig),
270
271 Color(ColorValidatorConfig),
273
274 Uuid(UuidValidatorConfig),
276
277 Slug(SlugValidatorConfig),
279
280 Date(DateTimeValidatorConfig),
282
283 Time(DateTimeValidatorConfig),
285
286 DateTime(DateTimeValidatorConfig),
288
289 FileType(FileTypeValidatorConfig),
291
292 FileSize(FileSizeValidatorConfig),
294
295 ImageDimension(ImageDimensionValidatorConfig),
297}
298
299impl ValidatorConfig {
300 pub fn string() -> StringValidatorConfig {
302 StringValidatorConfig::default()
303 }
304
305 pub fn numeric() -> NumericValidatorConfig {
307 NumericValidatorConfig::default()
308 }
309
310 pub fn email() -> EmailValidatorConfig {
312 EmailValidatorConfig::default()
313 }
314
315 pub fn url() -> UrlValidatorConfig {
317 UrlValidatorConfig::default()
318 }
319
320 pub fn ip_address() -> IpAddressValidatorConfig {
322 IpAddressValidatorConfig {
323 allow_ipv4: true,
324 allow_ipv6: true,
325 message: None,
326 }
327 }
328
329 pub fn phone_number() -> PhoneNumberValidatorConfig {
331 PhoneNumberValidatorConfig::default()
332 }
333
334 pub fn color() -> ColorValidatorConfig {
336 ColorValidatorConfig::default()
337 }
338
339 pub fn uuid() -> UuidValidatorConfig {
341 UuidValidatorConfig::default()
342 }
343
344 pub fn slug() -> SlugValidatorConfig {
346 SlugValidatorConfig::default()
347 }
348}
349
350impl StringValidatorConfig {
352 pub fn min_length(mut self, len: usize) -> Self {
354 self.min_length = Some(len);
355 self
356 }
357
358 pub fn max_length(mut self, len: usize) -> Self {
360 self.max_length = Some(len);
361 self
362 }
363
364 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
366 self.pattern = Some(pattern.into());
367 self
368 }
369
370 pub fn pattern_message(mut self, message: impl Into<String>) -> Self {
372 self.pattern_message = Some(message.into());
373 self
374 }
375
376 pub fn build(self) -> ValidatorConfig {
378 ValidatorConfig::String(self)
379 }
380}
381
382impl NumericValidatorConfig {
384 pub fn min(mut self, value: f64) -> Self {
386 self.min = Some(value);
387 self
388 }
389
390 pub fn max(mut self, value: f64) -> Self {
392 self.max = Some(value);
393 self
394 }
395
396 pub fn message(mut self, msg: impl Into<String>) -> Self {
398 self.message = Some(msg.into());
399 self
400 }
401
402 pub fn build(self) -> ValidatorConfig {
404 ValidatorConfig::Numeric(self)
405 }
406}
407
408impl EmailValidatorConfig {
410 pub fn min_length(mut self, len: usize) -> Self {
412 self.min_length = Some(len);
413 self
414 }
415
416 pub fn max_length(mut self, len: usize) -> Self {
418 self.max_length = Some(len);
419 self
420 }
421
422 pub fn message(mut self, msg: impl Into<String>) -> Self {
424 self.message = Some(msg.into());
425 self
426 }
427
428 pub fn build(self) -> ValidatorConfig {
430 ValidatorConfig::Email(self)
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn test_string_config_serialization() {
440 let config = ValidatorConfig::String(StringValidatorConfig {
441 min_length: Some(3),
442 max_length: Some(50),
443 pattern: Some(r"^[a-z]+$".into()),
444 pattern_message: Some("Only lowercase letters allowed".into()),
445 });
446
447 let json = serde_json::to_string(&config).unwrap();
448 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
449
450 match loaded {
451 ValidatorConfig::String(cfg) => {
452 assert_eq!(cfg.min_length, Some(3));
453 assert_eq!(cfg.max_length, Some(50));
454 assert_eq!(cfg.pattern, Some(r"^[a-z]+$".into()));
455 }
456 _ => panic!("Expected String config"),
457 }
458 }
459
460 #[test]
461 fn test_numeric_config_serialization() {
462 let config = ValidatorConfig::Numeric(NumericValidatorConfig {
463 min: Some(0.0),
464 max: Some(100.0),
465 message: Some("Value must be between 0 and 100".into()),
466 });
467
468 let json = serde_json::to_string(&config).unwrap();
469 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
470
471 match loaded {
472 ValidatorConfig::Numeric(cfg) => {
473 assert_eq!(cfg.min, Some(0.0));
474 assert_eq!(cfg.max, Some(100.0));
475 }
476 _ => panic!("Expected Numeric config"),
477 }
478 }
479
480 #[test]
481 fn test_email_config_serialization() {
482 let config = ValidatorConfig::Email(EmailValidatorConfig {
483 min_length: Some(5),
484 max_length: Some(254),
485 message: None,
486 });
487
488 let json = serde_json::to_string(&config).unwrap();
489 assert!(json.contains("\"type\":\"email\""));
490
491 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
492 match loaded {
493 ValidatorConfig::Email(cfg) => {
494 assert_eq!(cfg.min_length, Some(5));
495 assert_eq!(cfg.max_length, Some(254));
496 }
497 _ => panic!("Expected Email config"),
498 }
499 }
500
501 #[test]
502 fn test_ip_address_config_defaults() {
503 let json = r#"{"type":"ip_address"}"#;
504 let config: ValidatorConfig = serde_json::from_str(json).unwrap();
505
506 match config {
507 ValidatorConfig::IpAddress(cfg) => {
508 assert!(cfg.allow_ipv4);
509 assert!(cfg.allow_ipv6);
510 }
511 _ => panic!("Expected IpAddress config"),
512 }
513 }
514
515 #[test]
516 fn test_phone_number_config_serialization() {
517 let config = ValidatorConfig::PhoneNumber(PhoneNumberValidatorConfig {
518 country_codes: Some(vec!["1".into(), "81".into(), "44".into()]),
519 allow_extensions: true,
520 message: None,
521 });
522
523 let json = serde_json::to_string(&config).unwrap();
524 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
525
526 match loaded {
527 ValidatorConfig::PhoneNumber(cfg) => {
528 assert_eq!(cfg.country_codes.as_ref().unwrap().len(), 3);
529 assert!(cfg.allow_extensions);
530 }
531 _ => panic!("Expected PhoneNumber config"),
532 }
533 }
534
535 #[test]
536 fn test_color_config_serialization() {
537 let config = ValidatorConfig::Color(ColorValidatorConfig {
538 allowed_formats: Some(vec![ColorFormatConfig::Hex, ColorFormatConfig::Rgb]),
539 message: Some("Use hex or RGB format".into()),
540 });
541
542 let json = serde_json::to_string(&config).unwrap();
543 assert!(json.contains("\"hex\""));
544 assert!(json.contains("\"rgb\""));
545
546 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
547 match loaded {
548 ValidatorConfig::Color(cfg) => {
549 let formats = cfg.allowed_formats.unwrap();
550 assert_eq!(formats.len(), 2);
551 assert!(formats.contains(&ColorFormatConfig::Hex));
552 assert!(formats.contains(&ColorFormatConfig::Rgb));
553 }
554 _ => panic!("Expected Color config"),
555 }
556 }
557
558 #[test]
559 fn test_file_type_config_serialization() {
560 let config = ValidatorConfig::FileType(FileTypeValidatorConfig {
561 allowed_extensions: Some(vec!["jpg".into(), "png".into()]),
562 allowed_mime_types: Some(vec!["image/jpeg".into(), "image/png".into()]),
563 message: None,
564 });
565
566 let json = serde_json::to_string(&config).unwrap();
567 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
568
569 match loaded {
570 ValidatorConfig::FileType(cfg) => {
571 assert_eq!(cfg.allowed_extensions.as_ref().unwrap().len(), 2);
572 assert_eq!(cfg.allowed_mime_types.as_ref().unwrap().len(), 2);
573 }
574 _ => panic!("Expected FileType config"),
575 }
576 }
577
578 #[test]
579 fn test_image_dimension_config_serialization() {
580 let config = ValidatorConfig::ImageDimension(ImageDimensionValidatorConfig {
581 min_width: Some(100),
582 max_width: Some(1920),
583 min_height: Some(100),
584 max_height: Some(1080),
585 message: None,
586 });
587
588 let json = serde_json::to_string(&config).unwrap();
589 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
590
591 match loaded {
592 ValidatorConfig::ImageDimension(cfg) => {
593 assert_eq!(cfg.min_width, Some(100));
594 assert_eq!(cfg.max_width, Some(1920));
595 assert_eq!(cfg.min_height, Some(100));
596 assert_eq!(cfg.max_height, Some(1080));
597 }
598 _ => panic!("Expected ImageDimension config"),
599 }
600 }
601
602 #[test]
603 fn test_builder_pattern() {
604 let config = ValidatorConfig::string()
605 .min_length(1)
606 .max_length(100)
607 .pattern(r"^[a-zA-Z]+$")
608 .pattern_message("Only letters allowed")
609 .build();
610
611 match config {
612 ValidatorConfig::String(cfg) => {
613 assert_eq!(cfg.min_length, Some(1));
614 assert_eq!(cfg.max_length, Some(100));
615 assert!(cfg.pattern.is_some());
616 assert!(cfg.pattern_message.is_some());
617 }
618 _ => panic!("Expected String config"),
619 }
620 }
621
622 #[test]
623 fn test_numeric_builder() {
624 let config = ValidatorConfig::numeric()
625 .min(0.0)
626 .max(100.0)
627 .message("Value out of range")
628 .build();
629
630 match config {
631 ValidatorConfig::Numeric(cfg) => {
632 assert_eq!(cfg.min, Some(0.0));
633 assert_eq!(cfg.max, Some(100.0));
634 assert!(cfg.message.is_some());
635 }
636 _ => panic!("Expected Numeric config"),
637 }
638 }
639
640 #[test]
641 fn test_email_builder() {
642 let config = ValidatorConfig::email()
643 .min_length(5)
644 .max_length(254)
645 .message("Invalid email")
646 .build();
647
648 match config {
649 ValidatorConfig::Email(cfg) => {
650 assert_eq!(cfg.min_length, Some(5));
651 assert_eq!(cfg.max_length, Some(254));
652 assert!(cfg.message.is_some());
653 }
654 _ => panic!("Expected Email config"),
655 }
656 }
657
658 #[test]
659 fn test_skip_serializing_none_fields() {
660 let config = ValidatorConfig::String(StringValidatorConfig {
661 min_length: Some(1),
662 max_length: None,
663 pattern: None,
664 pattern_message: None,
665 });
666
667 let json = serde_json::to_string(&config).unwrap();
668
669 assert!(json.contains("\"min_length\":1"));
671 assert!(!json.contains("\"max_length\""));
672 assert!(!json.contains("\"pattern\""));
673 assert!(!json.contains("\"pattern_message\""));
674 }
675
676 #[test]
677 fn test_deserialize_minimal_config() {
678 let json = r#"{"type":"string"}"#;
680 let config: ValidatorConfig = serde_json::from_str(json).unwrap();
681
682 match config {
683 ValidatorConfig::String(cfg) => {
684 assert!(cfg.min_length.is_none());
685 assert!(cfg.max_length.is_none());
686 assert!(cfg.pattern.is_none());
687 }
688 _ => panic!("Expected String config"),
689 }
690 }
691
692 #[test]
693 fn test_slug_config() {
694 let config = ValidatorConfig::Slug(SlugValidatorConfig {
695 allow_unicode: true,
696 message: Some("Invalid slug".into()),
697 });
698
699 let json = serde_json::to_string(&config).unwrap();
700 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
701
702 match loaded {
703 ValidatorConfig::Slug(cfg) => {
704 assert!(cfg.allow_unicode);
705 assert!(cfg.message.is_some());
706 }
707 _ => panic!("Expected Slug config"),
708 }
709 }
710
711 #[test]
712 fn test_uuid_config() {
713 let config = ValidatorConfig::Uuid(UuidValidatorConfig {
714 message: Some("Invalid UUID format".into()),
715 });
716
717 let json = serde_json::to_string(&config).unwrap();
718 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
719
720 match loaded {
721 ValidatorConfig::Uuid(cfg) => {
722 assert_eq!(cfg.message, Some("Invalid UUID format".into()));
723 }
724 _ => panic!("Expected UUID config"),
725 }
726 }
727
728 #[test]
729 fn test_file_size_config() {
730 let config = ValidatorConfig::FileSize(FileSizeValidatorConfig {
731 max_size: Some(10 * 1024 * 1024), min_size: Some(1024), message: None,
734 });
735
736 let json = serde_json::to_string(&config).unwrap();
737 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
738
739 match loaded {
740 ValidatorConfig::FileSize(cfg) => {
741 assert_eq!(cfg.max_size, Some(10 * 1024 * 1024));
742 assert_eq!(cfg.min_size, Some(1024));
743 }
744 _ => panic!("Expected FileSize config"),
745 }
746 }
747
748 #[test]
749 fn test_url_config() {
750 let config = ValidatorConfig::Url(UrlValidatorConfig {
751 message: Some("Invalid URL".into()),
752 });
753
754 let json = serde_json::to_string(&config).unwrap();
755 assert!(json.contains("\"type\":\"url\""));
756
757 let loaded: ValidatorConfig = serde_json::from_str(&json).unwrap();
758 match loaded {
759 ValidatorConfig::Url(cfg) => {
760 assert!(cfg.message.is_some());
761 }
762 _ => panic!("Expected URL config"),
763 }
764 }
765}