codes_check_digits/
lib.rs

1/*!
2Provides tools for building different check-digit algorithms.
3
4This package contains implementations of various check digit specifications,
5including [ISO/IEC 7064:2003](https://www.iso.org/standard/31531.html)
6Information technology — Security techniques — Check character systems.
7
8# Example
9
10```rust,ignore
11use codes_check_digits::{luhn, Calculator};
12
13let calculator = luhn::get_algorithm_instance();
14assert!(calculator.is_valid("US0378331005"));
15assert!(calculator.validate("US0378331005").is_ok());
16assert_eq!(calculator.calculate("US037833100"), Ok(5));
17```
18
19# Features
20
21* `gs1` - Adds the `gs1` module containing algorithms for various codes such as
22  EAN, GTIN, GLN, and UPC.
23* `iso_7064` - Adds the `iso_7064` module containing implementations of the
24  variants defined in ISO/IEC 7064:2003.
25* `luhn` - Adds the `luhn` module containing an implementation of the Luhn Algorithm.
26* `sedol` - Adds the `sedol` module containing an implementation of the algorithm
27  used in SEDOL numbers.
28
29*/
30
31#![warn(
32    unknown_lints,
33    // ---------- Stylistic
34    absolute_paths_not_starting_with_crate,
35    elided_lifetimes_in_paths,
36    explicit_outlives_requirements,
37    macro_use_extern_crate,
38    nonstandard_style, /* group */
39    noop_method_call,
40    rust_2018_idioms,
41    single_use_lifetimes,
42    trivial_casts,
43    trivial_numeric_casts,
44    // ---------- Future
45    future_incompatible, /* group */
46    rust_2021_compatibility, /* group */
47    // ---------- Public
48    missing_debug_implementations,
49    // missing_docs,
50    unreachable_pub,
51    // ---------- Unsafe
52    unsafe_code,
53    unsafe_op_in_unsafe_fn,
54    // ---------- Unused
55    unused, /* group */
56)]
57#![deny(
58    // ---------- Public
59    exported_private_dependencies,
60    private_in_public,
61    // ---------- Deprecated
62    anonymous_parameters,
63    bare_trait_objects,
64    ellipsis_inclusive_range_patterns,
65    // ---------- Unsafe
66    deref_nullptr,
67    drop_bounds,
68    dyn_drop,
69)]
70
71use codes_common::Code;
72use error::CheckDigitError;
73use std::{borrow::Cow, fmt::Display};
74use tracing::trace;
75
76use crate::error::invalid_check_digit;
77
78// ------------------------------------------------------------------------------------------------
79// Public Types
80// ------------------------------------------------------------------------------------------------
81
82pub trait CodeWithCheckDigits: Code<String> + AsRef<str> {
83    type CheckDigit: Display + PartialEq;
84    type CheckDigitCalculator: Calculator<Self::CheckDigit> + Copy;
85    const CHECK_DIGIT_ALGORITHM: Self::CheckDigitCalculator;
86
87    fn data_no_check_digit(&self) -> Cow<'_, str> {
88        let s = self.as_ref();
89        s[..(s.len() - Self::CHECK_DIGIT_ALGORITHM.number_of_check_digit_chars())].into()
90    }
91
92    fn check_digit_as_str(&self) -> Cow<'_, str> {
93        let s = self.as_ref();
94        s[(s.len() - Self::CHECK_DIGIT_ALGORITHM.number_of_check_digit_chars())..].into()
95    }
96}
97
98///
99/// Trait for types that implement check digit algorithms.
100///
101pub trait Calculator<T>
102where
103    T: Display + PartialEq,
104{
105    ///
106    /// Return the number of characters used as the check digit.
107    /// Currently it is assumed that these are the *n* right-most
108    /// characters in the input string.
109    ///
110    fn number_of_check_digit_chars(&self) -> usize {
111        1
112    }
113
114    ///
115    /// Return the name of this algorithm.
116    ///
117    fn name(&self) -> &'static str;
118
119    ///
120    /// Calculate a check digit for the provided string.
121    ///
122    fn calculate(&self, s: &str) -> Result<T, CheckDigitError>;
123
124    ///
125    /// Create a new string with the original data plus check digit.
126    ///
127    fn create(&self, s: &str) -> Result<String, CheckDigitError> {
128        Ok(format!(
129            "{}{:0>width$}",
130            s,
131            self.calculate(s)?,
132            width = self.number_of_check_digit_chars()
133        ))
134    }
135
136    ///
137    /// Validate that the string is valid and that it contains a valid
138    /// check digit.
139    ///
140    fn validate<S>(&self, s: S) -> Result<(), CheckDigitError>
141    where
142        Self: Sized,
143        S: AsRef<str>,
144    {
145        let s = s.as_ref();
146        trace!(
147            algorithm_name = self.name(),
148            num_check_digits = self.number_of_check_digit_chars(),
149            "Validating check digits for input {:?}",
150            s
151        );
152        let check_digit_index = s.len() - self.number_of_check_digit_chars();
153        let check = self.calculate(&s[0..check_digit_index])?;
154        if s[check_digit_index..] == check.to_string() {
155            Ok(())
156        } else {
157            Err(invalid_check_digit(&s[check_digit_index..], check))
158        }
159    }
160
161    ///
162    /// Returns `true` if the provided string includes a valid check digit,
163    /// else `false`. The default implementation relies on the `validate`
164    /// method.
165    ///
166    fn is_valid<S>(&self, s: S) -> bool
167    where
168        Self: Sized,
169        S: AsRef<str>,
170    {
171        self.validate(s).is_ok()
172    }
173}
174
175// ------------------------------------------------------------------------------------------------
176// Public Macros
177// ------------------------------------------------------------------------------------------------
178
179#[macro_export]
180macro_rules! check_digits_impl {
181    ($type_name:ty, $error_type:ty, $algorithm_type:ty, $check_digit_type:ty, $algorithm_init:expr) => {
182        impl CodeWithCheckDigits for $type_name {
183            type CheckDigit = $check_digit_type;
184            type CheckDigitCalculator = $algorithm_type;
185            const CHECK_DIGIT_ALGORITHM: Self::CheckDigitCalculator = $algorithm_init;
186        }
187
188        impl ::std::str::FromStr for $type_name {
189            type Err = GlobalLocationNumberError;
190
191            fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
192                use codes_check_digits::Calculator;
193                Self::CHECK_DIGIT_ALGORITHM.validate(s)?;
194                Ok(Self(s.to_string()))
195            }
196        }
197    };
198}
199
200// ------------------------------------------------------------------------------------------------
201// Public Functions
202// ------------------------------------------------------------------------------------------------
203
204// ------------------------------------------------------------------------------------------------
205// Implementations
206// ------------------------------------------------------------------------------------------------
207
208// ------------------------------------------------------------------------------------------------
209// Modules
210// ------------------------------------------------------------------------------------------------
211
212#[doc(hidden)]
213mod common;
214
215pub mod error;
216
217#[cfg(feature = "gs1")]
218pub mod gs1;
219
220#[cfg(feature = "iso_7064")]
221pub mod iso_7064;
222
223#[cfg(feature = "luhn")]
224pub mod luhn;
225
226#[cfg(feature = "sedol")]
227pub mod sedol;