1use alloc::string::String;
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::rand::{simple_seed, xorshift64};
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
55#[derive(Clone, Copy, PartialEq, Eq, Hash)]
71pub struct Cnpj {
72 bytes: [u8; CNPJ_LEN],
73}
74
75impl Cnpj {
76 pub fn as_str(&self) -> &str {
78 unsafe { core::str::from_utf8_unchecked(&self.bytes) }
80 }
81
82 pub fn kind(&self) -> CnpjKind {
84 if self.bytes[..12].iter().all(u8::is_ascii_digit) {
85 CnpjKind::Numeric
86 } else {
87 CnpjKind::Alphanumeric
88 }
89 }
90
91 pub fn raiz(&self) -> &str {
93 unsafe { core::str::from_utf8_unchecked(&self.bytes[..8]) }
94 }
95
96 pub fn ordem(&self) -> &str {
98 unsafe { core::str::from_utf8_unchecked(&self.bytes[8..12]) }
99 }
100
101 pub fn establishment_type(&self) -> EstablishmentType {
103 if &self.bytes[8..12] == b"0001" {
104 EstablishmentType::Matriz
105 } else {
106 EstablishmentType::Filial
107 }
108 }
109
110 pub fn check_digits(&self) -> (u8, u8) {
112 (self.bytes[12] - b'0', self.bytes[13] - b'0')
113 }
114
115 pub fn masked(&self) -> String {
117 let s = self.as_str();
118 alloc::format!("{}.{}.{}/****-**", &s[0..2], &s[2..5], &s[5..8])
119 }
120}
121
122impl fmt::Display for Cnpj {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 let s = self.as_str();
125 write!(
126 f,
127 "{}.{}.{}/{}-{}",
128 &s[0..2],
129 &s[2..5],
130 &s[5..8],
131 &s[8..12],
132 &s[12..14]
133 )
134 }
135}
136
137impl_document_traits!(Cnpj, CnpjError);
138
139pub fn remove_symbols(cnpj: &str) -> String {
141 cnpj.chars()
142 .filter(char::is_ascii_alphanumeric)
143 .map(|c| c.to_ascii_uppercase())
144 .collect()
145}
146
147pub fn is_valid(cnpj: &str) -> bool {
149 let raw = remove_symbols(cnpj);
150 if raw.len() != CNPJ_LEN {
151 return false;
152 }
153 let bytes = raw.as_bytes();
154 if !bytes[..12]
156 .iter()
157 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
158 {
159 return false;
160 }
161 if !bytes[12..14].iter().all(u8::is_ascii_digit) {
162 return false;
163 }
164 validate(bytes)
165}
166
167pub fn is_valid_strict(cnpj: &str) -> Result<(), CnpjError> {
169 parse_strict(cnpj).map(|_| ())
170}
171
172pub fn format_cnpj(cnpj: &str) -> Option<String> {
174 let raw = remove_symbols(cnpj);
175 if raw.len() != CNPJ_LEN {
176 return None;
177 }
178 let bytes = raw.as_bytes();
179 if !bytes[..12]
180 .iter()
181 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
182 {
183 return None;
184 }
185 if !bytes[12..14].iter().all(u8::is_ascii_digit) {
186 return None;
187 }
188 Some(alloc::format!(
189 "{}.{}.{}/{}-{}",
190 &raw[0..2],
191 &raw[2..5],
192 &raw[5..8],
193 &raw[8..12],
194 &raw[12..14]
195 ))
196}
197
198pub fn generate(kind: CnpjKind) -> String {
200 generate_cnpj(kind).as_str().into()
201}
202
203pub fn generate_cnpj(kind: CnpjKind) -> Cnpj {
205 generate_with_seed(simple_seed(), kind)
206}
207
208pub fn generate_matriz(kind: CnpjKind) -> Cnpj {
210 generate_with_ordem(simple_seed(), kind, *b"0001")
211}
212
213pub fn compute_check_digits(base: &str) -> Option<(u8, u8)> {
216 let raw = remove_symbols(base);
217 if raw.len() != 12 {
218 return None;
219 }
220 let bytes = raw.as_bytes();
221 if !bytes
222 .iter()
223 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
224 {
225 return None;
226 }
227 if all_equal(bytes) {
228 return None;
229 }
230
231 let values: Vec<u32> = bytes.iter().map(|&b| char_value(b)).collect();
232 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
233 let mut full = Vec::with_capacity(13);
234 full.extend_from_slice(&values);
235 full.push(u32::from(d1));
236 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
237
238 Some((d1, d2))
239}
240
241fn char_value(b: u8) -> u32 {
246 u32::from(b) - 48
247}
248
249fn all_equal(bytes: &[u8]) -> bool {
250 util::all_equal(bytes)
251}
252
253fn validate(bytes: &[u8]) -> bool {
254 if all_equal(bytes) {
255 return false;
256 }
257 let values: Vec<u32> = bytes[..12].iter().map(|&b| char_value(b)).collect();
258 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
259 let mut full = Vec::with_capacity(13);
260 full.extend_from_slice(&values);
261 full.push(u32::from(d1));
262 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
263 bytes[12] - b'0' == d1 && bytes[13] - b'0' == d2
264}
265
266fn calc_check_digit(values: &[u32], weights: &[u32]) -> u8 {
267 let sum: u32 = values.iter().zip(weights).map(|(&v, &w)| v * w).sum();
268 let rem = sum % 11;
269 #[allow(clippy::cast_possible_truncation)]
270 if rem < 2 { 0 } else { (11 - rem) as u8 }
272}
273
274fn append_check_digits(bytes: &mut [u8; CNPJ_LEN]) {
275 let values: Vec<u32> = bytes[..12].iter().map(|&b| char_value(b)).collect();
276 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
277 let mut full = Vec::with_capacity(13);
278 full.extend_from_slice(&values);
279 full.push(u32::from(d1));
280 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
281 bytes[12] = b'0' + d1;
282 bytes[13] = b'0' + d2;
283}
284
285fn parse_strict(s: &str) -> Result<Cnpj, CnpjError> {
286 let raw = s.as_bytes();
287
288 let chars: Vec<u8> = match raw.len() {
289 14 => {
290 if !raw[..12]
292 .iter()
293 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
294 {
295 return Err(CnpjError::InvalidCharacter);
296 }
297 if !raw[12..14].iter().all(u8::is_ascii_digit) {
298 return Err(CnpjError::InvalidCharacter);
299 }
300 raw.to_vec()
301 }
302 18 => {
303 if raw[2] != b'.' || raw[6] != b'.' || raw[10] != b'/' || raw[15] != b'-' {
305 return Err(CnpjError::InvalidFormat);
306 }
307 let extracted: Vec<u8> = FORMATTED_CHAR_POS.iter().map(|&i| raw[i]).collect();
308 if !extracted[..12]
309 .iter()
310 .all(|&b| b.is_ascii_uppercase() || b.is_ascii_digit())
311 {
312 return Err(CnpjError::InvalidCharacter);
313 }
314 if !extracted[12..14].iter().all(u8::is_ascii_digit) {
315 return Err(CnpjError::InvalidCharacter);
316 }
317 extracted
318 }
319 _ => return Err(CnpjError::InvalidLength),
320 };
321
322 if all_equal(&chars) {
323 return Err(CnpjError::AllCharsEqual);
324 }
325
326 let values: Vec<u32> = chars[..12].iter().map(|&b| char_value(b)).collect();
327 let d1 = calc_check_digit(&values, &WEIGHTS_D1);
328 let mut full = Vec::with_capacity(13);
329 full.extend_from_slice(&values);
330 full.push(u32::from(d1));
331 let d2 = calc_check_digit(&full, &WEIGHTS_D2);
332
333 if chars[12] - b'0' != d1 || chars[13] - b'0' != d2 {
334 return Err(CnpjError::InvalidCheckDigits);
335 }
336
337 let mut bytes = [0u8; CNPJ_LEN];
338 bytes.copy_from_slice(&chars);
339 Ok(Cnpj { bytes })
340}
341
342const ALPHANUMERIC_CHARS: &[u8; 36] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
343
344fn generate_with_seed(mut seed: u64, kind: CnpjKind) -> Cnpj {
345 let mut bytes = [0u8; CNPJ_LEN];
346
347 loop {
348 for b in &mut bytes[..12] {
349 seed = xorshift64(seed);
350 *b = match kind {
351 CnpjKind::Numeric => b'0' + (seed % 10) as u8,
352 CnpjKind::Alphanumeric => ALPHANUMERIC_CHARS[(seed % 36) as usize],
353 };
354 }
355 if !all_equal(&bytes[..12]) {
356 break;
357 }
358 }
359
360 append_check_digits(&mut bytes);
361 Cnpj { bytes }
362}
363
364fn generate_with_ordem(mut seed: u64, kind: CnpjKind, ordem: [u8; 4]) -> Cnpj {
365 let mut bytes = [0u8; CNPJ_LEN];
366 bytes[8..12].copy_from_slice(&ordem);
367
368 loop {
369 for b in &mut bytes[..8] {
370 seed = xorshift64(seed);
371 *b = match kind {
372 CnpjKind::Numeric => b'0' + (seed % 10) as u8,
373 CnpjKind::Alphanumeric => ALPHANUMERIC_CHARS[(seed % 36) as usize],
374 };
375 }
376 if !all_equal(&bytes[..12]) {
377 break;
378 }
379 }
380
381 append_check_digits(&mut bytes);
382 Cnpj { bytes }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use alloc::string::ToString;
389
390 fn cnpj_numeric() -> Cnpj {
392 "11222333000181".parse().unwrap()
393 }
394
395 fn cnpj_numeric_b() -> Cnpj {
397 "11444777000161".parse().unwrap()
398 }
399
400 fn make_cnpj(base: &[u8; 12]) -> Cnpj {
402 let mut bytes = [0u8; CNPJ_LEN];
403 bytes[..12].copy_from_slice(base);
404 append_check_digits(&mut bytes);
405 Cnpj { bytes }
406 }
407
408 fn cnpj_alpha() -> Cnpj {
409 make_cnpj(b"12ABC34500DE")
411 }
412
413 #[test]
414 fn parse_known_numeric() {
415 let cnpj = cnpj_numeric();
416 assert_eq!(cnpj.as_str(), "11222333000181");
417 assert_eq!(cnpj.kind(), CnpjKind::Numeric);
418 }
419
420 #[test]
421 fn parse_known_numeric_b() {
422 let cnpj = cnpj_numeric_b();
423 assert_eq!(cnpj.as_str(), "11444777000161");
424 assert_eq!(cnpj.kind(), CnpjKind::Numeric);
425 }
426
427 #[test]
428 fn validate_known_numeric() {
429 assert!(is_valid("11.222.333/0001-81"));
430 assert!(is_valid("11222333000181"));
431 assert!(is_valid("11.444.777/0001-61"));
432 assert!(is_valid("11444777000161"));
433 }
434
435 #[test]
436 fn parse_alphanumeric() {
437 let cnpj = cnpj_alpha();
438 assert_eq!(cnpj.kind(), CnpjKind::Alphanumeric);
439 assert!(is_valid(cnpj.as_str()));
440 }
441
442 #[test]
443 fn validate_alphanumeric_formatted() {
444 let cnpj = cnpj_alpha();
445 let formatted = cnpj.to_string();
446 assert!(is_valid_strict(&formatted).is_ok());
447 }
448
449 #[test]
450 fn is_valid_lenient_strips_garbage() {
451 let cnpj = cnpj_numeric();
452 let s = cnpj.as_str();
453 let garbage = alloc::format!("{}${}#{}!{}", &s[0..2], &s[2..5], &s[5..8], &s[8..14]);
454 assert!(is_valid(&garbage));
455 }
456
457 #[test]
458 fn is_valid_lenient_lowercase_to_uppercase() {
459 let cnpj = cnpj_alpha();
460 let lower = cnpj.as_str().to_lowercase();
461 assert!(is_valid(&lower));
462 }
463
464 #[test]
465 fn is_valid_rejects_bad_check_digits() {
466 let mut bytes = *b"11222333000182"; assert!(!is_valid(core::str::from_utf8(&bytes).unwrap()));
468 bytes = *b"11222333000191"; assert!(!is_valid(core::str::from_utf8(&bytes).unwrap()));
470 }
471
472 #[test]
473 fn is_valid_rejects_all_equal() {
474 assert!(!is_valid("11111111111111"));
475 assert!(!is_valid("00000000000000"));
476 assert!(!is_valid("AAAAAAAAAAAAAA"));
477 }
478
479 #[test]
480 fn is_valid_rejects_wrong_length() {
481 assert!(!is_valid(""));
482 assert!(!is_valid("1234567890123"));
483 assert!(!is_valid("123456789012345"));
484 }
485
486 #[test]
487 fn is_valid_rejects_invalid_chars() {
488 assert!(!is_valid("1122233300018!")); }
490
491 #[test]
492 fn strict_accepts_valid_unformatted() {
493 assert!(is_valid_strict("11222333000181").is_ok());
494 assert!(is_valid_strict("11444777000161").is_ok());
495 }
496
497 #[test]
498 fn strict_accepts_valid_formatted() {
499 assert!(is_valid_strict("11.222.333/0001-81").is_ok());
500 assert!(is_valid_strict("11.444.777/0001-61").is_ok());
501 }
502
503 #[test]
504 fn strict_rejects_garbage() {
505 assert!(is_valid_strict("11$222$333$0001$81").is_err());
506 }
507
508 #[test]
509 fn strict_rejects_whitespace() {
510 assert_eq!(
512 is_valid_strict(" 11222333000181 "),
513 Err(CnpjError::InvalidFormat)
514 );
515 assert_eq!(
517 is_valid_strict(" 11222333000181"),
518 Err(CnpjError::InvalidLength)
519 );
520 }
521
522 #[test]
523 fn strict_rejects_misplaced_separators() {
524 assert!(is_valid_strict("112.223.330/0018-1").is_err());
525 }
526
527 #[test]
528 fn strict_rejects_lowercase() {
529 let cnpj = cnpj_alpha();
530 let lower = cnpj.as_str().to_lowercase();
531 assert_eq!(is_valid_strict(&lower), Err(CnpjError::InvalidCharacter));
532 }
533
534 #[test]
535 fn strict_rejects_all_equal() {
536 assert_eq!(
537 is_valid_strict("11111111111111"),
538 Err(CnpjError::AllCharsEqual)
539 );
540 assert_eq!(
541 is_valid_strict("00.000.000/0000-00"),
542 Err(CnpjError::AllCharsEqual)
543 );
544 }
545
546 #[test]
547 fn strict_rejects_invalid_check_digits() {
548 assert_eq!(
549 is_valid_strict("11222333000182"),
550 Err(CnpjError::InvalidCheckDigits)
551 );
552 }
553
554 #[test]
555 fn parse_roundtrip_formatted() {
556 let cnpj = cnpj_numeric();
557 let parsed: Cnpj = cnpj.to_string().parse().unwrap();
558 assert_eq!(cnpj, parsed);
559 }
560
561 #[test]
562 fn parse_roundtrip_raw() {
563 let cnpj = cnpj_numeric();
564 let parsed: Cnpj = cnpj.as_str().parse().unwrap();
565 assert_eq!(cnpj, parsed);
566 }
567
568 #[test]
569 fn parse_roundtrip_alphanumeric() {
570 let cnpj = cnpj_alpha();
571 let from_raw: Cnpj = cnpj.as_str().parse().unwrap();
572 let from_fmt: Cnpj = cnpj.to_string().parse().unwrap();
573 assert_eq!(cnpj, from_raw);
574 assert_eq!(cnpj, from_fmt);
575 }
576
577 #[test]
578 fn accessor_kind() {
579 assert_eq!(cnpj_numeric().kind(), CnpjKind::Numeric);
580 assert_eq!(cnpj_alpha().kind(), CnpjKind::Alphanumeric);
581 }
582
583 #[test]
584 fn accessor_raiz() {
585 assert_eq!(cnpj_numeric().raiz(), "11222333");
586 }
587
588 #[test]
589 fn accessor_ordem() {
590 assert_eq!(cnpj_numeric().ordem(), "0001");
591 }
592
593 #[test]
594 fn accessor_establishment_type() {
595 assert_eq!(
596 cnpj_numeric().establishment_type(),
597 EstablishmentType::Matriz
598 );
599 let filial = make_cnpj(b"112223330002");
601 assert_eq!(filial.establishment_type(), EstablishmentType::Filial);
602 }
603
604 #[test]
605 fn accessor_check_digits() {
606 let cnpj = cnpj_numeric();
607 let (d1, d2) = cnpj.check_digits();
608 assert_eq!(d1, 8);
609 assert_eq!(d2, 1);
610 }
611
612 #[test]
613 fn accessor_masked() {
614 let cnpj = cnpj_numeric();
615 assert_eq!(cnpj.masked(), "11.222.333/****-**");
616 }
617
618 #[test]
619 fn format_cnpj_produces_formatted_output() {
620 assert_eq!(
621 format_cnpj("11222333000181"),
622 Some("11.222.333/0001-81".to_string())
623 );
624 }
625
626 #[test]
627 fn format_cnpj_preserves_letters() {
628 let cnpj = cnpj_alpha();
629 let formatted = format_cnpj(cnpj.as_str()).unwrap();
630 assert!(formatted.contains('/'));
631 assert!(formatted.contains('-'));
632 let reparsed: Cnpj = formatted.parse().unwrap();
633 assert_eq!(cnpj, reparsed);
634 }
635
636 #[test]
637 fn format_cnpj_returns_none_on_bad_length() {
638 assert_eq!(format_cnpj("1234"), None);
639 assert_eq!(format_cnpj(""), None);
640 }
641
642 #[test]
643 fn remove_symbols_strips_formatting() {
644 assert_eq!(remove_symbols("11.222.333/0001-81"), "11222333000181");
645 }
646
647 #[test]
648 fn remove_symbols_preserves_letters_and_uppercases() {
649 assert_eq!(remove_symbols("12.abc.345/00de-XX"), "12ABC34500DEXX");
650 }
651
652 #[test]
653 fn generate_numeric_produces_valid() {
654 for _ in 0..100 {
655 let cnpj = generate(CnpjKind::Numeric);
656 assert_eq!(cnpj.len(), 14);
657 assert!(is_valid(&cnpj), "generated invalid CNPJ: {cnpj}");
658 let parsed: Cnpj = cnpj.parse().unwrap();
659 assert_eq!(parsed.kind(), CnpjKind::Numeric);
660 }
661 }
662
663 #[test]
664 fn generate_alphanumeric_produces_valid() {
665 for _ in 0..100 {
666 let cnpj = generate(CnpjKind::Alphanumeric);
667 assert_eq!(cnpj.len(), 14);
668 assert!(is_valid(&cnpj), "generated invalid CNPJ: {cnpj}");
669 }
670 }
671
672 #[test]
673 fn generate_cnpj_roundtrips() {
674 for _ in 0..100 {
675 let cnpj = generate_cnpj(CnpjKind::Numeric);
676 assert!(is_valid(cnpj.as_str()));
677 let parsed: Cnpj = cnpj.as_str().parse().unwrap();
678 assert_eq!(cnpj, parsed);
679 }
680 }
681
682 #[test]
683 fn generate_matriz_has_correct_ordem_and_type() {
684 for _ in 0..20 {
685 let cnpj = generate_matriz(CnpjKind::Numeric);
686 assert_eq!(cnpj.ordem(), "0001");
687 assert_eq!(cnpj.establishment_type(), EstablishmentType::Matriz);
688 assert!(is_valid(cnpj.as_str()));
689 }
690 for _ in 0..20 {
691 let cnpj = generate_matriz(CnpjKind::Alphanumeric);
692 assert_eq!(cnpj.ordem(), "0001");
693 assert_eq!(cnpj.establishment_type(), EstablishmentType::Matriz);
694 assert!(is_valid(cnpj.as_str()));
695 }
696 }
697
698 #[test]
699 fn compute_check_digits_known_base() {
700 let (d1, d2) = compute_check_digits("112223330001").unwrap();
701 assert_eq!(d1, 8);
702 assert_eq!(d2, 1);
703 }
704
705 #[test]
706 fn compute_check_digits_alphanumeric_base() {
707 let cnpj = cnpj_alpha();
708 let base = &cnpj.as_str()[..12];
709 let (d1, d2) = compute_check_digits(base).unwrap();
710 assert_eq!(d1, cnpj.check_digits().0);
711 assert_eq!(d2, cnpj.check_digits().1);
712 }
713
714 #[test]
715 fn compute_check_digits_rejects_bad_input() {
716 assert_eq!(compute_check_digits("12345678901"), None); assert_eq!(compute_check_digits("1234567890123"), None); assert_eq!(compute_check_digits("000000000000"), None); }
720
721 #[test]
722 fn cnpj_is_copy() {
723 let a = cnpj_numeric();
724 let b = a;
725 assert_eq!(a, b);
726 }
727
728 #[test]
729 fn cnpj_as_ref_str() {
730 let cnpj = cnpj_numeric();
731 let r: &str = cnpj.as_ref();
732 assert_eq!(r, cnpj.as_str());
733 }
734
735 #[test]
736 fn debug_format() {
737 let cnpj = cnpj_numeric();
738 let dbg = alloc::format!("{cnpj:?}");
739 assert!(dbg.starts_with("Cnpj("));
740 assert!(dbg.ends_with(')'));
741 assert!(dbg.contains('.'));
742 assert!(dbg.contains('/'));
743 assert!(dbg.contains('-'));
744 }
745
746 #[test]
747 fn display_format() {
748 let cnpj = cnpj_numeric();
749 assert_eq!(cnpj.to_string(), "11.222.333/0001-81");
750 }
751
752 #[test]
753 fn from_str_trait() {
754 let cnpj: Cnpj = "11.222.333/0001-81".parse().unwrap();
755 assert_eq!(cnpj.as_str(), "11222333000181");
756 }
757
758 #[test]
759 fn all_zeros_rejected() {
760 assert!(!is_valid("00000000000000"));
761 assert_eq!(
762 is_valid_strict("00000000000000"),
763 Err(CnpjError::AllCharsEqual)
764 );
765 }
766
767 #[test]
768 fn leading_zeros() {
769 let result = is_valid("00623904000173");
771 if let Some((d1, d2)) = compute_check_digits("006239040001") {
773 let cnpj_str = alloc::format!("006239040001{d1}{d2}");
774 assert!(is_valid(&cnpj_str));
775 let cnpj: Cnpj = cnpj_str.parse().unwrap();
776 assert!(cnpj.as_str().starts_with("00"));
777 } else {
778 let _ = result;
780 }
781 }
782
783 #[test]
784 fn char_value_mapping() {
785 assert_eq!(char_value(b'0'), 0);
786 assert_eq!(char_value(b'9'), 9);
787 assert_eq!(char_value(b'A'), 17);
788 assert_eq!(char_value(b'Z'), 42);
789 }
790}