codes_check_digits/
gs1.rs

1/*!
2The type [CheckDigitAlgorithm] provides an implementation of the
3check digits specified for GS1 data types.
4
5# Example
6
7```rust
8use codes_check_digits::{gs1, Calculator};
9
10let calculator = gs1::get_algorithm_instance(gs1::CodeFormat::Gln);
11assert!(calculator.is_valid("9436465792104"));
12assert!(calculator.validate("9436465792104").is_ok());
13assert_eq!(calculator.calculate("943646579210"), Ok(4));
14```
15
16*/
17
18use crate::{
19    common::{is_ascii_numeric, is_length_eq},
20    error::CheckDigitError,
21    Calculator,
22};
23use std::fmt::Display;
24
25// ------------------------------------------------------------------------------------------------
26// Public Macros
27// ------------------------------------------------------------------------------------------------
28
29// ------------------------------------------------------------------------------------------------
30// Public Types
31// ------------------------------------------------------------------------------------------------
32
33///
34/// This enumeration denotes the type of code to be validated.
35///
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub enum CodeFormat {
38    /// Global Trade Item Number GTIN-8
39    Gtin8,
40    /// Global Trade Item Number GTIN-12
41    Gtin12,
42    /// Global Trade Item Number GTIN-13
43    Gtin13,
44    /// Global Trade Item Number GTIN-14
45    Gtin14,
46    /// Global Location Number (GLN)
47    Gln,
48    /// Serial Shipping Container Code (SSCC)
49    Sscc,
50    /// European Article Number EAN-8
51    LegacyEan8,
52    /// European Article Number EAN-13
53    LegacyEan13,
54    /// Universal Product Code UPC-A
55    LegacyUpcA,
56}
57
58///
59/// Validate the different identifiers defined by GS1.
60///
61#[derive(Clone, Copy, Debug)]
62pub struct CheckDigitAlgorithm {
63    code_type: CodeFormat,
64    length: usize,
65}
66
67// ------------------------------------------------------------------------------------------------
68// Public Functions
69// ------------------------------------------------------------------------------------------------
70
71pub const fn get_algorithm_instance(code_format: CodeFormat) -> CheckDigitAlgorithm {
72    CheckDigitAlgorithm::new(code_format)
73}
74
75// ------------------------------------------------------------------------------------------------
76// Private Types
77// ------------------------------------------------------------------------------------------------
78
79// ------------------------------------------------------------------------------------------------
80// Implementations
81// ------------------------------------------------------------------------------------------------
82
83impl Display for CodeFormat {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        write!(f, "{}", self.name())
86    }
87}
88
89impl CodeFormat {
90    const fn length(&self) -> usize {
91        match self {
92            Self::Gtin8 => 8,
93            Self::Gtin12 => 12,
94            Self::Gtin13 => 13,
95            Self::Gtin14 => 14,
96            Self::Gln => 13,
97            Self::Sscc => 18,
98            Self::LegacyEan8 => 8,
99            Self::LegacyEan13 => 13,
100            Self::LegacyUpcA => 12,
101        }
102    }
103    const fn name(&self) -> &'static str {
104        match self {
105            Self::Gtin8 => "GS1 GTIN-8",
106            Self::Gtin12 => "GS1 GTIN-12",
107            Self::Gtin13 => "GS1 GTIN-13",
108            Self::Gtin14 => "GS1 GTIN-14",
109            Self::Gln => "GS1 GLN",
110            Self::Sscc => "GS1 SSCC",
111            Self::LegacyEan8 => "GS1 EAN-8",
112            Self::LegacyEan13 => "GS1 EAN-13",
113            Self::LegacyUpcA => "GS1 UPC-A",
114        }
115    }
116}
117
118// ------------------------------------------------------------------------------------------------
119
120impl Calculator<u8> for CheckDigitAlgorithm {
121    fn name(&self) -> &'static str {
122        self.code_type.name()
123    }
124
125    fn calculate(&self, s: &str) -> Result<u8, CheckDigitError> {
126        is_length_eq(s, self.length - self.number_of_check_digit_chars())?;
127        is_ascii_numeric(s)?;
128        let remainder: u16 = s
129            .chars()
130            .rev()
131            .enumerate()
132            .map(|(i, c)| gs1_multiply_even_character(i, c) as u16)
133            .sum::<u16>()
134            % 10;
135        if remainder == 0 {
136            Ok(0)
137        } else {
138            Ok(10 - remainder as u8)
139        }
140    }
141}
142
143impl From<CodeFormat> for CheckDigitAlgorithm {
144    fn from(code_type: CodeFormat) -> Self {
145        Self::new(code_type)
146    }
147}
148
149impl CheckDigitAlgorithm {
150    const fn new(code_type: CodeFormat) -> Self {
151        Self {
152            code_type,
153            length: code_type.length(),
154        }
155    }
156}
157
158// ------------------------------------------------------------------------------------------------
159// Private Functions
160// ------------------------------------------------------------------------------------------------
161
162#[inline(always)]
163fn gs1_multiply_even_character(i: usize, c: char) -> u8 {
164    let n = (c as u8) - b'0';
165    if i & 1 == 0 {
166        n * 3
167    } else {
168        n
169    }
170}
171
172// ------------------------------------------------------------------------------------------------
173// Unit Tests
174// ------------------------------------------------------------------------------------------------
175
176#[cfg(test)]
177mod tests {
178    use crate::gs1::{CheckDigitAlgorithm, CodeFormat};
179    use crate::Calculator;
180
181    #[test]
182    fn test_check_digits() {
183        assert_eq!(
184            CheckDigitAlgorithm::new(CodeFormat::Gln).calculate("123456789012"),
185            Ok(8)
186        );
187        assert_eq!(
188            CheckDigitAlgorithm::new(CodeFormat::Gln).calculate("210987654321"),
189            Ok(0)
190        );
191        assert_eq!(
192            CheckDigitAlgorithm::new(CodeFormat::Gln).calculate("943646579210"),
193            Ok(4)
194        );
195    }
196}