use super::extract_digits;
#[must_use]
pub fn validate(id: &str) -> bool {
let digits = extract_digits(id);
if digits.len() != 10 {
return false;
}
if digits.chars().all(|c| c == digits.as_bytes()[0] as char) {
return false;
}
let bytes = digits.as_bytes();
let mut sum: u32 = 0;
for (i, b) in bytes.iter().take(9).enumerate() {
sum += (b - b'0') as u32 * (10 - i as u32);
}
let check = (bytes[9] - b'0') as u32;
let r = sum % 11;
if r < 2 {
check == r
} else {
check == 11 - r
}
}
#[must_use]
pub fn expected_check_digit(id: &str) -> Option<u32> {
let digits = extract_digits(id);
if digits.len() < 9 {
return None;
}
let bytes = digits.as_bytes();
let mut sum: u32 = 0;
for (i, b) in bytes.iter().take(9).enumerate() {
sum += (b - b'0') as u32 * (10 - i as u32);
}
let r = sum % 11;
Some(if r < 2 { r } else { 11 - r })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_known_ids() {
for id in ["0499370899", "0084575948", "1729374654"] {
assert!(validate(id), "{id} should be valid");
}
}
#[test]
fn rejects_bad_checksum() {
assert!(!validate("0499370891"));
assert!(!validate("1729374650"));
}
#[test]
fn rejects_repunit() {
for d in '0'..='9' {
let id: String = std::iter::repeat_n(d, 10).collect();
assert!(!validate(&id), "{id} should be rejected");
}
}
#[test]
fn rejects_wrong_length() {
assert!(!validate("12345"));
assert!(!validate("12345678901"));
assert!(!validate(""));
}
#[test]
fn accepts_persian_digits() {
assert!(validate("۰۴۹۹۳۷۰۸۹۹"));
}
#[test]
fn ignores_separators() {
assert!(validate("049-937-0899"));
assert!(validate("049 937 0899"));
}
#[test]
fn check_digit() {
assert_eq!(expected_check_digit("049937089"), Some(9));
}
}