Skip to main content

codetether_agent/
util.rs

1//! Shared utility helpers used across modules.
2
3/// Truncates `s` to at most `max_bytes` bytes while respecting UTF-8 char boundaries.
4///
5/// If `s` is shorter than or equal to `max_bytes` bytes it is returned unchanged.
6/// Otherwise the longest prefix that fits within `max_bytes` bytes **and** ends on
7/// a valid UTF-8 boundary is returned, so the result is always a valid `&str`.
8///
9/// This is safe to use for building diagnostic messages from arbitrary API
10/// response bodies, which may contain multi-byte characters like em-dashes.
11pub fn truncate_bytes_safe(s: &str, max_bytes: usize) -> &str {
12    if s.len() <= max_bytes {
13        return s;
14    }
15    let mut end = max_bytes;
16    while end > 0 && !s.is_char_boundary(end) {
17        end -= 1;
18    }
19    &s[..end]
20}
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25
26    #[test]
27    fn ascii_shorter_than_limit() {
28        assert_eq!(truncate_bytes_safe("hello", 10), "hello");
29    }
30
31    #[test]
32    fn ascii_exactly_at_limit() {
33        assert_eq!(truncate_bytes_safe("hello", 5), "hello");
34    }
35
36    #[test]
37    fn ascii_truncated() {
38        assert_eq!(truncate_bytes_safe("hello world", 5), "hello");
39    }
40
41    #[test]
42    fn multibyte_on_boundary() {
43        // em dash U+2014 → 3 bytes (0xE2 0x80 0x94); sits at bytes 3..6
44        let s = "abc\u{2014}def";
45        assert_eq!(truncate_bytes_safe(s, 6), "abc\u{2014}");
46    }
47
48    #[test]
49    fn multibyte_cut_inside_char_does_not_panic() {
50        // Reproduces the crash: byte 300 inside em dash (bytes 299..302)
51        let prefix = "a".repeat(299);
52        let s = format!("{prefix}\u{2014}trailing");
53        // byte index 300 sits inside the em dash
54        let result = truncate_bytes_safe(&s, 300);
55        assert_eq!(result, prefix.as_str());
56    }
57
58    #[test]
59    fn zero_limit_returns_empty() {
60        assert_eq!(truncate_bytes_safe("hello", 0), "");
61    }
62}