Skip to main content

cnpj_util/
lib.rs

1//! # CNPJ util
2//!
3//! `cnpj util` is a library focused on solving a common problems
4//! that we face daily in the development of applications using
5//! CNPJ (Brazil companies ID number).
6
7use std::cmp;
8use std::cmp::Ordering;
9
10const CNPJ_LENGTH: usize = 14;
11
12fn get_separator(x: usize) -> &'static str {
13    match x {
14        2 | 5 => ".",
15        8 => "/",
16        12 => "-",
17        _ => "",
18    }
19}
20
21/// Format string with CNPJ mask.
22///
23/// # Examples
24///
25/// ```rust
26/// let cnpj_without_mask = "46843485000186";
27/// let cnpj_with_mask = cnpj_util::format(cnpj_without_mask);
28///
29/// assert_eq!("46.843.485/0001-86", cnpj_with_mask);
30/// ```
31///
32/// ```rust
33/// let cnpj_without_mask = "468434850001860000000000";
34/// let cnpj_with_mask = cnpj_util::format(cnpj_without_mask);
35///
36/// assert_eq!("46.843.485/0001-86", cnpj_with_mask);
37/// ```
38///
39/// ```rust
40/// let cnpj_without_mask = "46.?ABC843.485/0001-86abc";
41/// let cnpj_with_mask = cnpj_util::format(cnpj_without_mask);
42///
43/// assert_eq!("46.843.485/0001-86", cnpj_with_mask);
44/// ```
45pub fn format(cnpj: &str) -> String {
46    let cnpj = cnpj.matches(char::is_numeric).collect::<Vec<_>>();
47
48    let mut cnpj_with_mask: String = String::from("");
49
50    for (i, n) in cnpj
51        .iter()
52        .enumerate()
53        .take(cmp::min(cnpj.len(), CNPJ_LENGTH))
54    {
55        cnpj_with_mask.push_str(get_separator(i));
56        cnpj_with_mask.push_str(n);
57    }
58
59    cnpj_with_mask
60}
61
62pub fn reserved_numbers() -> Vec<String> {
63    vec![
64        String::from("00000000000000"),
65        String::from("11111111111111"),
66        String::from("22222222222222"),
67        String::from("33333333333333"),
68        String::from("44444444444444"),
69        String::from("55555555555555"),
70        String::from("66666666666666"),
71        String::from("77777777777777"),
72        String::from("88888888888888"),
73        String::from("99999999999999"),
74    ]
75}
76
77fn check_sum(cnpj: &[&str], factors: Vec<u32>) -> u32 {
78    let mut sum: u32 = 0;
79    for x in 0..factors.len() {
80        sum += cnpj[x].parse::<u32>().unwrap() * factors[x];
81    }
82
83    let mod_11 = sum % 11;
84
85    match mod_11.cmp(&2) {
86        Ordering::Less => 0,
87        _ => 11 - mod_11,
88    }
89}
90
91fn validate(cnpj: String) -> bool {
92    let cnpj = cnpj.matches(char::is_numeric).collect::<Vec<_>>();
93
94    let factors = vec![5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
95    let digito_1 = check_sum(&cnpj, factors);
96
97    let factors = vec![6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
98    let digito_2 = check_sum(&cnpj, factors);
99
100    digito_1 == cnpj[CNPJ_LENGTH - 2].parse::<u32>().unwrap()
101        && digito_2 == cnpj[CNPJ_LENGTH - 1].parse::<u32>().unwrap()
102}
103
104/// Check if CNPJ is valid.
105///
106/// # Examples
107///
108/// ```rust
109/// use cnpj_util as cnpj;
110/// assert_eq!(false, cnpj::is_valid("12312312312"));
111/// assert_eq!(false, cnpj::is_valid("6ad0.t391.9asd47/0ad001-00"));
112/// assert_eq!(true, cnpj::is_valid("13723705000189"));
113/// assert_eq!(true, cnpj::is_valid("60.391.947/0001-00"));
114/// ```
115pub fn is_valid(cnpj: &str) -> bool {
116    if cnpj.matches(char::is_lowercase).count() > 0 || cnpj.matches(char::is_uppercase).count() > 0
117    {
118        return false;
119    }
120
121    let cnpj = cnpj.matches(char::is_numeric).collect::<Vec<_>>().concat();
122
123    cnpj.len() == CNPJ_LENGTH
124        && !reserved_numbers().contains(&cnpj)
125        && !cnpj.is_empty()
126        && validate(cnpj)
127}
128
129#[cfg(test)]
130mod test_is_valid {
131    use super::*;
132
133    #[test]
134    fn should_return_false_when_it_is_on_reserved_numbers() {
135        for reserved_number in reserved_numbers() {
136            assert!(!is_valid(&reserved_number));
137        }
138    }
139
140    #[test]
141    fn should_return_false_when_is_a_empty_string() {
142        assert!(!is_valid(""));
143    }
144
145    #[test]
146    fn should_return_false_when_dont_match_with_cnpj_length() {
147        assert!(!is_valid("12312312312"));
148    }
149
150    #[test]
151    fn should_return_false_when_contains_only_letters_or_special_characters() {
152        assert!(!is_valid("ababcabcabcdab"));
153    }
154
155    #[test]
156    fn should_return_false_when_is_a_cnpj_invalid_test_numbers_with_letters() {
157        assert!(!is_valid("6ad0.t391.9asd47/0ad001-00"));
158    }
159
160    #[test]
161    fn should_return_false_when_is_a_cnpj_invalid() {
162        assert!(!is_valid("11257245286531"));
163    }
164
165    #[test]
166    fn should_return_true_when_is_a_valid_cnpj_without_mask() {
167        assert!(is_valid("13723705000189"));
168    }
169
170    #[test]
171    fn should_return_true_when_is_a_cnpj_valid_with_mask() {
172        assert!(is_valid("60.391.947/0001-00"));
173    }
174}
175
176#[cfg(test)]
177mod test_format {
178    use super::*;
179
180    #[test]
181    fn should_format_cnpj_with_mask() {
182        assert_eq!(format(""), "");
183        assert_eq!(format("4"), "4");
184        assert_eq!(format("46"), "46");
185        assert_eq!(format("468"), "46.8");
186        assert_eq!(format("4684"), "46.84");
187        assert_eq!(format("46843"), "46.843");
188        assert_eq!(format("468434"), "46.843.4");
189        assert_eq!(format("4684348"), "46.843.48");
190        assert_eq!(format("46843485"), "46.843.485");
191        assert_eq!(format("468434850"), "46.843.485/0");
192        assert_eq!(format("4684348500"), "46.843.485/00");
193        assert_eq!(format("46843485000"), "46.843.485/000");
194        assert_eq!(format("468434850001"), "46.843.485/0001");
195        assert_eq!(format("4684348500018"), "46.843.485/0001-8");
196        assert_eq!(format("46843485000186"), "46.843.485/0001-86");
197    }
198
199    #[test]
200    fn should_not_add_digits_after_the_cnpj_length() {
201        assert_eq!(format("468434850001860000000000"), "46.843.485/0001-86");
202    }
203
204    #[test]
205    fn should_remove_all_non_numeric_characters() {
206        assert_eq!(format("46.?ABC843.485/0001-86abc"), "46.843.485/0001-86");
207    }
208}