Skip to main content

ftracker_identifiers/cnpj/
fmt.rs

1//! `Display`/`Debug` for [`Cnpj`] and the zero-allocation [`FormattedCnpj`] helper returned by [`Cnpj::formatted`].
2
3use crate::cnpj::Cnpj;
4use core::fmt;
5use core::ops::Deref;
6
7/// Total length of the punctuated representation: `AA.AAA.AAA/AAAA-DD`.
8const FORMATTED_LEN: usize = 18;
9
10/// A stack-allocated, punctuated rendering of a [`Cnpj`] (`AA.AAA.AAA/AAAA-DD`), e.g. `"12.ABC.345/01DE-35"`.
11///
12/// This exists so that [`Cnpj::formatted`] can hand back a `Display`-able, `Deref<Target = str>`
13/// value without allocating on the heap. Everything backing it is a fixed-size `[u8; 18]` produced
14/// once at construction.
15#[derive(Clone, Copy, PartialEq, Eq, Hash)]
16pub struct FormattedCnpj([u8; FORMATTED_LEN]);
17
18impl FormattedCnpj {
19    pub(super) fn new(cnpj: &Cnpj) -> Self {
20        let d = cnpj.as_bytes();
21        let mut out = [0u8; FORMATTED_LEN];
22        let layout: [u8; FORMATTED_LEN] = [
23            d[0], d[1], b'.', d[2], d[3], d[4], b'.', d[5], d[6], d[7], b'/', d[8], d[9], d[10],
24            d[11], b'-', d[12], d[13],
25        ];
26        out.copy_from_slice(&layout);
27        FormattedCnpj(out)
28    }
29
30    /// Borrows the formatted value as a `&str`.
31    ///
32    /// This never allocates and never panics: the byte layout is built exclusively from [`Cnpj`]'s
33    /// own validated ASCII bytes plus ASCII punctuation, which is guaranteed valid UTF-8.
34    #[must_use]
35    pub fn as_str(&self) -> &str {
36        // SAFETY: `FormattedCnpj::new` builds this buffer exclusively from a validated `Cnpj`'s
37        // ASCII bytes interleaved with ASCII punctuation (`.`, `/`, `-`), so it is always UTF-8.
38        unsafe { core::str::from_utf8_unchecked(&self.0) }
39    }
40}
41
42impl Deref for FormattedCnpj {
43    type Target = str;
44
45    fn deref(&self) -> &str {
46        self.as_str()
47    }
48}
49
50impl AsRef<str> for FormattedCnpj {
51    fn as_ref(&self) -> &str {
52        self.as_str()
53    }
54}
55
56impl fmt::Display for FormattedCnpj {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        f.write_str(self.as_str())
59    }
60}
61
62impl fmt::Debug for FormattedCnpj {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_tuple("FormattedCnpj")
65            .field(&self.as_str())
66            .finish()
67    }
68}
69
70impl fmt::Display for Cnpj {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.write_str(self.formatted().as_str())
73    }
74}
75
76impl fmt::Debug for Cnpj {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        f.debug_tuple("Cnpj")
79            .field(&self.formatted().as_str())
80            .finish()
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use crate::cnpj::Cnpj;
87    use alloc::format;
88    use alloc::string::ToString;
89
90    #[test]
91    fn formats_numeric_cnpj() {
92        let cnpj = Cnpj::parse("00000000000191").unwrap();
93        assert_eq!(cnpj.formatted().as_str(), "00.000.000/0001-91");
94        assert_eq!(cnpj.to_string(), "00.000.000/0001-91");
95    }
96
97    #[test]
98    fn formats_alphanumeric_cnpj() {
99        let cnpj = Cnpj::parse("12ABC34501DE35").unwrap();
100        assert_eq!(cnpj.formatted().as_str(), "12.ABC.345/01DE-35");
101    }
102
103    #[test]
104    fn debug_is_readable() {
105        let cnpj = Cnpj::parse("00000000000191").unwrap();
106        assert_eq!(format!("{cnpj:?}"), "Cnpj(\"00.000.000/0001-91\")");
107    }
108}