brids/
cpf.rs

1// cpf.rs
2//
3// Copyright 2018 Ricardo Silva Veloso <ricvelozo@gmail.com>
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// https://www.apache.org/licenses/LICENSE-2.0> or the MIT License
7// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10//
11// SPDX-License-Identifier: (MIT OR Apache-2.0)
12
13use core::{
14    convert::TryFrom,
15    fmt::{self, Write},
16    str::FromStr,
17};
18
19#[cfg(all(feature = "std", feature = "rand"))]
20use rand::thread_rng;
21
22#[cfg(feature = "rand")]
23use rand::{
24    distributions::{Distribution, Standard},
25    Rng,
26};
27
28#[cfg(feature = "serde")]
29use serde::*;
30
31/// An error which can be returned when parsing an [`Cpf`] number.
32#[derive(Debug, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum ParseCpfError {
35    Empty,
36    InvalidCharacter(char, usize),
37    InvalidNumber,
38}
39
40impl fmt::Display for ParseCpfError {
41    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42        use ParseCpfError::*;
43        match self {
44            Empty => write!(f, "empty"),
45            InvalidCharacter(ch, offset) => {
46                write!(f, "invalid character `{ch}` at offset {offset}")
47            }
48            InvalidNumber => write!(f, "invalid CPF number"),
49        }
50    }
51}
52
53impl core::error::Error for ParseCpfError {}
54
55/// A valid CPF number. Parsing recognizes numbers with or without separators (dot, minus,
56/// and slash).
57#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub struct Cpf([u8; 11]);
59
60impl Cpf {
61    /// Parses a byte slice of numbers as an CPF, guessing the missing parts.
62    ///
63    /// # Examples
64    ///
65    /// Basic usage:
66    ///
67    /// ```rust
68    /// use brids::Cpf;
69    ///
70    /// match Cpf::from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]) {
71    ///     Ok(cpf) => println!("{cpf} is a valid number."),
72    ///     Err(err) => eprintln!("Error: {err}"),
73    /// }
74    /// ```
75    ///
76    /// Guess the check digits:
77    ///
78    /// ```rust
79    /// use brids::Cpf;
80    ///
81    /// match Cpf::from_slice(&[1, 2, 3, 4, 5, 6, 7, 8, 9]) {
82    ///     Ok(cpf) => println!("{cpf} is a valid number."),
83    ///     Err(err) => eprintln!("Error: {err}"),
84    /// }
85    /// ```
86    pub fn from_slice(slice: &[u8]) -> Result<Self, ParseCpfError> {
87        let mut numbers = [0; 11];
88        match slice.len() {
89            0 => return Err(ParseCpfError::Empty),
90            len @ (9 | 11) => numbers[..len].copy_from_slice(slice),
91            _ => return Err(ParseCpfError::InvalidNumber),
92        }
93
94        // 0..=9
95        if numbers.iter().any(|&x| x > 9) {
96            return Err(ParseCpfError::InvalidNumber);
97        }
98
99        // Checks for repeated numbers
100        let first_number = numbers[0];
101        if slice.len() == 11 && numbers.iter().all(|&x| x == first_number) {
102            return Err(ParseCpfError::InvalidNumber);
103        }
104
105        for i in 0..=1 {
106            let remainder = calc_remainder(numbers, i);
107            let check_digit = numbers[9 + i];
108
109            if slice.len() < 11 {
110                numbers[9 + i] = remainder; // check digit
111            } else if remainder != check_digit {
112                return Err(ParseCpfError::InvalidNumber);
113            }
114        }
115
116        Ok(Cpf(numbers))
117    }
118
119    /// Returns a byte slice of the numbers.
120    ///
121    /// # Examples
122    ///
123    /// ```rust
124    /// use brids::Cpf;
125    ///
126    /// let cpf = "123.456.789-09".parse::<Cpf>().expect("Invalid CPF");
127    /// let digits = cpf.as_bytes();
128    /// ```
129    #[inline]
130    pub fn as_bytes(&self) -> &[u8; 11] {
131        &self.0
132    }
133
134    /// Generates a random number, using [`rand::thread_rng`] (requires `std` and `rand` features).
135    /// To use a different generator, instantiate the generator directly.
136    ///
137    /// # Examples
138    ///
139    /// ```rust
140    /// use brids::Cpf;
141    ///
142    /// let cpf = Cpf::generate();
143    /// ```
144    #[cfg(all(feature = "std", feature = "rand"))]
145    #[inline]
146    pub fn generate() -> Self {
147        thread_rng().gen()
148    }
149}
150
151impl AsRef<[u8]> for Cpf {
152    #[inline]
153    fn as_ref(&self) -> &[u8] {
154        self.as_bytes()
155    }
156}
157
158impl From<Cpf> for [u8; 11] {
159    #[inline]
160    fn from(cpf: Cpf) -> [u8; 11] {
161        cpf.0
162    }
163}
164
165impl TryFrom<&[u8]> for Cpf {
166    type Error = ParseCpfError;
167
168    #[inline]
169    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
170        Self::from_slice(value)
171    }
172}
173
174impl TryFrom<&[u8; 11]> for Cpf {
175    type Error = ParseCpfError;
176
177    #[inline]
178    fn try_from(value: &[u8; 11]) -> Result<Self, Self::Error> {
179        Self::from_slice(value)
180    }
181}
182
183impl fmt::Debug for Cpf {
184    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185        write!(f, "Cpf(\"{self}\")")
186    }
187}
188
189impl fmt::Display for Cpf {
190    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
191        for (i, number) in self.0.iter().enumerate() {
192            match i {
193                3 | 6 => f.write_char('.')?,
194                9 => f.write_char('-')?,
195                _ => (),
196            }
197            number.fmt(f)?;
198        }
199        Ok(())
200    }
201}
202
203impl FromStr for Cpf {
204    type Err = ParseCpfError;
205
206    fn from_str(s: &str) -> Result<Self, Self::Err> {
207        let mut numbers = [0; 11];
208
209        if s.is_empty() {
210            return Err(ParseCpfError::Empty);
211        }
212
213        // Checks for invalid symbols and converts numbers to integers
214        let mut i = 0;
215        let mut has_dot = false;
216        for (offset, ch) in s.chars().enumerate() {
217            match (ch, offset) {
218                ('0'..='9', _) => {
219                    if i < 11 {
220                        // SAFETY: Digit already matched
221                        numbers[i] = unsafe { ch.to_digit(10).unwrap_unchecked() as u8 };
222                        i += 1;
223                    } else {
224                        return Err(ParseCpfError::InvalidNumber);
225                    }
226                }
227                ('.', 3 | 7) => has_dot = true,
228                ('-' | '/', 11) if has_dot => continue,
229                ('-' | '/', 9) if !has_dot => continue,
230                _ => return Err(ParseCpfError::InvalidCharacter(ch, offset)),
231            }
232        }
233
234        // Checks the length
235        if i != 11 {
236            return Err(ParseCpfError::InvalidNumber);
237        }
238
239        // Checks for repeated numbers
240        let first_number = numbers[0];
241        if numbers.iter().all(|&x| x == first_number) {
242            return Err(ParseCpfError::InvalidNumber);
243        }
244
245        for i in 0..=1 {
246            let remainder = calc_remainder(numbers, i);
247            let check_digit = numbers[9 + i];
248
249            if remainder != check_digit {
250                return Err(ParseCpfError::InvalidNumber);
251            }
252        }
253
254        Ok(Cpf(numbers))
255    }
256}
257
258#[cfg(feature = "rand")]
259impl Distribution<Cpf> for Standard {
260    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Cpf {
261        let mut numbers = [0; 11];
262        for number in &mut numbers[..9] {
263            *number = rng.gen_range(0..=9);
264        }
265
266        for i in 0..=1 {
267            numbers[9 + i] = calc_remainder(numbers, i); // check digit
268        }
269
270        Cpf(numbers)
271    }
272}
273
274#[cfg(feature = "serde")]
275impl Serialize for Cpf {
276    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
277        serializer.serialize_str(&self.to_string())
278    }
279}
280
281#[cfg(feature = "serde")]
282impl<'de> Deserialize<'de> for Cpf {
283    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
284        struct CpfStringVisitor;
285
286        impl<'vi> de::Visitor<'vi> for CpfStringVisitor {
287            type Value = Cpf;
288
289            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
290                write!(formatter, "a CPF string")
291            }
292
293            fn visit_str<E: de::Error>(self, value: &str) -> Result<Cpf, E> {
294                value.parse().map_err(E::custom)
295            }
296
297            fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Cpf, E> {
298                Cpf::try_from(value).map_err(E::custom)
299            }
300        }
301
302        deserializer.deserialize_str(CpfStringVisitor)
303    }
304}
305
306#[inline]
307fn calc_remainder(numbers: impl IntoIterator<Item = u8>, i: usize) -> u8 {
308    let remainder = numbers
309        .into_iter()
310        // Includes the first check digit in the second iteration
311        .take(9 + i)
312        // 10, 9, 8, ... 3, 2; and after: 11, 10, 9, 8, ... 3, 2
313        .zip((2..=10 + i).rev())
314        .map(|(x, y)| u32::from(x) * y as u32)
315        .sum::<u32>()
316        * 10
317        % 11;
318
319    match remainder {
320        10 | 11 => 0,
321        _ => remainder as u8,
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    #[cfg(not(feature = "std"))]
328    use alloc::format;
329
330    use super::*;
331
332    #[test]
333    fn from_slice() {
334        let a = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]);
335        let b: [u8; 11] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9];
336        let c: [u8; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
337
338        assert_eq!(a, Cpf::from_slice(&b).unwrap());
339        assert_eq!(a, Cpf::from_slice(&c).unwrap());
340    }
341
342    #[test]
343    fn as_bytes() {
344        let a: [u8; 11] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9];
345        let b = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]);
346
347        assert_eq!(&a, b.as_bytes());
348    }
349
350    #[cfg(feature = "rand")]
351    #[test]
352    fn generate() {
353        let a = Cpf::generate();
354        let b = a.to_string().parse::<Cpf>().unwrap();
355
356        assert_eq!(a, b);
357    }
358
359    #[test]
360    fn as_ref() {
361        fn test_trait<T: AsRef<[u8]>>(b: T) {
362            let a: [u8; 11] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9];
363            assert_eq!(&a, b.as_ref());
364        }
365
366        let b = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]);
367
368        test_trait(b);
369    }
370
371    #[test]
372    fn from() {
373        let a: [u8; 11] = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]).into();
374        let b: [u8; 11] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9];
375
376        assert_eq!(a, b);
377    }
378
379    #[test]
380    fn try_from() {
381        let a: [u8; 11] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9];
382        let b = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]);
383
384        assert_eq!(Cpf::try_from(&a).unwrap(), b);
385    }
386
387    #[test]
388    fn cmp() {
389        let a = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]);
390        let b = Cpf([1, 2, 3, 4, 5, 6, 7, 9, 0, 3, 4]);
391
392        assert!(a < b);
393    }
394
395    #[test]
396    fn debug() {
397        let a = r#"Cpf("123.456.789-09")"#;
398        let b = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]);
399
400        assert_eq!(a, format!("{b:?}"));
401    }
402
403    #[test]
404    fn display() {
405        let a = "123.456.789-09";
406        let b = Cpf([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 9]);
407
408        assert_eq!(a, format!("{b}"));
409    }
410
411    #[test]
412    fn from_str() {
413        let a = "123.456.789-09".parse::<Cpf>().unwrap();
414        let b = "123456789/09".parse::<Cpf>().unwrap();
415        let c = "12345678909".parse::<Cpf>().unwrap();
416
417        assert_eq!(a, b);
418        assert_eq!(a, c);
419        assert_eq!("".parse::<Cpf>(), Err(ParseCpfError::Empty));
420        assert_eq!(
421            "123-456-789-09".parse::<Cpf>(),
422            Err(ParseCpfError::InvalidCharacter('-', 3))
423        );
424        assert_eq!(
425            "123.456.789-10".parse::<Cpf>(),
426            Err(ParseCpfError::InvalidNumber)
427        );
428        assert_eq!(
429            "123.456.789-009".parse::<Cpf>(),
430            Err(ParseCpfError::InvalidNumber)
431        );
432    }
433
434    #[cfg(feature = "serde")]
435    #[test]
436    fn serialize() {
437        let cpf_str = "123.456.789-09";
438        let cpf = Cpf::from_str(cpf_str).unwrap();
439        serde_test::assert_tokens(&cpf, &[serde_test::Token::Str(cpf_str)]);
440    }
441}