use crate::Gender;
use regex::Regex;
use std::collections::HashMap;
lazy_static! {
static ref PREFIX_LETTERS: HashMap<&'static str, (u32, &'static str)> = {
let mut map = HashMap::new();
map.insert("A", (10, "台北市"));
map.insert("B", (11, "台中市"));
map.insert("C", (12, "基隆市"));
map.insert("D", (13, "台南市"));
map.insert("E", (14, "高雄市"));
map.insert("F", (15, "新北市"));
map.insert("G", (16, "宜兰县"));
map.insert("H", (17, "桃园市"));
map.insert("J", (18, "新竹县"));
map.insert("K", (19, "苗栗县"));
map.insert("L", (20, "台中县")); map.insert("M", (21, "南投县"));
map.insert("N", (22, "彰化县"));
map.insert("P", (23, "云林县"));
map.insert("Q", (24, "嘉义县"));
map.insert("R", (25, "台南县")); map.insert("S", (26, "高雄县")); map.insert("T", (27, "屏东县"));
map.insert("U", (28, "花莲县"));
map.insert("V", (29, "台东县"));
map.insert("X", (30, "澎湖县"));
map.insert("Y", (31, "阳明山管理局")); map.insert("W", (32, "金门县"));
map.insert("Z", (33, "连江县"));
map.insert("I", (34, "嘉义市"));
map.insert("O", (35, "新竹市"));
map
};
static ref PATTERN: Regex = Regex::new(r"^[a-zA-Z][0-9]{9}$").unwrap();
}
pub fn validate(number: &str) -> bool {
let number = number.trim().to_ascii_uppercase();
if number.len() == 10 && PATTERN.is_match(&number) {
let start = &number[0..1];
let sex = &number[1..2];
let mid = &number[1..9];
let end = &number[9..];
if sex != "1" && sex != "2" {
return false;
}
let start = match PREFIX_LETTERS.get(start) {
Some(value) => value,
_ => return false,
};
let mut sum = start.0 / 10 + (start.0 % 10) * 9;
let mut flag = 8;
for ch in mid.chars() {
let i = match ch.to_digit(10) {
Some(value) => value,
_ => return false,
};
sum = sum + i * flag;
flag -= 1;
}
let end = match end.chars().nth(0) {
Some(ch) => match ch.to_digit(10) {
Some(value) => value,
_ => return false,
},
_ => return false,
};
let checksum = if sum % 10 == 0 { 0 } else { 10 - sum % 10 };
checksum == end
} else {
false
}
}
pub fn gender(number: &str) -> Option<Gender> {
if !validate(number) {
return None;
}
if let Some(sex) = number.chars().nth(1) {
if sex == '1' {
Some(Gender::Male)
} else if sex == '2' {
Some(Gender::Female)
} else {
None
}
} else {
None
}
}
pub fn region(number: &str) -> Option<&str> {
if !validate(number) {
return None;
}
let code = &number[0..1];
if !code.is_empty() {
if let Some((_, name)) = PREFIX_LETTERS.get(code) {
Some(*name)
} else {
None
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_tw() {
assert_eq!(validate("A123456789"), true);
assert_eq!(validate("B142610160"), true);
assert_eq!(validate("Q155304682"), true);
assert_eq!(validate("Q155304680"), false);
}
#[test]
fn extract_info() {
let place = region("B142610160");
assert_eq!(place, Some("台中市"));
let place = region("0142610160");
assert_eq!(place, None);
let place = region("Q155304680");
assert_eq!(place, None);
let sex = gender("Q155304682");
assert_eq!(sex, Some(super::super::Gender::Male));
let sex = gender("A225376624");
assert_eq!(sex, Some(super::super::Gender::Female));
let sex = gender("Q155304680");
assert_eq!(sex, None);
}
}