const MAX_LUHN_DIGITS: usize = 25;
const MAX_MOD97_BYTES: usize = 34;
pub struct LuhnValidator;
impl LuhnValidator {
#[must_use]
pub fn validate(value: &str) -> bool {
if value.is_empty() || value.len() > MAX_LUHN_DIGITS {
return false;
}
if !value.chars().all(|c| c.is_ascii_digit()) {
return false;
}
let mut sum = 0;
let mut is_second = false;
for ch in value.chars().rev() {
let digit = ch.to_digit(10).expect("pre-filtered to numeric chars only") as usize;
let processed = if is_second {
let doubled = digit * 2;
if doubled > 9 { doubled - 9 } else { doubled }
} else {
digit
};
sum += processed;
is_second = !is_second;
}
sum % 10 == 0
}
#[must_use]
pub const fn error_message() -> &'static str {
"Invalid checksum (Luhn algorithm)"
}
}
pub struct Mod97Validator;
impl Mod97Validator {
#[must_use]
pub fn validate(value: &str) -> bool {
if value.len() < 4 || value.len() > MAX_MOD97_BYTES {
return false;
}
let value_upper = value.to_uppercase();
if !value_upper.chars().all(|c| c.is_ascii_alphanumeric()) {
return false;
}
let rearranged = format!("{}{}", &value_upper[4..], &value_upper[..4]);
let mut numeric = String::new();
for ch in rearranged.chars() {
if ch.is_ascii_digit() {
numeric.push(ch);
} else if ch.is_ascii_uppercase() {
numeric.push_str(&(10 + (ch as usize - 'A' as usize)).to_string());
} else {
return false;
}
}
let remainder = Self::mod97(&numeric);
remainder == 1
}
fn mod97(numeric: &str) -> u32 {
let mut remainder = 0u32;
for digit_char in numeric.chars() {
if let Some(digit) = digit_char.to_digit(10) {
remainder = (remainder * 10 + digit) % 97;
}
}
remainder
}
#[must_use]
pub const fn error_message() -> &'static str {
"Invalid checksum (MOD-97 algorithm)"
}
}