toktrie/
bytes.rs

1use std::fmt::{self, Display, Write};
2use std::mem::size_of;
3
4use anyhow::{anyhow, Result};
5use bytemuck::{NoUninit, Pod as PodTrait};
6use bytemuck_derive::{Pod, Zeroable};
7
8#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroable, Pod)]
9#[repr(C)]
10pub struct U32Pair(pub u32, pub u32);
11
12pub fn clone_vec_as_bytes<T: NoUninit>(input: &[T]) -> Vec<u8> {
13    bytemuck::cast_slice(input).to_vec()
14}
15
16pub fn vec_from_bytes<T: PodTrait>(bytes: &[u8]) -> Vec<T> {
17    if bytes.len() % size_of::<T>() != 0 {
18        panic!(
19            "vecT: got {} bytes, needed multiple of {}",
20            bytes.len(),
21            size_of::<T>()
22        );
23    }
24    bytemuck::cast_slice(bytes).to_vec()
25}
26
27pub fn limit_str(s: &str, max_len: usize) -> String {
28    limit_bytes(s.as_bytes(), max_len)
29}
30
31pub fn limit_bytes(s: &[u8], max_len: usize) -> String {
32    if s.len() > max_len {
33        format!("{}...", String::from_utf8_lossy(&s[0..max_len]))
34    } else {
35        String::from_utf8_lossy(s).to_string()
36    }
37}
38
39pub fn to_hex_string(bytes: &[u8]) -> String {
40    bytes
41        .iter()
42        .map(|b| format!("{:02x}", b))
43        .collect::<Vec<_>>()
44        .join("")
45}
46
47pub fn from_hex_string(s: &str) -> Result<Vec<u8>> {
48    let mut result = Vec::with_capacity(s.len() / 2);
49    let mut iter = s.chars();
50    while let Some(c1) = iter.next() {
51        let c2 = iter
52            .next()
53            .ok_or_else(|| anyhow!("expecting even number of chars"))?;
54        let byte = u8::from_str_radix(&format!("{}{}", c1, c2), 16)?;
55        result.push(byte);
56    }
57    Ok(result)
58}
59
60struct LimitedWriter<'a> {
61    buf: &'a mut Vec<u8>,
62    max_len: usize,
63}
64
65impl<'a> LimitedWriter<'a> {
66    fn new(buf: &'a mut Vec<u8>, max_len: usize) -> Self {
67        Self { buf, max_len }
68    }
69}
70
71impl Write for LimitedWriter<'_> {
72    fn write_str(&mut self, s: &str) -> fmt::Result {
73        let remaining = self.max_len.saturating_sub(self.buf.len());
74        if s.len() > remaining {
75            self.buf.extend_from_slice(&s.as_bytes()[..remaining]);
76            Err(fmt::Error)
77        } else {
78            self.buf.extend_from_slice(s.as_bytes());
79            Ok(())
80        }
81    }
82}
83
84pub fn limit_display(obj: impl Display, max_len: usize) -> String {
85    let mut buffer = Vec::new();
86    let mut writer = LimitedWriter::new(&mut buffer, max_len);
87
88    let r = write!(writer, "{}", obj);
89    let mut exceeded = r.is_err();
90    let mut valid_str = match String::from_utf8(buffer) {
91        Ok(s) => s,
92        Err(e) => {
93            exceeded = true;
94            let l = e.utf8_error().valid_up_to();
95            let mut buf = e.into_bytes();
96            buf.truncate(l);
97            String::from_utf8(buf).unwrap()
98        }
99    };
100
101    if exceeded {
102        valid_str.push_str("...");
103    }
104    valid_str
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_short_string() {
113        let result = limit_display("hello", 10);
114        assert_eq!(result, "hello");
115    }
116
117    #[test]
118    fn test_exact_length() {
119        let result = limit_display("1234567890", 10);
120        assert_eq!(result, "1234567890");
121    }
122
123    #[test]
124    fn test_truncate_with_ellipsis() {
125        let result = limit_display("This is a long string", 10);
126        assert_eq!(result, "This is a ...");
127    }
128
129    #[test]
130    fn test_utf8_truncation() {
131        let result = limit_display("😀😀😀😀😀", 10);
132        assert_eq!(result, "😀😀...");
133    }
134
135    #[test]
136    fn test_utf8_partial_char() {
137        let result = limit_display("😀😀😀", 7);
138        assert_eq!(result, "😀...");
139    }
140
141    #[test]
142    fn test_empty_string() {
143        let result = limit_display("", 10);
144        assert_eq!(result, "");
145    }
146
147    #[test]
148    fn test_very_small_limit() {
149        let result = limit_display("hello", 1);
150        assert_eq!(result, "h...");
151    }
152}