Skip to main content

gtin_validate/gtin12/
mod.rs

1//! Performs validation and correction of GTIN-12 and UPC-A codes.
2
3use utils;
4
5/// Errors that make GTIN-12 correction impossible.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum FixError {
8    /// The provided string contains non-ASCII characters.
9    NonAsciiString,
10    /// The provided code was too long to be valid.
11    TooLong,
12    /// The calculated check-digit did not match the code's check-digit.
13    CheckDigitIncorrect,
14}
15
16/// Check that a UPC-A code is valid by confirming that it is made of
17/// exactly 12 digits and that the check-digit is correct.
18///
19/// # Examples
20/// ```
21/// use gtin_validate::gtin12;
22///
23/// assert_eq!(gtin12::check("897854613315"), true);  // Valid GTIN-12
24/// assert_eq!(gtin12::check("89785461331"), false);  // Too short
25/// assert_eq!(gtin12::check("897854613318"), false); // Bad check digit
26/// ```
27pub fn check(code: &str) -> bool {
28    if code.len() != 12 {
29        return false;
30    }
31    if !utils::is_ascii_numeric(code) {
32        return false;
33    }
34
35    // Calculate and compare check digit
36    let bytes = code.as_bytes();
37    let check = utils::compute_check_digit(bytes);
38    if check != bytes[11] - 48 {
39        return false;
40    }
41
42    true
43}
44
45/// Attempt to fix invalid UPC codes by stripping whitespace from the
46/// left and right sides and zero-padding the UPC if it is less than 12
47/// digits in length.
48///
49/// These corrections fix many common errors introduced by manual data
50/// entry and software that treats UPCs as integers rather than strings,
51/// thus truncating leading zeros.
52///
53/// # Examples
54/// ```
55/// use gtin_validate::gtin12;
56///
57/// // Add missing zero, fixing length:
58/// let result1 = gtin12::fix("87248795257");
59/// assert!(result1.is_ok());
60/// assert_eq!(result1.unwrap(), "087248795257");
61///
62/// // Remove extra whitespace:
63/// let result2 = gtin12::fix("087248795257 ");
64/// assert!(result2.is_ok());
65/// assert_eq!(result2.unwrap(), "087248795257");
66/// ```
67///
68/// It is also possible to detect errors:
69///
70/// ```
71/// use gtin_validate::gtin12;
72/// let result = gtin12::fix("123412341234123"); // UPC too long
73/// assert!(result.is_err());
74/// ```
75pub fn fix(code: &str) -> Result<String, FixError> {
76    let mut fixed = code.trim().to_string();
77
78    if !fixed.is_ascii() {
79        return Err(FixError::NonAsciiString);
80    }
81    if fixed.len() > 12 {
82        return Err(FixError::TooLong);
83    }
84    fixed = utils::zero_pad(fixed, 12);
85    if !check(&fixed) {
86        return Err(FixError::CheckDigitIncorrect);
87    }
88
89    Ok(fixed)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::check;
95    use super::fix;
96    use super::FixError;
97
98    #[test]
99    fn check_valid() {
100        assert_eq!(check(&"000000000000"), true);
101    }
102
103    #[test]
104    fn check_invalid_length() {
105        assert_eq!(check("000"), false);
106    }
107
108    #[test]
109    fn check_non_ascii() {
110        assert_eq!(check("❤"), false);
111    }
112
113    #[test]
114    fn check_non_numeric() {
115        assert_eq!(check("a"), false);
116        assert_eq!(check("abcdabcdabcd"), false); // length 12
117        assert_eq!(check("00000000000a"), false); // invalid check digit
118    }
119
120    #[test]
121    fn check_invalid_check_digit() {
122        assert_eq!(check("000000000001"), false);
123        assert_eq!(check("000000000002"), false);
124        assert_eq!(check("000000000003"), false);
125        assert_eq!(check("000000000004"), false);
126        assert_eq!(check("000000000005"), false);
127        assert_eq!(check("000000000006"), false);
128        assert_eq!(check("000000000007"), false);
129        assert_eq!(check("000000000008"), false);
130        assert_eq!(check("000000000009"), false);
131    }
132
133    #[test]
134    fn check_static_data() {
135        assert_eq!(check("000000000000"), true);
136        assert_eq!(check("123456789012"), true);
137        assert_eq!(check("123456789013"), false);
138        assert_eq!(check("999999999993"), true);
139        assert_eq!(check("999999999999"), false);
140    }
141
142    #[test]
143    fn fix_non_ascii() {
144        assert!(fix("❤").is_err());
145    }
146
147    #[test]
148    fn fix_too_long() {
149        assert_eq!(fix("0000000000000"), Err(FixError::TooLong));
150    }
151
152    #[test]
153    fn fix_incorrect_check_digit() {
154        assert_eq!(fix("123456789013"), Err(FixError::CheckDigitIncorrect));
155    }
156
157    #[test]
158    fn fix_needs_zero_padding() {
159        assert!(fix("0").is_ok());
160        assert_eq!(fix("0").unwrap(), "000000000000");
161    }
162
163    proptest! {
164        #[test]
165        fn doesnt_crash(ref s in ".*") {
166            check(s);
167        }
168    }
169}