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::{AccountResult, GenOptions};

pub fn generate(_opts: &GenOptions, rng: &mut impl Rng) -> AccountResult {
    // Bank code: 4 digits (0001-9999)
    let bank: u16 = rng.gen_range(1..=9999);
    let bank_str = format!("{:04}", bank);

    // Branch code: 3 digits (001-999)
    let branch: u16 = rng.gen_range(1..=999);
    let branch_str = format!("{:03}", branch);

    // Account type: 1 digit (1=ordinary, 2=current, 4=savings)
    let acct_types = [1u8, 2, 4];
    let acct_type = acct_types[rng.gen_range(0..acct_types.len())];
    let type_str = format!("{}", acct_type);

    // Account number: 7 digits
    let account: String = (0..7)
        .map(|_| (b'0' + rng.gen_range(0..=9u8)) as char)
        .collect();

    let formatted = format!("{}-{}-{}-{}", bank_str, branch_str, type_str, account);
    let raw = format!("{}{}{}{}", bank_str, branch_str, type_str, account);

    AccountResult {
        country_code: "JP".into(),
        country_name: crate::countries::get_country_name("JP")
            .unwrap_or("Unknown")
            .to_string(),
        format_name: "Bank + Branch + Account".into(),
        bank_code: Some(bank_str),
        branch_code: Some(branch_str),
        account_number: account,
        check_digits: None,
        formatted,
        raw,
        iban: None,
        valid: true,
    }
}

pub fn validate(raw: &str) -> bool {
    if raw.len() != 15 {
        return false;
    }
    raw.chars().all(|c| c.is_ascii_digit())
}

pub fn format(raw: &str) -> String {
    if raw.len() == 15 {
        format!("{}-{}-{}-{}", &raw[..4], &raw[4..7], &raw[7..8], &raw[8..])
    } else {
        raw.to_string()
    }
}