1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
/*!
The type [CheckDigitAlgorithm] provides an implementation of the
[Luhn Algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm).
# Example
```rust
use codes_check_digits::{luhn, Calculator};
let calculator = luhn::get_algorithm_instance();
assert!(calculator.is_valid("US0378331005"));
assert!(calculator.validate("US0378331005").is_ok());
assert_eq!(calculator.calculate("US037833100"), Ok(5));
```
*/
use crate::{common::is_ascii_alphanumeric_upper, error::CheckDigitError, Calculator};
// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------
///
/// This algorithm is known by many names: the "Luhn Formula", "The IBM Check",
/// "Mod 10", and is officially specified in "Annex B to ISO/IEC 7812, Part 1"
/// and in "ANSI X4.13". It is used as the check scheme on credit cards such
/// as Visa, Master Card, and American Express.
///
/// # Issues
///
/// It catches all single digit errors, but does not catch transposition errors
/// with "0" and "9" (meaning "09" → "90" and "90" → "09" are not caught).
///
#[derive(Debug, Default)]
pub struct CheckDigitAlgorithm {}
// ------------------------------------------------------------------------------------------------
// Public Functions
// ------------------------------------------------------------------------------------------------
const SHARED_INSTANCE: CheckDigitAlgorithm = CheckDigitAlgorithm {};
pub const fn get_algorithm_instance() -> &'static CheckDigitAlgorithm {
&SHARED_INSTANCE
}
// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------
impl Calculator<u8> for CheckDigitAlgorithm {
fn name(&self) -> &'static str {
"Luhn Algorithm (ISO/IEC 7812, Part 1, Annex B)"
}
fn calculate(&self, s: &str) -> Result<u8, CheckDigitError> {
is_ascii_alphanumeric_upper(s)?;
let sum: u8 = s
.chars()
.flat_map(luhn_alphanum_to_vec)
.rev()
.enumerate()
.map(|(i, n)| luhn_double_odd(i & 1 == 1, n))
.sum();
Ok((10 - (sum % 10)) % 10)
}
}
// ------------------------------------------------------------------------------------------------
// Private Functions
// ------------------------------------------------------------------------------------------------
#[inline(always)]
fn luhn_double_odd(is_odd: bool, d: u8) -> u8 {
if is_odd {
d
} else {
match d {
0..=4 => d * 2,
5 => 1,
6 => 3,
7 => 5,
8 => 7,
9 => 9,
_ => panic!(),
}
}
}
#[inline(always)]
fn luhn_alphanum_to_vec(c: char) -> Vec<u8> {
let d = c as u8;
match d {
b'0'..=b'9' => vec![d - b'0'],
b'A'..=b'Z' => {
let d = d - 55;
vec![d / 10, d % 10]
}
_ => panic!(),
}
}
// ------------------------------------------------------------------------------------------------
// Unit Tests
// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use crate::luhn::CheckDigitAlgorithm;
use crate::Calculator;
#[test]
fn test_check_digits() {
let calculator: CheckDigitAlgorithm = Default::default();
assert!(calculator.validate("US0378331005").is_ok());
assert!(calculator.validate("037833100").is_ok());
}
}