Skip to main content

ftracker_identifiers/cnpj/
error.rs

1use core::fmt;
2
3/// The set of characters permitted at a given position of a CNPJ.
4///
5/// Reported by [`CnpjError::InvalidCharacter`] to describe what was expected where an invalid
6/// character was found.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum CharacterClass {
9    /// An ASCII digit, `'0'...='9'`.
10    Digit,
11    /// An ASCII digit or an uppercase ASCII letter, `'0'...='9' | 'A'...='Z'`.
12    AlphanumericUppercase,
13}
14
15impl fmt::Display for CharacterClass {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            CharacterClass::Digit => write!(f, "a digit (0-9)"),
19            CharacterClass::AlphanumericUppercase => {
20                write!(f, "a digit (0-9) or an uppercase letter (A-Z)")
21            }
22        }
23    }
24}
25
26/// The error returned when a [`Cnpj`](crate::Cnpj) fails to parse or validate.
27///
28/// Each variant corresponds to one validation rule, in the order the rules are applied. See the
29/// [module level documentation](crate::cnpj) for the full rule list.
30#[non_exhaustive]
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum CnpjError {
33    /// The input was an empty string.
34    Empty,
35
36    /// After stripping punctuation (`.`, `/`, `-`, whitespace), the input did not contain exactly
37    /// 14 meaningful characters.
38    InvalidLength {
39        /// The number of meaningful (non-punctuation) characters found.
40        found: usize,
41    },
42
43    /// A character outside the allowed set was found at a given position.
44    InvalidCharacter {
45        /// The offending character, as originally provided (before case folding).
46        character: char,
47        /// 1-indexed position within the 14 meaningful characters.
48        position: u8,
49        /// The character class that was expected at this position.
50        expected: CharacterClass,
51    },
52
53    /// The Módulo 11 checksum did not match one of the two verification digits.
54    InvalidCheckDigits {
55        /// 1-indexed position of the mismatching verification digit (13 or 14).
56        position: u8,
57        /// The verification digit computed from the Módulo 11 algorithm.
58        expected: u8,
59        /// The verification digit actually present in the input.
60        found: u8,
61    },
62
63    /// All 14 characters were identical (e.g. `"00000000000000"`).
64    ///
65    /// Such inputs are structurally well-formed and can even satisfy the Módulo 11 checksum for
66    /// certain repeated digits, but the Receita Federal never issues them;
67    /// they are reliably placeholder or data-entry artifacts.
68    RepeatedDigits,
69}
70
71impl fmt::Display for CnpjError {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            CnpjError::Empty => f.write_str("CNPJ input is empty"),
75            CnpjError::InvalidLength { found } => write!(
76                f,
77                "CNPJ must contain exactly 14 characters once formatting is removed, found {found}"
78            ),
79            CnpjError::InvalidCharacter {
80                character,
81                position,
82                expected,
83            } => write!(
84                f,
85                "invalid character '{character}' at position {position} of 14: expected {expected}"
86            ),
87            CnpjError::InvalidCheckDigits {
88                position,
89                expected,
90                found,
91            } => write!(
92                f,
93                "invalid check digit at position {position} of 14: expected {expected}, found {found}"
94            ),
95            CnpjError::RepeatedDigits => {
96                f.write_str("CNPJ cannot consist of a single character repeated 14 times")
97            }
98        }
99    }
100}
101
102impl core::error::Error for CnpjError {}