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}