use crate::{
common::{is_ascii_numeric, is_length_eq},
error::CheckDigitError,
Calculator,
};
use std::fmt::Display;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CodeFormat {
Gtin8,
Gtin12,
Gtin13,
Gtin14,
Gln,
Sscc,
LegacyEan8,
LegacyEan13,
LegacyUpcA,
}
#[derive(Clone, Copy, Debug)]
pub struct CheckDigitAlgorithm {
code_type: CodeFormat,
length: usize,
}
pub const fn get_algorithm_instance(code_format: CodeFormat) -> CheckDigitAlgorithm {
CheckDigitAlgorithm::new(code_format)
}
impl Display for CodeFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl CodeFormat {
const fn length(&self) -> usize {
match self {
Self::Gtin8 => 8,
Self::Gtin12 => 12,
Self::Gtin13 => 13,
Self::Gtin14 => 14,
Self::Gln => 13,
Self::Sscc => 18,
Self::LegacyEan8 => 8,
Self::LegacyEan13 => 13,
Self::LegacyUpcA => 12,
}
}
const fn name(&self) -> &'static str {
match self {
Self::Gtin8 => "GS1 GTIN-8",
Self::Gtin12 => "GS1 GTIN-12",
Self::Gtin13 => "GS1 GTIN-13",
Self::Gtin14 => "GS1 GTIN-14",
Self::Gln => "GS1 GLN",
Self::Sscc => "GS1 SSCC",
Self::LegacyEan8 => "GS1 EAN-8",
Self::LegacyEan13 => "GS1 EAN-13",
Self::LegacyUpcA => "GS1 UPC-A",
}
}
}
impl Calculator<u8> for CheckDigitAlgorithm {
fn name(&self) -> &'static str {
self.code_type.name()
}
fn calculate(&self, s: &str) -> Result<u8, CheckDigitError> {
is_length_eq(s, self.length - self.number_of_check_digit_chars())?;
is_ascii_numeric(s)?;
let remainder: u16 = s
.chars()
.rev()
.enumerate()
.map(|(i, c)| gs1_multiply_even_character(i, c) as u16)
.sum::<u16>()
% 10;
if remainder == 0 {
Ok(0)
} else {
Ok(10 - remainder as u8)
}
}
}
impl From<CodeFormat> for CheckDigitAlgorithm {
fn from(code_type: CodeFormat) -> Self {
Self::new(code_type)
}
}
impl CheckDigitAlgorithm {
const fn new(code_type: CodeFormat) -> Self {
Self {
code_type,
length: code_type.length(),
}
}
}
#[inline(always)]
fn gs1_multiply_even_character(i: usize, c: char) -> u8 {
let n = (c as u8) - b'0';
if i & 1 == 0 {
n * 3
} else {
n
}
}
#[cfg(test)]
mod tests {
use crate::gs1::{CheckDigitAlgorithm, CodeFormat};
use crate::Calculator;
#[test]
fn test_check_digits() {
assert_eq!(
CheckDigitAlgorithm::new(CodeFormat::Gln).calculate("123456789012"),
Ok(8)
);
assert_eq!(
CheckDigitAlgorithm::new(CodeFormat::Gln).calculate("210987654321"),
Ok(0)
);
assert_eq!(
CheckDigitAlgorithm::new(CodeFormat::Gln).calculate("943646579210"),
Ok(4)
);
}
}