use super::funejson::Value; /// The Luhn (mod 10) checksum, as used by payment card numbers, IMEIs, /// SIM ICCIDs and a long tail of national identifiers. /// /// Luhn is a *transcription* check. It catches a single mistyped digit and /// most adjacent transpositions before a request leaves the building. It says /// nothing about whether the card exists, is open, belongs to the person /// typing it, or has any money behind it. Only the acquirer can answer that, /// so never render "card valid" on the strength of this function. pub fn is_luhn(value: &str) -> bool { let mut digits: Vec = Vec::new(); for ch in value.chars() { // Card numbers are printed and pasted in four-digit groups, so // tolerating the separators here saves every caller the same strip. if ch == ' ' || ch == '-' { continue; } if ch < '0' || ch > '9' { return false; } digits.push(ch as u32 - 48); } // A lone digit satisfies the arithmetic whenever it is 0, which would make // this function a rubber stamp for a stray keystroke. No identifier scheme // issues one-digit numbers, so two is the honest floor. if digits.len() < 2 { return false; } let mut total: u32 = 0; for (position, digit) in digits.iter().rev().enumerate() { let mut d = *digit; if position % 2 == 1 { d *= 2; // Doubling can only reach 18, so subtracting 9 is the same as // summing the two decimal digits, without the string round trip. if d > 9 { d -= 9; } } total += d; } total % 10 == 0 } /// The digit that would make `prefix` pass the Luhn check. /// /// Useful for generating test data and for completing a partially known /// number; it is the inverse of the check above, not a second opinion on it. /// Returns -1 when the prefix is not usable digits at all. pub fn luhn_check_digit(prefix: &str) -> i64 { let mut digits: Vec = Vec::new(); for ch in prefix.chars() { if ch == ' ' || ch == '-' { continue; } if ch < '0' || ch > '9' { return -1; } digits.push(ch as u32 - 48); } if digits.is_empty() { return -1; } let mut total: u32 = 0; // The check digit will occupy position 0, so the prefix starts at 1. for (position, digit) in digits.iter().rev().enumerate() { let mut d = *digit; if position % 2 == 0 { d *= 2; if d > 9 { d -= 9; } } total += d; } ((10 - (total % 10)) % 10) as i64 } pub fn fune_vector(args: &[Value]) -> Value { // A non-string argument arrives here as an empty string, which is exactly // the answer TypeScript and Python give for a non-string: not valid. Value::Bool(is_luhn(args[0].as_str())) }