1use alloc::string::String;
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::rand::{RandomSource, SeededRng, below_u8, simple_seed};
11use crate::util::{self, impl_document_traits};
12
13const CNPJ_LEN: usize = 14;
14const WEIGHTS_D1: [u32; 12] = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
15const WEIGHTS_D2: [u32; 13] = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
16const FORMATTED_CHAR_POS: [usize; 14] = [0, 1, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 16, 17];
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[repr(u8)]
21pub enum CnpjKind {
22 Numeric = 0,
23 Alphanumeric = 1,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[repr(u8)]
29pub enum EstablishmentType {
30 Matriz = 0,
31 Filial = 1,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum CnpjError {
36 InvalidLength,
37 InvalidCharacter,
38 InvalidFormat,
39 AllCharsEqual,
40 InvalidCheckDigits,
41}
42
43impl fmt::Display for CnpjError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.write_str(match self {
46 Self::InvalidLength => "CNPJ must contain exactly 14 characters",
47 Self::InvalidCharacter => "CNPJ contains invalid characters",
48 Self::InvalidFormat => "CNPJ format must be XX.XXX.XXX/XXXX-DD or 14 characters",
49 Self::AllCharsEqual => "CNPJ with all equal characters is invalid",
50 Self::InvalidCheckDigits => "CNPJ check digits are invalid",
51 })
52 }
53}
54
55impl core::error::Error for CnpjError {}
56
57#[derive(Clone, Copy, PartialEq, Eq, Hash)]
73pub struct Cnpj {
74 bytes: [u8; CNPJ_LEN],
75}
76
77impl Cnpj {
78 pub fn as_str(&self) -> &str {
80 unsafe { core::str::from_utf8_unchecked(&self.bytes) }
82 }
83
84 pub fn kind(&self) -> CnpjKind {
86 if self.bytes[..12].iter().all(u8::is_ascii_digit) {
87 CnpjKind::Numeric
88 } else {
89 CnpjKind::Alphanumeric
90 }
91 }
92
93 pub fn raiz(&self) -> &str {
95 unsafe { core::str::from_utf8_unchecked(&self.bytes[..8]) }
96 }
97
98 pub fn ordem(&self) -> &str {
100 unsafe { core::str::from_utf8_unchecked(&self.bytes[8..12]) }
101 }
102
103 pub fn establishment_type(&self) -> EstablishmentType {
105 if &self.bytes[8..12] == b"0001" {
106 EstablishmentType::Matriz
107 } else {
108 EstablishmentType::Filial
109 }
110 }
111
112 pub fn check_digits(&self) -> (u8, u8) {
114 (self.bytes[12] - b'0', self.bytes[13] - b'0')
115 }
116
117 pub fn masked(&self) -> String {
119 let s = self.as_str();
120 alloc::format!("{}.{}.{}/****-**", &s[0..2], &s[2..5], &s[5..8])
121 }
122}
123
124impl fmt::Display for Cnpj {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 let s = self.as_str();
127 write!(
128 f,
129 "{}.{}.{}/{}-{}",
130 &s[0..2],
131 &s[2..5],
132 &s[5..8],
133 &s[8..12],
134 &s[12..14]
135 )
136 }
137}
138
139impl_document_traits!(Cnpj, CnpjError);
140
141pub fn normalize(cnpj: &str) -> String {
144 cnpj.chars()
145 .filter(char::is_ascii_alphanumeric)
146 .map(|c| c.to_ascii_uppercase())
147 .collect()
148}
149
150pub fn is_valid_lenient(cnpj: &str) -> bool {
152 let raw = normalize(cnpj);
153 if raw.len() != CNPJ_LEN {
154 return false;
155 }
156 let bytes = raw.as_bytes();
157 if !bytes[..12]
159 .iter()
160 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
161 {
162 return false;
163 }
164 if !bytes[12..14].iter().all(u8::is_ascii_digit) {
165 return false;
166 }
167 validate(bytes)
168}
169
170pub fn remove_symbols(cnpj: &str) -> String {
172 normalize(cnpj)
173}
174
175pub fn is_valid(cnpj: &str) -> bool {
180 is_valid_lenient(cnpj)
181}
182
183pub fn is_valid_strict(cnpj: &str) -> Result<(), CnpjError> {
185 parse_strict(cnpj).map(|_| ())
186}
187
188pub fn format_cnpj(cnpj: &str) -> Option<String> {
190 let raw = remove_symbols(cnpj);
191 if raw.len() != CNPJ_LEN {
192 return None;
193 }
194 let bytes = raw.as_bytes();
195 if !bytes[..12]
196 .iter()
197 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
198 {
199 return None;
200 }
201 if !bytes[12..14].iter().all(u8::is_ascii_digit) {
202 return None;
203 }
204 Some(alloc::format!(
205 "{}.{}.{}/{}-{}",
206 &raw[0..2],
207 &raw[2..5],
208 &raw[5..8],
209 &raw[8..12],
210 &raw[12..14]
211 ))
212}
213
214pub fn generate(kind: CnpjKind) -> String {
218 generate_cnpj(kind).as_str().into()
219}
220
221pub fn generate_cnpj(kind: CnpjKind) -> Cnpj {
225 let mut rng = SeededRng::new(simple_seed());
226 generate_cnpj_with_rng(&mut rng, kind)
227}
228
229pub fn generate_cnpj_with_rng<R: RandomSource + ?Sized>(rng: &mut R, kind: CnpjKind) -> Cnpj {
233 generate_with_rng(rng, kind)
234}
235
236pub fn generate_matriz(kind: CnpjKind) -> Cnpj {
240 let mut rng = SeededRng::new(simple_seed());
241 generate_matriz_with_rng(&mut rng, kind)
242}
243
244pub fn generate_with_seed(seed: u64, kind: CnpjKind) -> Cnpj {
248 let mut rng = SeededRng::new(seed);
249 generate_with_rng(&mut rng, kind)
250}
251
252pub fn generate_matriz_with_seed(seed: u64, kind: CnpjKind) -> Cnpj {
256 let mut rng = SeededRng::new(seed);
257 generate_matriz_with_rng(&mut rng, kind)
258}
259
260pub fn generate_matriz_with_rng<R: RandomSource + ?Sized>(rng: &mut R, kind: CnpjKind) -> Cnpj {
264 generate_with_ordem(rng, kind, *b"0001")
265}
266
267pub fn compute_check_digits(base: &str) -> Option<(u8, u8)> {
270 let raw = remove_symbols(base);
271 if raw.len() != 12 {
272 return None;
273 }
274 let bytes = raw.as_bytes();
275 if !bytes
276 .iter()
277 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
278 {
279 return None;
280 }
281 if all_equal(bytes) {
282 return None;
283 }
284
285 let values: Vec<u32> = bytes.iter().map(|&b| char_value(b)).collect();
286 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
287 let mut full = Vec::with_capacity(13);
288 full.extend_from_slice(&values);
289 full.push(u32::from(d1));
290 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
291
292 Some((d1, d2))
293}
294
295fn char_value(b: u8) -> u32 {
300 u32::from(b) - 48
301}
302
303fn all_equal(bytes: &[u8]) -> bool {
304 util::all_equal(bytes)
305}
306
307fn validate(bytes: &[u8]) -> bool {
308 if all_equal(&bytes[..12]) {
309 return false;
310 }
311 let values: Vec<u32> = bytes[..12].iter().map(|&b| char_value(b)).collect();
312 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
313 let mut full = Vec::with_capacity(13);
314 full.extend_from_slice(&values);
315 full.push(u32::from(d1));
316 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
317 bytes[12] - b'0' == d1 && bytes[13] - b'0' == d2
318}
319
320fn calc_check_digit(values: &[u32], weights: &[u32]) -> u8 {
321 let sum: u32 = values.iter().zip(weights).map(|(&v, &w)| v * w).sum();
322 let rem = sum % 11;
323 #[allow(clippy::cast_possible_truncation)]
324 if rem < 2 { 0 } else { (11 - rem) as u8 }
326}
327
328fn append_check_digits(bytes: &mut [u8; CNPJ_LEN]) {
329 let values: Vec<u32> = bytes[..12].iter().map(|&b| char_value(b)).collect();
330 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
331 let mut full = Vec::with_capacity(13);
332 full.extend_from_slice(&values);
333 full.push(u32::from(d1));
334 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
335 bytes[12] = b'0' + d1;
336 bytes[13] = b'0' + d2;
337}
338
339fn parse_strict(s: &str) -> Result<Cnpj, CnpjError> {
340 let raw = s.as_bytes();
341
342 let chars: Vec<u8> = match raw.len() {
343 14 => {
344 if !raw[..12]
346 .iter()
347 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
348 {
349 return Err(CnpjError::InvalidCharacter);
350 }
351 if !raw[12..14].iter().all(u8::is_ascii_digit) {
352 return Err(CnpjError::InvalidCharacter);
353 }
354 raw.to_vec()
355 }
356 18 => {
357 if raw[2] != b'.' || raw[6] != b'.' || raw[10] != b'/' || raw[15] != b'-' {
359 return Err(CnpjError::InvalidFormat);
360 }
361 let extracted: Vec<u8> = FORMATTED_CHAR_POS.iter().map(|&i| raw[i]).collect();
362 if !extracted[..12]
363 .iter()
364 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
365 {
366 return Err(CnpjError::InvalidCharacter);
367 }
368 if !extracted[12..14].iter().all(u8::is_ascii_digit) {
369 return Err(CnpjError::InvalidCharacter);
370 }
371 extracted
372 }
373 _ => return Err(CnpjError::InvalidLength),
374 };
375
376 if all_equal(&chars[..12]) {
377 return Err(CnpjError::AllCharsEqual);
378 }
379
380 let values: Vec<u32> = chars[..12].iter().map(|&b| char_value(b)).collect();
381 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
382 let mut full = Vec::with_capacity(13);
383 full.extend_from_slice(&values);
384 full.push(u32::from(d1));
385 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
386
387 if chars[12] - b'0' != d1 || chars[13] - b'0' != d2 {
388 return Err(CnpjError::InvalidCheckDigits);
389 }
390
391 let mut bytes = [0u8; CNPJ_LEN];
392 bytes.copy_from_slice(&chars);
393 Ok(Cnpj { bytes })
394}
395
396const ALPHANUMERIC_CHARS: &[u8; 36] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
397
398pub fn generate_with_rng<R: RandomSource + ?Sized>(rng: &mut R, kind: CnpjKind) -> Cnpj {
402 let mut bytes = [0u8; CNPJ_LEN];
403
404 loop {
405 for b in &mut bytes[..12] {
406 *b = match kind {
407 CnpjKind::Numeric => b'0' + below_u8(rng, 10),
408 CnpjKind::Alphanumeric => ALPHANUMERIC_CHARS[usize::from(below_u8(rng, 36))],
409 };
410 }
411 if generated_base_is_valid(&bytes[..12], kind) {
412 break;
413 }
414 }
415
416 append_check_digits(&mut bytes);
417 Cnpj { bytes }
418}
419
420fn generate_with_ordem<R: RandomSource + ?Sized>(
421 rng: &mut R,
422 kind: CnpjKind,
423 ordem: [u8; 4],
424) -> Cnpj {
425 let mut bytes = [0u8; CNPJ_LEN];
426 bytes[8..12].copy_from_slice(&ordem);
427
428 loop {
429 for b in &mut bytes[..8] {
430 *b = match kind {
431 CnpjKind::Numeric => b'0' + below_u8(rng, 10),
432 CnpjKind::Alphanumeric => ALPHANUMERIC_CHARS[usize::from(below_u8(rng, 36))],
433 };
434 }
435 if generated_base_is_valid(&bytes[..12], kind) {
436 break;
437 }
438 }
439
440 append_check_digits(&mut bytes);
441 Cnpj { bytes }
442}
443
444fn generated_base_is_valid(base: &[u8], kind: CnpjKind) -> bool {
445 !all_equal(base) && (kind == CnpjKind::Numeric || base.iter().any(u8::is_ascii_uppercase))
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use alloc::string::ToString;
452
453 fn cnpj_numeric() -> Cnpj {
455 "11222333000181".parse().unwrap()
456 }
457
458 fn cnpj_numeric_b() -> Cnpj {
460 "11444777000161".parse().unwrap()
461 }
462
463 fn make_cnpj(base: &[u8; 12]) -> Cnpj {
465 let mut bytes = [0u8; CNPJ_LEN];
466 bytes[..12].copy_from_slice(base);
467 append_check_digits(&mut bytes);
468 Cnpj { bytes }
469 }
470
471 fn cnpj_alpha() -> Cnpj {
472 make_cnpj(b"12ABC34500DE")
474 }
475
476 #[test]
477 fn parse_known_numeric() {
478 let cnpj = cnpj_numeric();
479 assert_eq!(cnpj.as_str(), "11222333000181");
480 assert_eq!(cnpj.kind(), CnpjKind::Numeric);
481 }
482
483 #[test]
484 fn parse_known_numeric_b() {
485 let cnpj = cnpj_numeric_b();
486 assert_eq!(cnpj.as_str(), "11444777000161");
487 assert_eq!(cnpj.kind(), CnpjKind::Numeric);
488 }
489
490 #[test]
491 fn validate_known_numeric() {
492 assert!(is_valid("11.222.333/0001-81"));
493 assert!(is_valid("11222333000181"));
494 assert!(is_valid("11.444.777/0001-61"));
495 assert!(is_valid("11444777000161"));
496 }
497
498 #[test]
499 fn parse_alphanumeric() {
500 let cnpj = cnpj_alpha();
501 assert_eq!(cnpj.kind(), CnpjKind::Alphanumeric);
502 assert!(is_valid(cnpj.as_str()));
503 }
504
505 #[test]
506 fn validate_alphanumeric_formatted() {
507 let cnpj = cnpj_alpha();
508 let formatted = cnpj.to_string();
509 assert!(is_valid_strict(&formatted).is_ok());
510 }
511
512 #[test]
513 fn is_valid_lenient_strips_garbage() {
514 let cnpj = cnpj_numeric();
515 let s = cnpj.as_str();
516 let garbage = alloc::format!("{}${}#{}!{}", &s[0..2], &s[2..5], &s[5..8], &s[8..14]);
517 assert!(is_valid(&garbage));
518 }
519
520 #[test]
521 fn is_valid_lenient_lowercase_to_uppercase() {
522 let cnpj = cnpj_alpha();
523 let lower = cnpj.as_str().to_lowercase();
524 assert!(is_valid(&lower));
525 }
526
527 #[test]
528 fn is_valid_rejects_bad_check_digits() {
529 let mut bytes = *b"11222333000182"; assert!(!is_valid(core::str::from_utf8(&bytes).unwrap()));
531 bytes = *b"11222333000191"; assert!(!is_valid(core::str::from_utf8(&bytes).unwrap()));
533 }
534
535 #[test]
536 fn is_valid_rejects_all_equal() {
537 assert!(!is_valid("11111111111111"));
538 assert!(!is_valid("00000000000000"));
539 assert!(!is_valid("AAAAAAAAAAAAAA"));
540 }
541
542 #[test]
543 fn all_equal_rule_applies_to_the_registration_base() {
544 let repeated_base = make_cnpj(b"111111111111");
545 assert!(!is_valid(repeated_base.as_str()));
546 assert_eq!(
547 is_valid_strict(repeated_base.as_str()),
548 Err(CnpjError::AllCharsEqual)
549 );
550 assert_eq!(compute_check_digits("111111111111"), None);
551 }
552
553 #[test]
554 fn is_valid_rejects_wrong_length() {
555 assert!(!is_valid(""));
556 assert!(!is_valid("1234567890123"));
557 assert!(!is_valid("123456789012345"));
558 }
559
560 #[test]
561 fn is_valid_rejects_invalid_chars() {
562 assert!(!is_valid("1122233300018!")); }
564
565 #[test]
566 fn strict_accepts_valid_unformatted() {
567 assert!(is_valid_strict("11222333000181").is_ok());
568 assert!(is_valid_strict("11444777000161").is_ok());
569 }
570
571 #[test]
572 fn strict_accepts_valid_formatted() {
573 assert!(is_valid_strict("11.222.333/0001-81").is_ok());
574 assert!(is_valid_strict("11.444.777/0001-61").is_ok());
575 }
576
577 #[test]
578 fn strict_rejects_garbage() {
579 assert!(is_valid_strict("11$222$333$0001$81").is_err());
580 }
581
582 #[test]
583 fn strict_rejects_whitespace() {
584 assert_eq!(
586 is_valid_strict(" 11222333000181 "),
587 Err(CnpjError::InvalidFormat)
588 );
589 assert_eq!(
591 is_valid_strict(" 11222333000181"),
592 Err(CnpjError::InvalidLength)
593 );
594 }
595
596 #[test]
597 fn strict_rejects_misplaced_separators() {
598 assert!(is_valid_strict("112.223.330/0018-1").is_err());
599 }
600
601 #[test]
602 fn strict_rejects_lowercase() {
603 let cnpj = cnpj_alpha();
604 let lower = cnpj.as_str().to_lowercase();
605 assert_eq!(is_valid_strict(&lower), Err(CnpjError::InvalidCharacter));
606 }
607
608 #[test]
609 fn strict_rejects_all_equal() {
610 assert_eq!(
611 is_valid_strict("11111111111111"),
612 Err(CnpjError::AllCharsEqual)
613 );
614 assert_eq!(
615 is_valid_strict("00.000.000/0000-00"),
616 Err(CnpjError::AllCharsEqual)
617 );
618 }
619
620 #[test]
621 fn strict_rejects_invalid_check_digits() {
622 assert_eq!(
623 is_valid_strict("11222333000182"),
624 Err(CnpjError::InvalidCheckDigits)
625 );
626 }
627
628 #[test]
629 fn parse_roundtrip_formatted() {
630 let cnpj = cnpj_numeric();
631 let parsed: Cnpj = cnpj.to_string().parse().unwrap();
632 assert_eq!(cnpj, parsed);
633 }
634
635 #[test]
636 fn parse_roundtrip_raw() {
637 let cnpj = cnpj_numeric();
638 let parsed: Cnpj = cnpj.as_str().parse().unwrap();
639 assert_eq!(cnpj, parsed);
640 }
641
642 #[test]
643 fn parse_roundtrip_alphanumeric() {
644 let cnpj = cnpj_alpha();
645 let from_raw: Cnpj = cnpj.as_str().parse().unwrap();
646 let from_fmt: Cnpj = cnpj.to_string().parse().unwrap();
647 assert_eq!(cnpj, from_raw);
648 assert_eq!(cnpj, from_fmt);
649 }
650
651 #[test]
652 fn accessor_kind() {
653 assert_eq!(cnpj_numeric().kind(), CnpjKind::Numeric);
654 assert_eq!(cnpj_alpha().kind(), CnpjKind::Alphanumeric);
655 }
656
657 #[test]
658 fn accessor_raiz() {
659 assert_eq!(cnpj_numeric().raiz(), "11222333");
660 }
661
662 #[test]
663 fn accessor_ordem() {
664 assert_eq!(cnpj_numeric().ordem(), "0001");
665 }
666
667 #[test]
668 fn accessor_establishment_type() {
669 assert_eq!(
670 cnpj_numeric().establishment_type(),
671 EstablishmentType::Matriz
672 );
673 let filial = make_cnpj(b"112223330002");
675 assert_eq!(filial.establishment_type(), EstablishmentType::Filial);
676 }
677
678 #[test]
679 fn accessor_check_digits() {
680 let cnpj = cnpj_numeric();
681 let (d1, d2) = cnpj.check_digits();
682 assert_eq!(d1, 8);
683 assert_eq!(d2, 1);
684 }
685
686 #[test]
687 fn accessor_masked() {
688 let cnpj = cnpj_numeric();
689 assert_eq!(cnpj.masked(), "11.222.333/****-**");
690 }
691
692 #[test]
693 fn format_cnpj_produces_formatted_output() {
694 assert_eq!(
695 format_cnpj("11222333000181"),
696 Some("11.222.333/0001-81".to_string())
697 );
698 }
699
700 #[test]
701 fn format_cnpj_preserves_letters() {
702 let cnpj = cnpj_alpha();
703 let formatted = format_cnpj(cnpj.as_str()).unwrap();
704 assert!(formatted.contains('/'));
705 assert!(formatted.contains('-'));
706 let reparsed: Cnpj = formatted.parse().unwrap();
707 assert_eq!(cnpj, reparsed);
708 }
709
710 #[test]
711 fn format_cnpj_returns_none_on_bad_length() {
712 assert_eq!(format_cnpj("1234"), None);
713 assert_eq!(format_cnpj(""), None);
714 }
715
716 #[test]
717 fn remove_symbols_strips_formatting() {
718 assert_eq!(remove_symbols("11.222.333/0001-81"), "11222333000181");
719 }
720
721 #[test]
722 fn remove_symbols_preserves_letters_and_uppercases() {
723 assert_eq!(remove_symbols("12.abc.345/00de-XX"), "12ABC34500DEXX");
724 }
725
726 #[test]
727 fn generate_numeric_produces_valid() {
728 for _ in 0..100 {
729 let cnpj = generate(CnpjKind::Numeric);
730 assert_eq!(cnpj.len(), 14);
731 assert!(is_valid(&cnpj), "generated invalid CNPJ: {cnpj}");
732 let parsed: Cnpj = cnpj.parse().unwrap();
733 assert_eq!(parsed.kind(), CnpjKind::Numeric);
734 }
735 }
736
737 #[test]
738 fn generate_alphanumeric_produces_valid() {
739 for _ in 0..100 {
740 let cnpj = generate(CnpjKind::Alphanumeric);
741 assert_eq!(cnpj.len(), 14);
742 assert!(is_valid(&cnpj), "generated invalid CNPJ: {cnpj}");
743 assert!(cnpj[..12].bytes().any(|b| b.is_ascii_uppercase()));
744 }
745 }
746
747 #[test]
748 fn seeded_alphanumeric_generation_always_contains_a_letter() {
749 for seed in 0..1_000 {
750 let cnpj = generate_with_seed(seed, CnpjKind::Alphanumeric);
751 assert_eq!(cnpj.kind(), CnpjKind::Alphanumeric, "seed {seed}");
752
753 let matriz = generate_matriz_with_seed(seed, CnpjKind::Alphanumeric);
754 assert_eq!(matriz.kind(), CnpjKind::Alphanumeric, "seed {seed}");
755 assert_eq!(matriz.ordem(), "0001");
756 }
757 }
758
759 #[test]
760 fn generate_cnpj_roundtrips() {
761 for _ in 0..100 {
762 let cnpj = generate_cnpj(CnpjKind::Numeric);
763 assert!(is_valid(cnpj.as_str()));
764 let parsed: Cnpj = cnpj.as_str().parse().unwrap();
765 assert_eq!(cnpj, parsed);
766 }
767 }
768
769 #[test]
770 fn generate_matriz_has_correct_ordem_and_type() {
771 for _ in 0..20 {
772 let cnpj = generate_matriz(CnpjKind::Numeric);
773 assert_eq!(cnpj.ordem(), "0001");
774 assert_eq!(cnpj.establishment_type(), EstablishmentType::Matriz);
775 assert!(is_valid(cnpj.as_str()));
776 }
777 for _ in 0..20 {
778 let cnpj = generate_matriz(CnpjKind::Alphanumeric);
779 assert_eq!(cnpj.ordem(), "0001");
780 assert_eq!(cnpj.establishment_type(), EstablishmentType::Matriz);
781 assert!(is_valid(cnpj.as_str()));
782 }
783 }
784
785 #[test]
786 fn compute_check_digits_known_base() {
787 let (d1, d2) = compute_check_digits("112223330001").unwrap();
788 assert_eq!(d1, 8);
789 assert_eq!(d2, 1);
790 }
791
792 #[test]
793 fn compute_check_digits_alphanumeric_base() {
794 let cnpj = cnpj_alpha();
795 let base = &cnpj.as_str()[..12];
796 let (d1, d2) = compute_check_digits(base).unwrap();
797 assert_eq!(d1, cnpj.check_digits().0);
798 assert_eq!(d2, cnpj.check_digits().1);
799 }
800
801 #[test]
802 fn compute_check_digits_rejects_bad_input() {
803 assert_eq!(compute_check_digits("12345678901"), None); assert_eq!(compute_check_digits("1234567890123"), None); assert_eq!(compute_check_digits("000000000000"), None); }
807
808 #[test]
809 fn cnpj_is_copy() {
810 let a = cnpj_numeric();
811 let b = a;
812 assert_eq!(a, b);
813 }
814
815 #[test]
816 fn cnpj_as_ref_str() {
817 let cnpj = cnpj_numeric();
818 let r: &str = cnpj.as_ref();
819 assert_eq!(r, cnpj.as_str());
820 }
821
822 #[test]
823 fn debug_format() {
824 let cnpj = cnpj_numeric();
825 let dbg = alloc::format!("{cnpj:?}");
826 assert!(dbg.starts_with("Cnpj("));
827 assert!(dbg.ends_with(')'));
828 assert!(dbg.contains('.'));
829 assert!(dbg.contains('/'));
830 assert!(dbg.contains('-'));
831 }
832
833 #[test]
834 fn display_format() {
835 let cnpj = cnpj_numeric();
836 assert_eq!(cnpj.to_string(), "11.222.333/0001-81");
837 }
838
839 #[test]
840 fn from_str_trait() {
841 let cnpj: Cnpj = "11.222.333/0001-81".parse().unwrap();
842 assert_eq!(cnpj.as_str(), "11222333000181");
843 }
844
845 #[test]
846 fn all_zeros_rejected() {
847 assert!(!is_valid("00000000000000"));
848 assert_eq!(
849 is_valid_strict("00000000000000"),
850 Err(CnpjError::AllCharsEqual)
851 );
852 }
853
854 #[test]
855 fn leading_zeros() {
856 let result = is_valid("00623904000173");
858 if let Some((d1, d2)) = compute_check_digits("006239040001") {
860 let cnpj_str = alloc::format!("006239040001{d1}{d2}");
861 assert!(is_valid(&cnpj_str));
862 let cnpj: Cnpj = cnpj_str.parse().unwrap();
863 assert!(cnpj.as_str().starts_with("00"));
864 } else {
865 let _ = result;
867 }
868 }
869
870 #[test]
871 fn char_value_mapping() {
872 assert_eq!(char_value(b'0'), 0);
873 assert_eq!(char_value(b'9'), 9);
874 assert_eq!(char_value(b'A'), 17);
875 assert_eq!(char_value(b'Z'), 42);
876 }
877}