idsmith 0.5.4

Validate and generate checksum-correct IBANs, personal IDs, bank accounts, credit cards, SWIFT/BIC, company IDs, driver's licenses, tax IDs, and passports.
Documentation
use rand::Rng;

use super::date;
use super::{GenOptions, IdResult};

const W: &[u8] = &[3, 7, 9, 5, 8, 4, 2, 1, 6];

pub fn generate(opts: &GenOptions, rng: &mut impl Rng) -> String {
    let (y, m, d) = date::resolve_date(rng, opts.year);
    let date_part = format!("{:02}{:02}{:02}", d, m, y % 100);

    loop {
        let serial: u16 = rng.gen_range(100..=999);
        let mut all_digits: Vec<u8> = vec![
            (serial / 100) as u8,
            ((serial / 10) % 10) as u8,
            (serial % 10) as u8,
        ];
        for b in date_part.bytes() {
            all_digits.push(b - b'0');
        }
        let check: u32 = all_digits
            .iter()
            .zip(W.iter())
            .map(|(&d, &w)| d as u32 * w as u32)
            .sum::<u32>()
            % 11;
        if check < 10 {
            return format!("{:03}{}{}", serial, check, date_part);
        }
    }
}

pub fn validate(code: &str) -> bool {
    if code.len() != 10 || !code.chars().all(|c| c.is_ascii_digit()) {
        return false;
    }
    let d: Vec<u8> = code.bytes().map(|b| b - b'0').collect();
    let mut all_d: Vec<u8> = d[..3].to_vec();
    all_d.extend_from_slice(&d[4..]);
    let check: u32 = all_d
        .iter()
        .zip(W.iter())
        .map(|(&d, &w)| d as u32 * w as u32)
        .sum::<u32>()
        % 11;
    check == d[3] as u32
}

pub fn parse(code: &str) -> IdResult {
    let dd: u8 = code[4..6].parse().unwrap_or(0);
    let mm: u8 = code[6..8].parse().unwrap_or(0);
    let yy: u16 = code[8..10].parse().unwrap_or(0);
    let century: u16 = if yy <= 25 { 2000 } else { 1900 };
    IdResult {
        country_code: "".to_string(),
        code: code.to_string(),
        gender: None,
        dob: Some(format!("{}-{:02}-{:02}", century + yy, mm, dd)),
        valid: validate(code),
    }
}