pub fn utf8_prefix_at_or_before(s: &str, max_bytes: usize) -> &str {
if s.len() <= max_bytes {
return s;
}
let mut end = max_bytes;
while !s.is_char_boundary(end) && end > 0 {
end -= 1;
}
&s[..end]
}
#[cfg(test)]
mod tests {
use super::utf8_prefix_at_or_before;
#[test]
fn returns_whole_string_when_under_budget() {
assert_eq!(utf8_prefix_at_or_before("hello", 10), "hello");
}
#[test]
fn returns_whole_string_when_at_budget() {
assert_eq!(utf8_prefix_at_or_before("hello", 5), "hello");
}
#[test]
fn truncates_ascii_at_budget() {
assert_eq!(utf8_prefix_at_or_before("abcdef", 3), "abc");
}
#[test]
fn walks_back_when_cut_lands_inside_multibyte_char() {
let s = format!("{}é", "a".repeat(20));
assert_eq!(utf8_prefix_at_or_before(&s, 21), "a".repeat(20));
}
#[test]
fn returns_empty_when_budget_lands_inside_leading_multibyte() {
let s = "🦀tail";
assert_eq!(utf8_prefix_at_or_before(s, 2), "");
}
#[test]
fn handles_empty_string() {
assert_eq!(utf8_prefix_at_or_before("", 10), "");
assert_eq!(utf8_prefix_at_or_before("", 0), "");
}
#[test]
fn handles_zero_budget() {
assert_eq!(utf8_prefix_at_or_before("abc", 0), "");
}
}