Functional Weave
Code in Python

validation.luhn@1.0.0

impl/rust.rs

2,949 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one

/// 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<u32> = 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<u32> = 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()))
}