Skip to main content

tempest_core/
utils.rs

1use std::fmt;
2
3use bytes::{Bytes, BytesMut};
4
5/// Helper struct that wraps a byte count and improves its [`std::fmt::Debug`] formatting.
6/// Formats the value as a human-readable size, automatically selecting the most appropriate
7/// unit (B, KiB, MiB, GiB, TiB). Trailing fractional zeros are omitted, and exact multiples
8/// of a unit are printed without a decimal point entirely.
9///
10/// # Examples
11///
12/// ```
13/// # use tempest_core::utils::ByteSize;
14/// assert_eq!(format!("{:?}", ByteSize(484)),        "484B");
15/// assert_eq!(format!("{:?}", ByteSize(1536)),       "1.5KiB");
16/// assert_eq!(format!("{:?}", ByteSize(2147483648)), "2GiB");
17/// assert_eq!(format!("{:?}", ByteSize(2684354560)), "2.5GiB");
18/// ```
19pub struct ByteSize(pub u64);
20
21impl fmt::Debug for ByteSize {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        const KIB: u64 = 1024;
24        const MIB: u64 = 1024 * KIB;
25        const GIB: u64 = 1024 * MIB;
26        const TIB: u64 = 1024 * GIB;
27
28        macro_rules! fmt_unit {
29            ($val:expr, $unit:expr, $unit_str:expr) => {{
30                let whole = $val / $unit;
31                let rem = $val % $unit;
32                if rem == 0 {
33                    write!(f, "{}{}", whole, $unit_str)
34                } else {
35                    // two decimal places via integer arithmetic:
36                    // multiply remainder by 100, divide by unit to get 0-99
37                    let frac = rem * 100 / $unit;
38                    if frac % 10 == 0 {
39                        // e.g. 1.50 -> 1.5
40                        write!(f, "{}.{}{}", whole, frac / 10, $unit_str)
41                    } else {
42                        write!(f, "{}.{:02}{}", whole, frac, $unit_str)
43                    }
44                }
45            }};
46        }
47
48        match self.0 {
49            b if b >= TIB => fmt_unit!(b, TIB, "TiB"),
50            b if b >= GIB => fmt_unit!(b, GIB, "GiB"),
51            b if b >= MIB => fmt_unit!(b, MIB, "MiB"),
52            b if b >= KIB => fmt_unit!(b, KIB, "KiB"),
53            b => write!(f, "{}B", b),
54        }
55    }
56}
57
58/// Helper struct that wraps a byte slice and improves its [`std::fmt::Debug`] formatting.
59/// Printable ASCII characters are rendered as-is; non-printable bytes are escaped as `\xNN`.
60/// Matches the formatting convention of the [`bytes::Bytes`] type for consistency across logs.
61///
62/// # Examples
63///
64/// ```
65/// # use tempest_core::utils::PrettyBytes;
66/// assert_eq!(format!("{:?}", PrettyBytes(b"key1")), "b\"key1\"");
67/// assert_eq!(format!("{:?}", PrettyBytes(b"\x00\xFF")), "b\"\\x00\\xff\"");
68/// ```
69pub struct PrettyBytes<'a>(pub &'a [u8]);
70
71impl fmt::Debug for PrettyBytes<'_> {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "b\"")?;
74        for &b in self.0 {
75            match b {
76                // human readable symbols, alphabet, etc
77                b' '..=b'~' => write!(f, "{}", b as char)?,
78                // other bytes, like enter/backspace/newline
79                _ => write!(f, "\\x{:02x}", b)?,
80            }
81        }
82        write!(f, "\"")
83    }
84}
85
86macro_rules! impl_hex {
87    ($name:ident, $type:ty) => {
88        impl_hex!($name, $type, $type);
89    };
90    ($name:ident, $type:ty, $unsigned:ty) => {
91        #[doc = concat!(
92                    "Helper struct that wraps a [`",
93                    stringify!($type),
94                    "`] and improves its [`std::fmt::Debug`] formatting. ",
95                    "Formats the value as a zero-padded hexadecimal number with a `0x` prefix, ",
96                    "padded to the full bit-width of [`",
97                    stringify!($type),
98                    "`]. ",
99                    "Signed types are cast to their unsigned counterpart before formatting, ",
100                    "giving the two's complement representation without a sign prefix.",
101                )]
102        pub struct $name(pub $type);
103
104        impl fmt::Debug for $name {
105            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106                write!(f, "0x{:x}", self.0 as $unsigned)
107            }
108        }
109    };
110}
111
112impl_hex!(HexU8, u8);
113impl_hex!(HexU16, u16);
114impl_hex!(HexU32, u32);
115impl_hex!(HexU64, u64);
116impl_hex!(HexI8, i8, u8);
117impl_hex!(HexI16, i16, u16);
118impl_hex!(HexI32, i32, u32);
119impl_hex!(HexI64, i64, u64);
120
121#[inline(always)]
122pub fn contains_null(v: impl AsRef<[u8]>) -> bool {
123    let v = v.as_ref();
124    memchr::memchr(0, v).is_some()
125}
126
127pub fn next_prefix(prefix: &[u8]) -> Option<Bytes> {
128    let mut buf = BytesMut::from(prefix);
129    while buf.last() == Some(&0xFF) {
130        buf.truncate(buf.len() - 1);
131    }
132    let last = buf.last_mut()?;
133    *last += 1;
134    Some(buf.freeze())
135}