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;