1pub fn truncate_on_char_boundary(s: &str, max_bytes: usize) -> &str {
10 if s.len() <= max_bytes {
11 return s;
12 }
13 let mut end = max_bytes;
14 while end > 0 && !s.is_char_boundary(end) {
15 end -= 1;
16 }
17 &s[..end]
18}
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23
24 #[test]
25 fn ascii_truncates_exactly() {
26 assert_eq!(truncate_on_char_boundary("hello world", 5), "hello");
27 }
28
29 #[test]
30 fn short_strings_pass_through() {
31 assert_eq!(truncate_on_char_boundary("hi", 100), "hi");
32 assert_eq!(truncate_on_char_boundary("", 5), "");
33 }
34
35 #[test]
36 fn never_splits_a_multibyte_char() {
37 let s = "Привет";
40 let out = truncate_on_char_boundary(s, 5);
41 assert_eq!(out, "Пр");
42 assert!(s.starts_with(out));
43 }
44
45 #[test]
46 fn handles_emoji_boundaries() {
47 let s = "a😀b";
49 assert_eq!(truncate_on_char_boundary(s, 1), "a");
50 assert_eq!(truncate_on_char_boundary(s, 3), "a"); assert_eq!(truncate_on_char_boundary(s, 5), "a😀");
52 assert_eq!(truncate_on_char_boundary(s, 6), "a😀b");
53 }
54
55 #[test]
56 fn cjk_at_a_splitting_offset_does_not_panic() {
57 let s = "记忆".repeat(400); let out = truncate_on_char_boundary(&s, 500);
61 assert!(out.len() <= 500);
62 assert!(s.starts_with(out));
63 assert!(s.is_char_boundary(out.len()));
64 }
65}