use std::string::FromUtf8Error;
pub const fn bounded_read_cap(max: u64) -> u64 {
if max == 0 {
u64::MAX
} else {
max.saturating_add(1)
}
}
pub enum BoundedReadOutcome {
Ok(String),
TooLarge {
size: u64,
},
InvalidUtf8(FromUtf8Error),
}
pub fn check_bounded_utf8(buf: Vec<u8>, max: u64) -> BoundedReadOutcome {
if max != 0 && buf.len() as u64 > max {
return BoundedReadOutcome::TooLarge {
size: buf.len() as u64,
};
}
match String::from_utf8(buf) {
Ok(s) => BoundedReadOutcome::Ok(s),
Err(e) => BoundedReadOutcome::InvalidUtf8(e),
}
}
const TRUNCATION_MARKER: &str = "... (truncated)";
pub const MAX_LOG_STRING_BYTES: usize = 200;
pub fn truncate_str(s: &str, max_bytes: usize) -> String {
if s.len() <= max_bytes {
return s.to_string();
}
let cut = s
.char_indices()
.map(|(i, _)| i)
.take_while(|&i| i <= max_bytes)
.last()
.unwrap_or(0);
format!("{}{TRUNCATION_MARKER}", &s[..cut])
}
pub fn truncate_string(mut s: String, max_bytes: usize) -> String {
if s.len() <= max_bytes {
return s;
}
let cut = s
.char_indices()
.map(|(i, _)| i)
.take_while(|&i| i <= max_bytes)
.last()
.unwrap_or(0);
s.truncate(cut);
s.push_str(TRUNCATION_MARKER);
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bounded_read_cap_is_max_plus_one() {
assert_eq!(bounded_read_cap(100), 101);
assert_eq!(bounded_read_cap(u64::MAX - 1), u64::MAX);
}
#[test]
fn bounded_read_cap_zero_means_unlimited() {
assert_eq!(bounded_read_cap(0), u64::MAX);
}
#[test]
fn check_bounded_utf8_within_limit() {
let outcome = check_bounded_utf8(b"hello".to_vec(), 10);
assert!(matches!(outcome, BoundedReadOutcome::Ok(s) if s == "hello"));
}
#[test]
fn check_bounded_utf8_too_large() {
let outcome = check_bounded_utf8(b"hello".to_vec(), 4);
assert!(matches!(outcome, BoundedReadOutcome::TooLarge { size: 5 }));
}
#[test]
fn check_bounded_utf8_unlimited_when_max_zero() {
let outcome = check_bounded_utf8(b"a".repeat(1000), 0);
assert!(matches!(outcome, BoundedReadOutcome::Ok(s) if s.len() == 1000));
}
#[test]
fn check_bounded_utf8_invalid_utf8_within_limit() {
let outcome = check_bounded_utf8(vec![0xFF, 0xFE], 10);
assert!(matches!(outcome, BoundedReadOutcome::InvalidUtf8(_)));
}
#[test]
fn check_bounded_utf8_reports_oversized_before_invalid_utf8() {
let mut buf = "é".repeat(3).into_bytes();
buf.truncate(5);
let outcome = check_bounded_utf8(buf, 4);
assert!(matches!(outcome, BoundedReadOutcome::TooLarge { size: 5 }));
}
#[test]
fn no_truncation_at_or_below_limit() {
let exact = "a".repeat(10);
assert_eq!(truncate_str(&exact, 10), exact);
assert_eq!(truncate_str("", 10), "");
}
#[test]
fn truncates_just_above_limit() {
let message = "a".repeat(11);
assert_eq!(
truncate_str(&message, 10),
format!("{}... (truncated)", "a".repeat(10))
);
}
#[test]
fn handles_multibyte_char_boundary() {
let message = "é".repeat(10);
let truncated = truncate_str(&message, 5);
assert!(truncated.starts_with(&"é".repeat(2)));
assert!(truncated.ends_with("... (truncated)"));
}
#[test]
fn truncate_string_no_truncation_at_or_below_limit() {
let exact = "a".repeat(10);
assert_eq!(truncate_string(exact.clone(), 10), exact);
assert_eq!(truncate_string(String::new(), 10), "");
}
#[test]
fn truncate_string_truncates_just_above_limit() {
let message = "a".repeat(11);
assert_eq!(
truncate_string(message, 10),
format!("{}... (truncated)", "a".repeat(10))
);
}
#[test]
fn truncate_string_handles_multibyte_char_boundary() {
let message = "é".repeat(10);
let truncated = truncate_string(message, 5);
assert!(truncated.starts_with(&"é".repeat(2)));
assert!(truncated.ends_with("... (truncated)"));
}
#[test]
fn truncate_str_and_truncate_string_agree() {
let message = "x".repeat(500);
assert_eq!(truncate_str(&message, 100), truncate_string(message, 100));
}
}