pub fn truncate_on_char_boundary(s: &str, max_bytes: usize) -> &str {
if s.len() <= max_bytes {
return s;
}
let mut end = max_bytes;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
&s[..end]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_truncates_exactly() {
assert_eq!(truncate_on_char_boundary("hello world", 5), "hello");
}
#[test]
fn short_strings_pass_through() {
assert_eq!(truncate_on_char_boundary("hi", 100), "hi");
assert_eq!(truncate_on_char_boundary("", 5), "");
}
#[test]
fn never_splits_a_multibyte_char() {
let s = "Привет";
let out = truncate_on_char_boundary(s, 5);
assert_eq!(out, "Пр");
assert!(s.starts_with(out));
}
#[test]
fn handles_emoji_boundaries() {
let s = "a😀b";
assert_eq!(truncate_on_char_boundary(s, 1), "a");
assert_eq!(truncate_on_char_boundary(s, 3), "a"); assert_eq!(truncate_on_char_boundary(s, 5), "a😀");
assert_eq!(truncate_on_char_boundary(s, 6), "a😀b");
}
#[test]
fn cjk_at_a_splitting_offset_does_not_panic() {
let s = "记忆".repeat(400); let out = truncate_on_char_boundary(&s, 500);
assert!(out.len() <= 500);
assert!(s.starts_with(out));
assert!(s.is_char_boundary(out.len()));
}
}