pub const DEFAULT_MAX_RESPONSE_BYTES: usize = 100 * 1024;
const TRUNCATION_MARKER: &str = "\n\n[output truncated]";
pub fn truncate_response(text: String, limit: usize) -> (String, bool) {
if limit == 0 || text.len() <= limit {
return (text, false);
}
let mut cutoff = limit;
while cutoff > 0 && !text.is_char_boundary(cutoff) {
cutoff -= 1;
}
let mut truncated = text;
truncated.truncate(cutoff);
truncated.push_str(TRUNCATION_MARKER);
(truncated, true)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn short_text_is_unchanged() {
let (out, truncated) = truncate_response("hello".to_string(), 100);
assert_eq!(out, "hello");
assert!(!truncated);
}
#[test]
fn exact_length_is_unchanged() {
let (out, truncated) = truncate_response("hello".to_string(), 5);
assert_eq!(out, "hello");
assert!(!truncated);
}
#[test]
fn over_limit_is_truncated_with_marker() {
let input = "a".repeat(1000);
let (out, truncated) = truncate_response(input, 100);
assert!(truncated);
assert!(out.len() < 1000);
assert!(out.starts_with(&"a".repeat(100)));
assert!(out.contains("[output truncated]"));
}
#[test]
fn zero_limit_means_no_limit() {
let input = "abc".repeat(1000);
let original_len = input.len();
let (out, truncated) = truncate_response(input, 0);
assert!(!truncated);
assert_eq!(out.len(), original_len);
}
#[test]
fn utf8_boundary_preserved() {
let input: String = "🦀".repeat(50);
let (out, truncated) = truncate_response(input, 10); assert!(truncated);
let body = out.trim_end_matches("[output truncated]");
for ch in body.chars() {
assert!(ch == '🦀' || ch == '\n');
}
}
#[test]
fn default_cap_is_100kb() {
assert_eq!(DEFAULT_MAX_RESPONSE_BYTES, 102_400);
}
#[test]
fn empty_string_is_not_truncated() {
let (out, truncated) = truncate_response(String::new(), 100);
assert_eq!(out, "");
assert!(!truncated);
}
}