use crate::secondary_validation::{Validator, get_next_digit};
pub struct SingaporeNricChecksum;
const WEIGHTS: &[u32; 7] = &[2, 7, 6, 5, 4, 3, 2];
const EXPECTED_CHAR_COUNT: usize = 9;
fn get_checksum_character(c: char, remainder: usize) -> char {
let remainder_vector = match c {
'S' | 'T' => vec!['J', 'Z', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A'],
'F' | 'G' => vec!['X', 'W', 'U', 'T', 'R', 'Q', 'P', 'N', 'M', 'L', 'K'],
'M' => vec!['K', 'L', 'J', 'N', 'P', 'Q', 'R', 'T', 'U', 'W', 'X'],
_ => vec![],
};
*remainder_vector.get(remainder).unwrap()
}
impl Validator for SingaporeNricChecksum {
fn is_valid_match(&self, regex_match: &str) -> bool {
if regex_match.chars().count() != EXPECTED_CHAR_COUNT {
return false;
}
let mut chars = regex_match.chars();
let prefix = match chars.next() {
Some(c) => c.to_uppercase().next().unwrap_or(c),
None => return false,
};
let offset = match prefix {
'S' | 'F' => 0,
'T' | 'G' => 4,
'M' => 3,
_ => return false,
};
let mut sum: u32 = 0;
for &weight in WEIGHTS {
let digit = match get_next_digit(&mut chars) {
Some(d) => d,
None => return false,
};
sum += digit * weight;
}
let mut remainder = ((sum + offset) % 11) as usize;
if prefix == 'M' {
remainder = 10 - remainder;
}
let expected_checksum = get_checksum_character(prefix, remainder);
let actual_checksum = chars.next().map(|c| c.to_uppercase().next().unwrap_or(c));
actual_checksum == Some(expected_checksum)
}
}
#[cfg(test)]
mod test {
use crate::secondary_validation::*;
#[test]
fn test_valid_numbers() {
let validator = SingaporeNricChecksum;
let valid_numbers = vec![
"S6859080I",
"T1680536F",
"M9612050P",
"F5221211K",
"G8169811U",
"g3970684p",
"M2200054X",
];
for number in valid_numbers {
assert!(validator.is_valid_match(number));
}
}
#[test]
fn test_invalid_numbers() {
let validator = SingaporeNricChecksum;
let invalid_numbers = vec![
"A",
"111111111",
"XXXXXXXXX",
"I6859080I",
"M9612050Q",
"A1234567A",
"g3970683p",
];
for number in invalid_numbers {
assert!(!validator.is_valid_match(number));
}
}
}