mod address_codes;
use address_codes::DEFAULT_ADDRESS_CODES;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressMatchMode {
Exact6,
Fallback64,
}
impl Default for AddressMatchMode {
fn default() -> Self {
AddressMatchMode::Fallback64
}
}
pub fn is_valid(id: &str) -> bool {
is_valid_with_mode(id, AddressMatchMode::default())
}
pub fn is_valid_with_mode(
id: &str,
mode: AddressMatchMode,
) -> bool {
is_valid_with_map_and_mode(id, &DEFAULT_ADDRESS_CODES, mode)
}
pub fn is_valid_with_map(id: &str, codes: &HashMap<&str, &str>) -> bool {
is_valid_with_map_and_mode(id, codes, AddressMatchMode::default())
}
pub fn is_valid_with_map_and_mode(
id: &str,
codes: &HashMap<&str, &str>,
mode: AddressMatchMode,
) -> bool {
let id = id.trim();
if id.len() != 18 {
return false;
}
let body = &id[..17];
if !body.chars().all(|c| c.is_ascii_digit()) {
return false;
}
let last = match id.chars().nth(17) {
Some(c) => c.to_ascii_uppercase(),
None => return false,
};
if !(last.is_ascii_digit() || last == 'X') {
return false;
}
match id.chars().next() {
Some('0') => return false,
None => return false,
_ => {}
}
if !is_valid_date(&id[6..14]) {
return false;
}
if !verify_checksum(body, last) {
return false;
}
match mode {
AddressMatchMode::Exact6 => is_address_exact6(id, codes),
AddressMatchMode::Fallback64 => is_address_fallback64(id, codes),
}
}
pub(crate) fn is_valid_date(s: &str) -> bool {
if s.len() != 8 {
return false;
}
let year = match s[0..4].parse::<u32>() {
Ok(v) => v,
Err(_) => return false,
};
let month = match s[4..6].parse::<u32>() {
Ok(v) => v,
Err(_) => return false,
};
let day = match s[6..8].parse::<u32>() {
Ok(v) => v,
Err(_) => return false,
};
if month < 1 || month > 12 {
return false;
}
let days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let max_day = if month == 2 && is_leap_year(year) {
29
} else {
days_in_month[(month - 1) as usize]
};
day >= 1 && day <= max_day
}
#[inline]
pub(crate) fn is_leap_year(y: u32) -> bool {
(y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)
}
pub(crate) const WEIGHTS: [u32; 17] =
[7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
pub(crate) const CHECK_MAP: &[u8; 11] = b"10X98765432";
pub(crate) fn verify_checksum(body: &str, last: char) -> bool {
let mut sum = 0u32;
for (i, c) in body.chars().enumerate() {
let d = match c.to_digit(10) {
Some(v) => v,
None => return false,
};
sum += d * WEIGHTS[i];
}
let expected = CHECK_MAP[(sum % 11) as usize] as char;
expected == last
}
pub(crate) fn is_address_exact6(id: &str, codes: &HashMap<&str, &str>) -> bool {
let code6 = match id.get(0..6) {
Some(c) => c,
None => return false,
};
codes.contains_key(code6)
}
pub(crate) fn is_address_fallback64(id: &str, codes: &HashMap<&str, &str>) -> bool {
let code6 = match id.get(0..6) {
Some(c) => c,
None => return false,
};
if codes.contains_key(code6) {
return true;
}
let code4 = match id.get(0..4) {
Some(c) => format!("{}00", c),
None => return false,
};
if codes.contains_key(code4.as_str()) {
return true;
}
let code2 = match id.get(0..2) {
Some(c) => format!("{}0000", c),
None => return false,
};
if codes.contains_key(code2.as_str()) {
return true;
}
false
}
pub fn generate_test_id_with_address_code(area_code: &str, birth_date: &str, seq: &str) -> Option<String> {
let base = format!("{}{}{}", area_code, birth_date, seq);
if base.len() != 17 {
return None;
}
let mut sum: u32 = 0;
for (i, c) in base.chars().enumerate() {
let digit = match c.to_digit(10) {
Some(d) => d,
None => return None, };
sum += digit * WEIGHTS[i];
}
let check = CHECK_MAP[(sum % 11) as usize] as char;
Some(format!("{}{}", base, check))
}