use rand::Rng;
const SIZE: usize = 11;
const BLACKLIST: [&str; 12] = [
"000",
"00000000000",
"11111111111",
"22222222222",
"33333333333",
"44444444444",
"55555555555",
"66666666666",
"77777777777",
"88888888888",
"99999999999",
"999999999999",
];
pub fn remove_symbols(dirty: &str) -> String {
dirty.chars().filter(|c| *c != '.' && *c != '-').collect()
}
pub fn format_cpf(cpf: &str) -> Option<String> {
if !is_valid(cpf) {
return None;
}
Some(format!(
"{}.{}.{}-{}",
&cpf[0..3],
&cpf[3..6],
&cpf[6..9],
&cpf[9..11]
))
}
pub fn validate(cpf: &str) -> bool {
if !cpf.chars().all(|c| c.is_ascii_digit()) || cpf.len() != SIZE {
return false;
}
if is_blacklisted(cpf) {
return false;
}
is_valid_checksum(cpf)
}
pub fn is_valid(cpf: &str) -> bool {
validate(cpf)
}
pub fn generate() -> String {
let mut rng = rand::thread_rng();
let base = format!("{:09}", rng.gen_range(1..=999999999));
let checksum = compute_checksum(&base);
format!("{}{}", base, checksum)
}
pub fn hashdigit(cpf: &str, position: usize) -> usize {
let sum: usize = cpf
.chars()
.take(position - 1)
.enumerate()
.map(|(i, c)| {
let digit = c.to_digit(10).unwrap() as usize;
let weight = position - i;
digit * weight
})
.sum();
let val = sum % 11;
if val < 2 {
0
} else {
11 - val
}
}
pub fn compute_checksum(basenum: &str) -> String {
let digit1 = hashdigit(basenum, 10);
let with_digit1 = format!("{}{}", basenum, digit1);
let digit2 = hashdigit(&with_digit1, 11);
format!("{}{}", digit1, digit2)
}
fn is_blacklisted(input: &str) -> bool {
BLACKLIST.contains(&input)
}
fn is_valid_checksum(input: &str) -> bool {
let digit1 = hashdigit(input, 10);
let digit2 = hashdigit(input, 11);
input.chars().nth(9).unwrap().to_digit(10).unwrap() == digit1 as u32
&& input.chars().nth(10).unwrap().to_digit(10).unwrap() == digit2 as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_symbols() {
assert_eq!(remove_symbols("00000000000"), "00000000000");
assert_eq!(remove_symbols("123.456.789-10"), "12345678910");
assert_eq!(remove_symbols("134..2435.-1892.-"), "13424351892");
assert_eq!(remove_symbols("abc1230916*!*&#"), "abc1230916*!*&#");
assert_eq!(
remove_symbols("ab.c1.--.2-309.-1-.6-.*.-!*&#"),
"abc1230916*!*&#"
);
assert_eq!(remove_symbols("...---..."), "");
}
#[test]
fn test_format_cpf() {
assert_eq!(
format_cpf("82178537464"),
Some("821.785.374-64".to_string())
);
assert_eq!(
format_cpf("55550207753"),
Some("555.502.077-53".to_string())
);
assert_eq!(
format_cpf("11144477735"),
Some("111.444.777-35".to_string())
);
assert_eq!(format_cpf("00000000000"), None);
assert_eq!(format_cpf("12345678901"), None);
assert_eq!(format_cpf("1234567890"), None);
assert_eq!(format_cpf("123456789012"), None);
}
#[test]
fn test_validate() {
assert!(validate("52513127765"));
assert!(validate("52599927765"));
assert!(validate("82178537464"));
assert!(validate("55550207753"));
assert!(!validate("00000000000"));
assert!(!validate("11111111111"));
assert!(!validate("12345678901"));
assert!(!validate("123456789"));
assert!(!validate("12345678901a"));
}
#[test]
fn test_is_valid() {
assert!(is_valid("96271845860"));
assert!(is_valid("40364478829"));
assert!(is_valid("11144477735"));
assert!(is_valid("82178537464"));
assert!(is_valid("55550207753"));
assert!(!is_valid("1"));
assert!(!is_valid("123456789"));
assert!(!is_valid("123456789012"));
assert!(!is_valid("1112223334-"));
assert!(!is_valid("111.444.777-35"));
for input in BLACKLIST.iter() {
assert!(!is_valid(input));
}
assert!(!is_valid("11144477705"));
assert!(!is_valid("11144477732"));
assert!(!is_valid("11111111215"));
}
#[test]
fn test_generate() {
for _ in 0..1000 {
let cpf = generate();
assert_eq!(cpf.len(), 11);
assert!(is_valid(&cpf));
assert!(cpf.chars().all(|c| c.is_ascii_digit()));
}
}
#[test]
fn test_hashdigit() {
assert_eq!(hashdigit("000000000", 10), 0);
assert_eq!(hashdigit("0000000000", 11), 0);
assert_eq!(hashdigit("52513127765", 10), 6);
assert_eq!(hashdigit("52513127765", 11), 5);
assert_eq!(hashdigit("52599927765", 10), 6);
assert_eq!(hashdigit("52599927765", 11), 5);
}
#[test]
fn test_compute_checksum() {
assert_eq!(compute_checksum("000000000"), "00");
assert_eq!(compute_checksum("525131277"), "65");
assert_eq!(compute_checksum("335451269"), "51");
assert_eq!(compute_checksum("382916331"), "26");
}
#[test]
fn test_is_blacklisted() {
assert!(is_blacklisted("00000000000"));
assert!(is_blacklisted("11111111111"));
assert!(is_blacklisted("99999999999"));
assert!(!is_blacklisted("12345678901"));
}
#[test]
fn test_is_valid_checksum() {
assert!(is_valid_checksum("11144477735"));
assert!(is_valid_checksum("96271845860"));
assert!(!is_valid_checksum("11144477705"));
assert!(!is_valid_checksum("11144477732"));
}
#[test]
fn test_edge_cases() {
assert!(!is_valid(""));
assert!(!is_valid("!@#$%^&*()_"));
assert!(!is_valid("111444777a5"));
}
#[test]
fn test_format_with_symbols() {
let cpf_with_symbols = "821.785.374-64";
let cpf_clean = remove_symbols(cpf_with_symbols);
assert_eq!(format_cpf(&cpf_clean), Some("821.785.374-64".to_string()));
}
#[test]
fn test_generate_uniqueness() {
let cpf1 = generate();
let cpf2 = generate();
let cpf3 = generate();
assert!(is_valid(&cpf1));
assert!(is_valid(&cpf2));
assert!(is_valid(&cpf3));
}
}