use rand::Rng;
use std::collections::HashMap;
pub fn is_valid(voter_id: &str) -> bool {
if !voter_id.chars().all(|c| c.is_ascii_digit()) {
return false;
}
if !is_length_valid(voter_id) {
return false;
}
let sequential_number = get_sequential_number(voter_id);
let federative_union = get_federative_union(voter_id);
let verifying_digits = get_verifying_digits(voter_id);
if !is_federative_union_valid(&federative_union) {
return false;
}
let vd1 = calculate_vd1(&sequential_number, &federative_union);
if vd1
!= verifying_digits
.chars().next()
.and_then(|c| c.to_digit(10))
.unwrap_or(99) as u8
{
return false;
}
let vd2 = calculate_vd2(&federative_union, vd1);
if vd2
!= verifying_digits
.chars()
.nth(1)
.and_then(|c| c.to_digit(10))
.unwrap_or(99) as u8
{
return false;
}
true
}
pub fn format_voter_id(voter_id: &str) -> Option<String> {
if !is_valid(voter_id) {
return None;
}
Some(format!(
"{} {} {} {}",
&voter_id[0..4],
&voter_id[4..8],
&voter_id[8..10],
&voter_id[10..12]
))
}
pub fn generate(federative_union: Option<&str>) -> Option<String> {
let ufs = get_uf_map();
let uf = federative_union.unwrap_or("ZZ").to_uppercase();
if let Some(uf_number) = ufs.get(uf.as_str()) {
if is_federative_union_valid(uf_number) {
let mut rng = rand::thread_rng();
let sequential_number = format!("{:08}", rng.gen_range(0..100000000));
let vd1 = calculate_vd1(&sequential_number, uf_number);
let vd2 = calculate_vd2(uf_number, vd1);
return Some(format!("{}{}{}{}", sequential_number, uf_number, vd1, vd2));
}
}
None
}
fn is_length_valid(voter_id: &str) -> bool {
let len = voter_id.len();
if len == 12 {
return true;
}
if len == 13 {
let federative_union = get_federative_union(voter_id);
return federative_union == "01" || federative_union == "02";
}
false
}
fn get_sequential_number(voter_id: &str) -> String {
voter_id[..8].to_string()
}
fn get_federative_union(voter_id: &str) -> String {
let len = voter_id.len();
voter_id[len - 4..len - 2].to_string()
}
fn get_verifying_digits(voter_id: &str) -> String {
let len = voter_id.len();
voter_id[len - 2..].to_string()
}
fn is_federative_union_valid(federative_union: &str) -> bool {
if let Ok(num) = federative_union.parse::<u8>() {
(1..=28).contains(&num)
} else {
false
}
}
pub fn calculate_vd1(sequential_number: &str, federative_union: &str) -> u8 {
if sequential_number.len() < 8 {
return 0;
}
let weights = [2, 3, 4, 5, 6, 7, 8, 9];
let mut sum = 0;
for (i, weight) in weights.iter().enumerate() {
if let Some(digit) = sequential_number
.chars()
.nth(i)
.and_then(|c| c.to_digit(10))
{
sum += digit * weight;
}
}
let rest = (sum % 11) as u8;
let mut vd1 = rest;
if rest == 0 && (federative_union == "01" || federative_union == "02") {
vd1 = 1;
}
if rest == 10 {
vd1 = 0;
}
vd1
}
pub fn calculate_vd2(federative_union: &str, vd1: u8) -> u8 {
if federative_union.len() < 2 {
return 0;
}
let weights = [7, 8, 9];
let mut sum = 0;
if let Some(d1) = federative_union.chars().next().and_then(|c| c.to_digit(10)) {
sum += d1 * weights[0];
}
if let Some(d2) = federative_union.chars().nth(1).and_then(|c| c.to_digit(10)) {
sum += d2 * weights[1];
}
sum += vd1 as u32 * weights[2];
let rest = (sum % 11) as u8;
let mut vd2 = rest;
if rest == 0 && (federative_union == "01" || federative_union == "02") {
vd2 = 1;
}
if rest == 10 {
vd2 = 0;
}
vd2
}
fn get_uf_map() -> HashMap<&'static str, &'static str> {
let mut map = HashMap::new();
map.insert("SP", "01");
map.insert("MG", "02");
map.insert("RJ", "03");
map.insert("RS", "04");
map.insert("BA", "05");
map.insert("PR", "06");
map.insert("CE", "07");
map.insert("PE", "08");
map.insert("SC", "09");
map.insert("GO", "10");
map.insert("MA", "11");
map.insert("PB", "12");
map.insert("PA", "13");
map.insert("ES", "14");
map.insert("PI", "15");
map.insert("RN", "16");
map.insert("AL", "17");
map.insert("MT", "18");
map.insert("MS", "19");
map.insert("DF", "20");
map.insert("SE", "21");
map.insert("AM", "22");
map.insert("RO", "23");
map.insert("AC", "24");
map.insert("AP", "25");
map.insert("RR", "26");
map.insert("TO", "27");
map.insert("ZZ", "28"); map
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_valid() {
assert!(is_valid("690847092828"));
assert!(is_valid("163204010922"));
assert!(!is_valid("123456789012"));
assert!(!is_valid("690847092829"));
assert!(!is_valid("123"));
assert!(!is_valid("12345678901234"));
assert!(!is_valid("6908470928a8"));
assert!(!is_valid(""));
}
#[test]
fn test_format_voter_id() {
assert_eq!(
format_voter_id("690847092828"),
Some("6908 4709 28 28".to_string())
);
assert_eq!(
format_voter_id("163204010922"),
Some("1632 0401 09 22".to_string())
);
assert_eq!(format_voter_id("123456789012"), None);
assert_eq!(format_voter_id("123"), None);
}
#[test]
fn test_generate() {
let voter_id = generate(Some("SP")).unwrap();
assert_eq!(voter_id.len(), 12);
assert!(is_valid(&voter_id));
assert_eq!(get_federative_union(&voter_id), "01");
let voter_id_zz = generate(None).unwrap();
assert_eq!(voter_id_zz.len(), 12);
assert!(is_valid(&voter_id_zz));
assert_eq!(get_federative_union(&voter_id_zz), "28");
assert_eq!(generate(Some("XX")), None);
}
#[test]
fn test_generate_uniqueness() {
let mut voter_ids = std::collections::HashSet::new();
for _ in 0..100 {
let voter_id = generate(Some("RJ")).unwrap();
assert!(is_valid(&voter_id));
voter_ids.insert(voter_id);
}
assert!(voter_ids.len() >= 95);
}
#[test]
fn test_calculate_vd1() {
assert_eq!(calculate_vd1("69084709", "28"), 2);
assert_eq!(calculate_vd1("16320401", "09"), 2);
}
#[test]
fn test_calculate_vd2() {
assert_eq!(calculate_vd2("28", 2), 8);
assert_eq!(calculate_vd2("09", 2), 2);
}
#[test]
fn test_get_sequential_number() {
assert_eq!(get_sequential_number("690847092828"), "69084709");
assert_eq!(get_sequential_number("163204010922"), "16320401");
}
#[test]
fn test_get_federative_union() {
assert_eq!(get_federative_union("690847092828"), "28");
assert_eq!(get_federative_union("163204010922"), "09");
}
#[test]
fn test_get_verifying_digits() {
assert_eq!(get_verifying_digits("690847092828"), "28");
assert_eq!(get_verifying_digits("163204010922"), "22");
}
#[test]
fn test_is_federative_union_valid() {
assert!(is_federative_union_valid("01"));
assert!(is_federative_union_valid("28"));
assert!(!is_federative_union_valid("00"));
assert!(!is_federative_union_valid("29"));
assert!(!is_federative_union_valid("XX"));
}
#[test]
fn test_is_length_valid() {
assert!(is_length_valid("690847092828")); assert!(!is_length_valid("123")); assert!(!is_length_valid("12345678901234")); }
#[test]
fn test_edge_case_sp_mg() {
for _ in 0..10 {
let voter_id_sp = generate(Some("SP")).unwrap();
assert!(is_valid(&voter_id_sp));
let voter_id_mg = generate(Some("MG")).unwrap();
assert!(is_valid(&voter_id_mg));
}
}
}