1use crate::ValidationError;
2
3pub fn validate(input: &str) -> Result<(), ValidationError> {
31 let input = input.trim();
32
33 if input.len() != 10 {
34 return Err(ValidationError::InvalidLength);
35 }
36
37 if !input.chars().all(|c| c.is_ascii_digit()) {
38 return Err(ValidationError::InvalidFormat);
39 }
40
41 let province: u32 = input[..2].parse().unwrap();
42 if province == 0 || province > 24 {
43 return Err(ValidationError::InvalidProvinceCode);
44 }
45
46 let third_digit = input.chars().nth(2).unwrap().to_digit(10).unwrap();
47 if third_digit > 6 {
48 return Err(ValidationError::InvalidFormat);
49 }
50
51 let digits: Vec<u32> = input.chars().map(|c| c.to_digit(10).unwrap()).collect();
52
53 let mut sum = 0u32;
54 for (i, &digit) in digits.iter().enumerate().take(9) {
55 let product = if i % 2 == 0 { digit * 2 } else { digit };
56 if product >= 10 {
57 sum += product - 9;
58 } else {
59 sum += product;
60 }
61 }
62
63 let remainder = if sum.is_multiple_of(10) {
64 0
65 } else {
66 10 - (sum % 10)
67 };
68
69 if remainder != digits[9] {
70 return Err(ValidationError::InvalidCheckDigit);
71 }
72
73 Ok(())
74}
75
76pub fn is_valid(input: &str) -> bool {
91 validate(input).is_ok()
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use proptest::prelude::*;
98
99 #[test]
100 fn valid_cedulas() {
101 assert!(validate("1713175071").is_ok());
102 assert!(validate("0910009000").is_ok());
103 assert!(validate("1710034065").is_ok());
104 }
105
106 #[test]
107 fn invalid_length() {
108 assert_eq!(validate("171317507"), Err(ValidationError::InvalidLength));
109 }
110
111 #[test]
112 fn invalid_province() {
113 assert_eq!(
114 validate("9913175071"),
115 Err(ValidationError::InvalidProvinceCode)
116 );
117 }
118
119 #[test]
120 fn invalid_check_digit() {
121 assert_eq!(
122 validate("1713175072"),
123 Err(ValidationError::InvalidCheckDigit)
124 );
125 }
126
127 #[test]
128 fn non_numeric() {
129 assert!(validate("171317507a").is_err());
130 }
131
132 proptest! {
133 #[test]
134 fn no_panic(s in "\\d{10}") {
135 let _ = validate(&s);
136 }
137 }
138}