use rand::Rng;
const WEIGHTS: [u32; 10] = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
pub fn remove_symbols(pis: &str) -> String {
pis.replace(".", "").replace("-", "")
}
pub fn checksum(base_pis: &str) -> u32 {
let pis_digits: Vec<u32> = base_pis.chars().filter_map(|c| c.to_digit(10)).collect();
let pis_sum: u32 = pis_digits
.iter()
.zip(WEIGHTS.iter())
.map(|(digit, weight)| digit * weight)
.sum();
let check_digit = 11 - (pis_sum % 11);
if check_digit == 10 || check_digit == 11 {
0
} else {
check_digit
}
}
pub fn is_valid(pis: &str) -> bool {
if pis.len() != 11 {
return false;
}
if !pis.chars().all(|c| c.is_ascii_digit()) {
return false;
}
let expected_check_digit = checksum(&pis[..10]);
let actual_check_digit = pis.chars().nth(10).and_then(|c| c.to_digit(10));
match actual_check_digit {
Some(digit) => digit == expected_check_digit,
None => false,
}
}
pub fn format_pis(pis: &str) -> Option<String> {
if !is_valid(pis) {
return None;
}
Some(format!(
"{}.{}.{}-{}",
&pis[0..3],
&pis[3..8],
&pis[8..10],
&pis[10..11]
))
}
pub fn generate() -> String {
let mut rng = rand::thread_rng();
let base = format!("{:010}", rng.gen_range(0..10000000000u64));
let check_digit = checksum(&base);
format!("{}{}", base, check_digit)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_remove_symbols() {
assert_eq!(remove_symbols("123.456.789-09"), "12345678909");
assert_eq!(remove_symbols("987.654.321-00"), "98765432100");
assert_eq!(remove_symbols("12345678909"), "12345678909");
assert_eq!(remove_symbols("170.24354.75-7"), "17024354757");
}
#[test]
fn test_checksum() {
assert_eq!(checksum("1234567890"), 0);
assert_eq!(checksum("9876543210"), 3);
assert_eq!(checksum("1702435475"), 3);
assert_eq!(checksum("8217853746"), 7);
assert_eq!(checksum("5555020775"), 6);
}
#[test]
fn test_is_valid() {
assert!(is_valid("12345678900"));
assert!(is_valid("98765432103"));
assert!(is_valid("17024354753"));
assert!(is_valid("82178537467"));
assert!(is_valid("55550207756"));
assert!(!is_valid("12345678901")); assert!(!is_valid("123")); assert!(!is_valid("123456789012")); assert!(!is_valid("1234567890a")); assert!(!is_valid("")); }
#[test]
fn test_format_pis() {
assert_eq!(
format_pis("12345678900"),
Some("123.45678.90-0".to_string())
);
assert_eq!(
format_pis("98765432103"),
Some("987.65432.10-3".to_string())
);
assert_eq!(
format_pis("17024354753"),
Some("170.24354.75-3".to_string())
);
assert_eq!(format_pis("123"), None);
assert_eq!(format_pis("12345678901"), None); }
#[test]
fn test_generate() {
for _ in 0..10 {
let pis = generate();
assert_eq!(pis.len(), 11);
assert!(pis.chars().all(|c| c.is_ascii_digit()));
assert!(is_valid(&pis));
}
}
#[test]
fn test_generate_uniqueness() {
let pis1 = generate();
let pis2 = generate();
let pis3 = generate();
assert!(pis1 != pis2 || pis2 != pis3);
}
#[test]
fn test_format_and_remove_symbols_roundtrip() {
let pis = "12345678900";
let formatted = format_pis(pis).unwrap();
let cleaned = remove_symbols(&formatted);
assert_eq!(cleaned, pis);
}
#[test]
fn test_generated_pis_can_be_formatted() {
let pis = generate();
let formatted = format_pis(&pis);
assert!(formatted.is_some());
let cleaned = remove_symbols(&formatted.unwrap());
assert_eq!(cleaned, pis);
}
}