1use serde::{Deserialize, Serialize};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
31pub enum IDPolicy {
32 #[serde(rename = "uuid")]
34 #[default]
35 UUID,
36
37 #[serde(rename = "opaque")]
39 OPAQUE,
40}
41
42impl IDPolicy {
43 #[must_use]
45 pub fn enforces_uuid(self) -> bool {
46 self == Self::UUID
47 }
48
49 #[must_use]
51 pub const fn as_str(self) -> &'static str {
52 match self {
53 Self::UUID => "uuid",
54 Self::OPAQUE => "opaque",
55 }
56 }
57}
58
59impl std::fmt::Display for IDPolicy {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "{}", self.as_str())
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct IDValidationError {
68 pub value: String,
70 pub policy: IDPolicy,
72 pub message: String,
74}
75
76impl std::fmt::Display for IDValidationError {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 write!(f, "{}", self.message)
79 }
80}
81
82impl std::error::Error for IDValidationError {}
83
84pub fn validate_id(id: &str, policy: IDPolicy) -> Result<(), IDValidationError> {
114 match policy {
115 IDPolicy::UUID => validate_uuid_format(id),
116 IDPolicy::OPAQUE => Ok(()), }
118}
119
120fn validate_uuid_format(id: &str) -> Result<(), IDValidationError> {
138 if id.len() != 36 {
140 return Err(IDValidationError {
141 value: id.to_string(),
142 policy: IDPolicy::UUID,
143 message: format!(
144 "ID must be a valid UUID (36 characters), got {} characters",
145 id.len()
146 ),
147 });
148 }
149
150 let parts: Vec<&str> = id.split('-').collect();
152 if parts.len() != 5 {
153 return Err(IDValidationError {
154 value: id.to_string(),
155 policy: IDPolicy::UUID,
156 message: "ID must be a valid UUID with format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
157 .to_string(),
158 });
159 }
160
161 let expected_lengths = [8, 4, 4, 4, 12];
163 for (i, (part, &expected_len)) in parts.iter().zip(&expected_lengths).enumerate() {
164 if part.len() != expected_len {
165 return Err(IDValidationError {
166 value: id.to_string(),
167 policy: IDPolicy::UUID,
168 message: format!(
169 "UUID segment {} has invalid length: expected {}, got {}",
170 i,
171 expected_len,
172 part.len()
173 ),
174 });
175 }
176 }
177
178 for (i, part) in parts.iter().enumerate() {
180 if !part.chars().all(|c| c.is_ascii_hexdigit()) {
181 return Err(IDValidationError {
182 value: id.to_string(),
183 policy: IDPolicy::UUID,
184 message: format!("UUID segment {i} contains non-hexadecimal characters: '{part}'"),
185 });
186 }
187 }
188
189 Ok(())
190}
191
192#[allow(dead_code)] pub fn validate_ids(ids: &[&str], policy: IDPolicy) -> Result<(), IDValidationError> {
220 for id in ids {
221 validate_id(id, policy)?;
222 }
223 Ok(())
224}
225
226pub trait IdValidator: Send + Sync {
261 fn validate(&self, value: &str) -> Result<(), IDValidationError>;
263
264 fn format_name(&self) -> &'static str;
266}
267
268#[derive(Debug, Clone, Copy)]
270pub struct UuidIdValidator;
271
272impl IdValidator for UuidIdValidator {
273 fn validate(&self, value: &str) -> Result<(), IDValidationError> {
274 validate_uuid_format(value)
275 }
276
277 fn format_name(&self) -> &'static str {
278 "UUID"
279 }
280}
281
282#[derive(Debug, Clone, Copy)]
284pub struct NumericIdValidator;
285
286impl IdValidator for NumericIdValidator {
287 fn validate(&self, value: &str) -> Result<(), IDValidationError> {
288 value.parse::<i64>().map_err(|_| IDValidationError {
289 value: value.to_string(),
290 policy: IDPolicy::OPAQUE,
291 message: format!(
292 "ID must be a valid {} (parseable as 64-bit integer)",
293 self.format_name()
294 ),
295 })?;
296 Ok(())
297 }
298
299 fn format_name(&self) -> &'static str {
300 "integer"
301 }
302}
303
304#[derive(Debug, Clone, Copy)]
309pub struct UlidIdValidator;
310
311impl IdValidator for UlidIdValidator {
312 fn validate(&self, value: &str) -> Result<(), IDValidationError> {
313 if value.len() != 26 {
314 return Err(IDValidationError {
315 value: value.to_string(),
316 policy: IDPolicy::OPAQUE,
317 message: format!(
318 "ID must be a valid {} ({} characters), got {}",
319 self.format_name(),
320 26,
321 value.len()
322 ),
323 });
324 }
325
326 if !value.chars().all(|c| {
328 c.is_ascii_digit()
329 || (c.is_ascii_uppercase() && c != 'I' && c != 'L' && c != 'O' && c != 'U')
330 }) {
331 return Err(IDValidationError {
332 value: value.to_string(),
333 policy: IDPolicy::OPAQUE,
334 message: format!(
335 "ID must be a valid {} (Crockford base32: 0-9, A-Z except I, L, O, U)",
336 self.format_name()
337 ),
338 });
339 }
340
341 Ok(())
342 }
343
344 fn format_name(&self) -> &'static str {
345 "ULID"
346 }
347}
348
349#[derive(Debug, Clone, Copy)]
351pub struct OpaqueIdValidator;
352
353impl IdValidator for OpaqueIdValidator {
354 fn validate(&self, _value: &str) -> Result<(), IDValidationError> {
355 Ok(()) }
357
358 fn format_name(&self) -> &'static str {
359 "opaque"
360 }
361}
362
363#[derive(Debug, Clone)]
375pub struct IDValidationProfile {
376 pub name: String,
378
379 pub validator: ValidationProfileType,
381}
382
383#[derive(Debug, Clone)]
385pub enum ValidationProfileType {
386 Uuid(UuidIdValidator),
388
389 Numeric(NumericIdValidator),
391
392 Ulid(UlidIdValidator),
394
395 Opaque(OpaqueIdValidator),
397}
398
399impl ValidationProfileType {
400 pub fn as_validator(&self) -> &dyn IdValidator {
402 match self {
403 Self::Uuid(v) => v,
404 Self::Numeric(v) => v,
405 Self::Ulid(v) => v,
406 Self::Opaque(v) => v,
407 }
408 }
409}
410
411impl IDValidationProfile {
412 #[must_use]
414 pub fn uuid() -> Self {
415 Self {
416 name: "uuid".to_string(),
417 validator: ValidationProfileType::Uuid(UuidIdValidator),
418 }
419 }
420
421 #[must_use]
423 pub fn numeric() -> Self {
424 Self {
425 name: "numeric".to_string(),
426 validator: ValidationProfileType::Numeric(NumericIdValidator),
427 }
428 }
429
430 #[must_use]
432 pub fn ulid() -> Self {
433 Self {
434 name: "ulid".to_string(),
435 validator: ValidationProfileType::Ulid(UlidIdValidator),
436 }
437 }
438
439 #[must_use]
441 pub fn opaque() -> Self {
442 Self {
443 name: "opaque".to_string(),
444 validator: ValidationProfileType::Opaque(OpaqueIdValidator),
445 }
446 }
447
448 #[must_use]
459 pub fn by_name(name: &str) -> Option<Self> {
460 match name.to_lowercase().as_str() {
461 "uuid" => Some(Self::uuid()),
462 "numeric" | "integer" => Some(Self::numeric()),
463 "ulid" => Some(Self::ulid()),
464 "opaque" | "string" => Some(Self::opaque()),
465 _ => None,
466 }
467 }
468
469 pub fn validate(&self, value: &str) -> Result<(), IDValidationError> {
471 self.validator.as_validator().validate(value)
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
482 fn test_validate_valid_uuid() {
483 let result = validate_id("550e8400-e29b-41d4-a716-446655440000", IDPolicy::UUID);
485 assert!(result.is_ok());
486 }
487
488 #[test]
489 fn test_validate_valid_uuid_uppercase() {
490 let result = validate_id("550E8400-E29B-41D4-A716-446655440000", IDPolicy::UUID);
492 assert!(result.is_ok());
493 }
494
495 #[test]
496 fn test_validate_valid_uuid_mixed_case() {
497 let result = validate_id("550e8400-E29b-41d4-A716-446655440000", IDPolicy::UUID);
498 assert!(result.is_ok());
499 }
500
501 #[test]
502 fn test_validate_nil_uuid() {
503 let result = validate_id("00000000-0000-0000-0000-000000000000", IDPolicy::UUID);
505 assert!(result.is_ok());
506 }
507
508 #[test]
509 fn test_validate_max_uuid() {
510 let result = validate_id("ffffffff-ffff-ffff-ffff-ffffffffffff", IDPolicy::UUID);
512 assert!(result.is_ok());
513 }
514
515 #[test]
516 fn test_validate_uuid_wrong_length() {
517 let result = validate_id("550e8400-e29b-41d4-a716", IDPolicy::UUID);
518 assert!(result.is_err());
519 let err = result.unwrap_err();
520 assert_eq!(err.policy, IDPolicy::UUID);
521 assert!(err.message.contains("36 characters"));
522 }
523
524 #[test]
525 fn test_validate_uuid_extra_chars() {
526 let result = validate_id("550e8400-e29b-41d4-a716-446655440000x", IDPolicy::UUID);
527 assert!(result.is_err());
528 }
529
530 #[test]
531 fn test_validate_uuid_missing_hyphens() {
532 let result = validate_id("550e8400e29b41d4a716446655440000", IDPolicy::UUID);
534 assert!(result.is_err());
535 let err = result.unwrap_err();
536 assert!(err.message.contains("36 characters"));
538 }
539
540 #[test]
541 fn test_validate_uuid_wrong_segment_lengths() {
542 let result = validate_id("550e840-e29b-41d4-a716-4466554400001", IDPolicy::UUID);
545 assert!(result.is_err());
546 let err = result.unwrap_err();
547 assert!(err.message.contains("segment"));
548 }
549
550 #[test]
551 fn test_validate_uuid_non_hex_chars() {
552 let result = validate_id("550e8400-e29b-41d4-a716-44665544000g", IDPolicy::UUID);
553 assert!(result.is_err());
554 let err = result.unwrap_err();
555 assert!(err.message.contains("non-hexadecimal"));
556 }
557
558 #[test]
559 fn test_validate_uuid_special_chars() {
560 let result = validate_id("550e8400-e29b-41d4-a716-4466554400@0", IDPolicy::UUID);
561 assert!(result.is_err());
562 }
563
564 #[test]
565 fn test_validate_uuid_empty_string() {
566 let result = validate_id("", IDPolicy::UUID);
567 assert!(result.is_err());
568 }
569
570 #[test]
573 fn test_opaque_accepts_any_string() {
574 assert!(validate_id("not-a-uuid", IDPolicy::OPAQUE).is_ok());
575 assert!(validate_id("anything", IDPolicy::OPAQUE).is_ok());
576 assert!(validate_id("12345", IDPolicy::OPAQUE).is_ok());
577 assert!(validate_id("special@chars!#$%", IDPolicy::OPAQUE).is_ok());
578 }
579
580 #[test]
581 fn test_opaque_accepts_empty_string() {
582 assert!(validate_id("", IDPolicy::OPAQUE).is_ok());
583 }
584
585 #[test]
586 fn test_opaque_accepts_uuid() {
587 assert!(validate_id("550e8400-e29b-41d4-a716-446655440000", IDPolicy::OPAQUE).is_ok());
588 }
589
590 #[test]
593 fn test_validate_multiple_valid_uuids() {
594 let ids = vec![
595 "550e8400-e29b-41d4-a716-446655440000",
596 "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
597 "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
598 ];
599 assert!(validate_ids(&ids, IDPolicy::UUID).is_ok());
600 }
601
602 #[test]
603 fn test_validate_multiple_fails_on_first_invalid() {
604 let ids = vec![
605 "550e8400-e29b-41d4-a716-446655440000",
606 "invalid-id",
607 "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
608 ];
609 let result = validate_ids(&ids, IDPolicy::UUID);
610 assert!(result.is_err());
611 assert_eq!(result.unwrap_err().value, "invalid-id");
612 }
613
614 #[test]
615 fn test_validate_multiple_opaque_all_pass() {
616 let ids = vec!["anything", "goes", "here", "12345"];
617 assert!(validate_ids(&ids, IDPolicy::OPAQUE).is_ok());
618 }
619
620 #[test]
623 fn test_policy_enforces_uuid() {
624 assert!(IDPolicy::UUID.enforces_uuid());
625 assert!(!IDPolicy::OPAQUE.enforces_uuid());
626 }
627
628 #[test]
629 fn test_policy_as_str() {
630 assert_eq!(IDPolicy::UUID.as_str(), "uuid");
631 assert_eq!(IDPolicy::OPAQUE.as_str(), "opaque");
632 }
633
634 #[test]
635 fn test_policy_default() {
636 assert_eq!(IDPolicy::default(), IDPolicy::UUID);
637 }
638
639 #[test]
640 fn test_policy_display() {
641 assert_eq!(format!("{}", IDPolicy::UUID), "uuid");
642 assert_eq!(format!("{}", IDPolicy::OPAQUE), "opaque");
643 }
644
645 #[test]
648 fn test_security_prevent_sql_injection_via_uuid() {
649 let result = validate_id("'; DROP TABLE users; --", IDPolicy::UUID);
651 assert!(result.is_err());
652 }
653
654 #[test]
655 fn test_security_prevent_path_traversal_via_uuid() {
656 let result = validate_id("../../etc/passwd", IDPolicy::UUID);
657 assert!(result.is_err());
658 }
659
660 #[test]
661 fn test_security_opaque_policy_accepts_any_format() {
662 assert!(validate_id("'; DROP TABLE users; --", IDPolicy::OPAQUE).is_ok());
665 assert!(validate_id("../../etc/passwd", IDPolicy::OPAQUE).is_ok());
666 }
667
668 #[test]
669 fn test_validation_error_contains_policy_info() {
670 let err = validate_id("invalid", IDPolicy::UUID).unwrap_err();
671 assert_eq!(err.policy, IDPolicy::UUID);
672 assert_eq!(err.value, "invalid");
673 assert!(!err.message.is_empty());
674 }
675
676 #[test]
679 fn test_uuid_validator_valid() {
680 let validator = UuidIdValidator;
681 let result = validator.validate("550e8400-e29b-41d4-a716-446655440000");
682 assert!(result.is_ok());
683 }
684
685 #[test]
686 fn test_uuid_validator_invalid() {
687 let validator = UuidIdValidator;
688 let result = validator.validate("not-a-uuid");
689 assert!(result.is_err());
690 let err = result.unwrap_err();
691 assert_eq!(err.value, "not-a-uuid");
692 }
693
694 #[test]
695 fn test_uuid_validator_format_name() {
696 let validator = UuidIdValidator;
697 assert_eq!(validator.format_name(), "UUID");
698 }
699
700 #[test]
701 fn test_uuid_validator_nil_uuid() {
702 let validator = UuidIdValidator;
703 assert!(validator.validate("00000000-0000-0000-0000-000000000000").is_ok());
704 }
705
706 #[test]
707 fn test_uuid_validator_uppercase() {
708 let validator = UuidIdValidator;
709 assert!(validator.validate("550E8400-E29B-41D4-A716-446655440000").is_ok());
710 }
711
712 #[test]
715 fn test_numeric_validator_valid_positive() {
716 let validator = NumericIdValidator;
717 assert!(validator.validate("12345").is_ok());
718 assert!(validator.validate("0").is_ok());
719 assert!(validator.validate("9223372036854775807").is_ok()); }
721
722 #[test]
723 fn test_numeric_validator_valid_negative() {
724 let validator = NumericIdValidator;
725 assert!(validator.validate("-1").is_ok());
726 assert!(validator.validate("-12345").is_ok());
727 assert!(validator.validate("-9223372036854775808").is_ok()); }
729
730 #[test]
731 fn test_numeric_validator_invalid_float() {
732 let validator = NumericIdValidator;
733 let result = validator.validate("123.45");
734 assert!(result.is_err());
735 let err = result.unwrap_err();
736 assert_eq!(err.value, "123.45");
737 }
738
739 #[test]
740 fn test_numeric_validator_invalid_non_numeric() {
741 let validator = NumericIdValidator;
742 let result = validator.validate("abc123");
743 assert!(result.is_err());
744 }
745
746 #[test]
747 fn test_numeric_validator_overflow() {
748 let validator = NumericIdValidator;
749 let result = validator.validate("9223372036854775808");
751 assert!(result.is_err());
752 }
753
754 #[test]
755 fn test_numeric_validator_empty_string() {
756 let validator = NumericIdValidator;
757 let result = validator.validate("");
758 assert!(result.is_err());
759 }
760
761 #[test]
762 fn test_numeric_validator_format_name() {
763 let validator = NumericIdValidator;
764 assert_eq!(validator.format_name(), "integer");
765 }
766
767 #[test]
770 fn test_ulid_validator_valid() {
771 let validator = UlidIdValidator;
772 assert!(validator.validate("01ARZ3NDEKTSV4RRFFQ69G5FAV").is_ok());
774 }
775
776 #[test]
777 fn test_ulid_validator_valid_all_digits() {
778 let validator = UlidIdValidator;
779 assert!(validator.validate("01234567890123456789012345").is_ok());
781 }
782
783 #[test]
784 fn test_ulid_validator_valid_all_uppercase() {
785 let validator = UlidIdValidator;
786 assert!(validator.validate("ABCDEFGHJKMNPQRSTVWXYZ0123").is_ok());
788 }
789
790 #[test]
791 fn test_ulid_validator_invalid_length_short() {
792 let validator = UlidIdValidator;
793 let result = validator.validate("01ARZ3NDEKTSV4RRFFQ69G5F");
794 assert!(result.is_err());
795 let err = result.unwrap_err();
796 assert!(err.message.contains("26 characters"));
797 }
798
799 #[test]
800 fn test_ulid_validator_invalid_length_long() {
801 let validator = UlidIdValidator;
802 let result = validator.validate("01ARZ3NDEKTSV4RRFFQ69G5FAVA");
803 assert!(result.is_err());
804 let err = result.unwrap_err();
805 assert!(err.message.contains("26 characters"));
806 }
807
808 #[test]
809 fn test_ulid_validator_invalid_lowercase() {
810 let validator = UlidIdValidator;
811 let result = validator.validate("01arz3ndektsv4rrffq69g5fav");
812 assert!(result.is_err());
813 }
814
815 #[test]
816 fn test_ulid_validator_invalid_char_i() {
817 let validator = UlidIdValidator;
818 let result = validator.validate("01ARZ3NDEKTSV4RRFFQ69G5FAI");
819 assert!(result.is_err());
820 let err = result.unwrap_err();
821 assert!(err.message.contains("Crockford base32"));
822 }
823
824 #[test]
825 fn test_ulid_validator_invalid_char_l() {
826 let validator = UlidIdValidator;
827 let result = validator.validate("01ARZ3NDEKTSV4RRFFQ69G5FAL");
828 assert!(result.is_err());
829 }
830
831 #[test]
832 fn test_ulid_validator_invalid_char_o() {
833 let validator = UlidIdValidator;
834 let result = validator.validate("01ARZ3NDEKTSV4RRFFQ69G5FAO");
835 assert!(result.is_err());
836 }
837
838 #[test]
839 fn test_ulid_validator_invalid_char_u() {
840 let validator = UlidIdValidator;
841 let result = validator.validate("01ARZ3NDEKTSV4RRFFQ69G5FAU");
842 assert!(result.is_err());
843 }
844
845 #[test]
846 fn test_ulid_validator_invalid_special_chars() {
847 let validator = UlidIdValidator;
848 let result = validator.validate("01ARZ3NDEKTSV4RRFFQ69G5FA-");
849 assert!(result.is_err());
850 }
851
852 #[test]
853 fn test_ulid_validator_empty_string() {
854 let validator = UlidIdValidator;
855 let result = validator.validate("");
856 assert!(result.is_err());
857 }
858
859 #[test]
860 fn test_ulid_validator_format_name() {
861 let validator = UlidIdValidator;
862 assert_eq!(validator.format_name(), "ULID");
863 }
864
865 #[test]
868 fn test_opaque_validator_any_string() {
869 let validator = OpaqueIdValidator;
870 assert!(validator.validate("anything").is_ok());
871 assert!(validator.validate("12345").is_ok());
872 assert!(validator.validate("special@chars!#$%").is_ok());
873 assert!(validator.validate("").is_ok());
874 }
875
876 #[test]
877 fn test_opaque_validator_malicious_strings() {
878 let validator = OpaqueIdValidator;
879 assert!(validator.validate("'; DROP TABLE users; --").is_ok());
881 assert!(validator.validate("../../etc/passwd").is_ok());
882 assert!(validator.validate("<script>alert('xss')</script>").is_ok());
883 }
884
885 #[test]
886 fn test_opaque_validator_uuid() {
887 let validator = OpaqueIdValidator;
888 assert!(validator.validate("550e8400-e29b-41d4-a716-446655440000").is_ok());
889 }
890
891 #[test]
892 fn test_opaque_validator_format_name() {
893 let validator = OpaqueIdValidator;
894 assert_eq!(validator.format_name(), "opaque");
895 }
896
897 #[test]
900 fn test_validators_trait_object() {
901 let validators: Vec<Box<dyn IdValidator>> = vec![
902 Box::new(UuidIdValidator),
903 Box::new(NumericIdValidator),
904 Box::new(UlidIdValidator),
905 Box::new(OpaqueIdValidator),
906 ];
907
908 for validator in validators {
909 let name = validator.format_name();
911 assert!(!name.is_empty());
912 }
913 }
914
915 #[test]
916 fn test_validator_selection_by_id_format() {
917 let uuid = "550e8400-e29b-41d4-a716-446655440000";
919 let numeric = "12345";
920 let ulid = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
921
922 let uuid_validator = UuidIdValidator;
923 let numeric_validator = NumericIdValidator;
924 let ulid_validator = UlidIdValidator;
925
926 assert!(uuid_validator.validate(uuid).is_ok());
927 assert!(numeric_validator.validate(numeric).is_ok());
928 assert!(ulid_validator.validate(ulid).is_ok());
929
930 assert!(uuid_validator.validate(numeric).is_err());
932 assert!(numeric_validator.validate(uuid).is_err());
933 assert!(ulid_validator.validate(numeric).is_err());
934 }
935
936 #[test]
939 fn test_id_validation_profile_uuid() {
940 let profile = IDValidationProfile::uuid();
941 assert_eq!(profile.name, "uuid");
942 assert!(profile.validate("550e8400-e29b-41d4-a716-446655440000").is_ok());
943 assert!(profile.validate("not-a-uuid").is_err());
944 }
945
946 #[test]
947 fn test_id_validation_profile_numeric() {
948 let profile = IDValidationProfile::numeric();
949 assert_eq!(profile.name, "numeric");
950 assert!(profile.validate("12345").is_ok());
951 assert!(profile.validate("not-a-number").is_err());
952 }
953
954 #[test]
955 fn test_id_validation_profile_ulid() {
956 let profile = IDValidationProfile::ulid();
957 assert_eq!(profile.name, "ulid");
958 assert!(profile.validate("01ARZ3NDEKTSV4RRFFQ69G5FAV").is_ok());
959 assert!(profile.validate("not-a-ulid").is_err());
960 }
961
962 #[test]
963 fn test_id_validation_profile_opaque() {
964 let profile = IDValidationProfile::opaque();
965 assert_eq!(profile.name, "opaque");
966 assert!(profile.validate("anything").is_ok());
967 assert!(profile.validate("12345").is_ok());
968 assert!(profile.validate("special@chars!#$%").is_ok());
969 }
970
971 #[test]
972 fn test_id_validation_profile_by_name() {
973 assert!(IDValidationProfile::by_name("uuid").is_some());
975 assert!(IDValidationProfile::by_name("numeric").is_some());
976 assert!(IDValidationProfile::by_name("ulid").is_some());
977 assert!(IDValidationProfile::by_name("opaque").is_some());
978
979 assert!(IDValidationProfile::by_name("UUID").is_some());
981 assert!(IDValidationProfile::by_name("NUMERIC").is_some());
982 assert!(IDValidationProfile::by_name("ULID").is_some());
983
984 assert!(IDValidationProfile::by_name("integer").is_some());
986 assert!(IDValidationProfile::by_name("string").is_some());
987
988 assert!(IDValidationProfile::by_name("invalid").is_none());
990 }
991
992 #[test]
993 fn test_id_validation_profile_by_name_uuid_validation() {
994 let profile = IDValidationProfile::by_name("uuid").unwrap();
995 assert_eq!(profile.name, "uuid");
996 assert!(profile.validate("550e8400-e29b-41d4-a716-446655440000").is_ok());
997 }
998
999 #[test]
1000 fn test_id_validation_profile_by_name_numeric_validation() {
1001 let profile = IDValidationProfile::by_name("numeric").unwrap();
1002 assert_eq!(profile.name, "numeric");
1003 assert!(profile.validate("12345").is_ok());
1004 }
1005
1006 #[test]
1007 fn test_id_validation_profile_by_name_integer_alias() {
1008 let profile_numeric = IDValidationProfile::by_name("numeric").unwrap();
1009 let profile_integer = IDValidationProfile::by_name("integer").unwrap();
1010
1011 assert!(profile_numeric.validate("12345").is_ok());
1013 assert!(profile_integer.validate("12345").is_ok());
1014 assert!(profile_numeric.validate("not-a-number").is_err());
1015 assert!(profile_integer.validate("not-a-number").is_err());
1016 }
1017
1018 #[test]
1019 fn test_id_validation_profile_by_name_string_alias() {
1020 let profile_opaque = IDValidationProfile::by_name("opaque").unwrap();
1021 let profile_string = IDValidationProfile::by_name("string").unwrap();
1022
1023 assert!(profile_opaque.validate("anything").is_ok());
1025 assert!(profile_string.validate("anything").is_ok());
1026 }
1027
1028 #[test]
1029 fn test_validation_profile_type_as_validator() {
1030 let uuid_type = ValidationProfileType::Uuid(UuidIdValidator);
1031 assert!(
1032 uuid_type
1033 .as_validator()
1034 .validate("550e8400-e29b-41d4-a716-446655440000")
1035 .is_ok()
1036 );
1037
1038 let numeric_type = ValidationProfileType::Numeric(NumericIdValidator);
1039 assert!(numeric_type.as_validator().validate("12345").is_ok());
1040
1041 let ulid_type = ValidationProfileType::Ulid(UlidIdValidator);
1042 assert!(ulid_type.as_validator().validate("01ARZ3NDEKTSV4RRFFQ69G5FAV").is_ok());
1043
1044 let opaque_type = ValidationProfileType::Opaque(OpaqueIdValidator);
1045 assert!(opaque_type.as_validator().validate("any_value").is_ok());
1046 }
1047
1048 #[test]
1049 fn test_id_validation_profile_clone() {
1050 let profile1 = IDValidationProfile::uuid();
1051 let profile2 = profile1.clone();
1052
1053 assert_eq!(profile1.name, profile2.name);
1054 assert!(profile1.validate("550e8400-e29b-41d4-a716-446655440000").is_ok());
1055 assert!(profile2.validate("550e8400-e29b-41d4-a716-446655440000").is_ok());
1056 }
1057
1058 #[test]
1059 fn test_all_profiles_available() {
1060 let profiles = [
1061 IDValidationProfile::uuid(),
1062 IDValidationProfile::numeric(),
1063 IDValidationProfile::ulid(),
1064 IDValidationProfile::opaque(),
1065 ];
1066
1067 assert_eq!(profiles.len(), 4);
1068 assert_eq!(profiles[0].name, "uuid");
1069 assert_eq!(profiles[1].name, "numeric");
1070 assert_eq!(profiles[2].name, "ulid");
1071 assert_eq!(profiles[3].name, "opaque");
1072 }
1073}