use crate::dsl::schema::TruncationStrategy;
use crate::dsl::state::{OutputType, TaskOutput};
pub fn truncate_output(
content: &str,
max_bytes: usize,
strategy: &TruncationStrategy,
) -> (String, bool) {
if content.len() <= max_bytes {
return (content.to_string(), false);
}
let truncated = match strategy {
TruncationStrategy::Head => truncate_head(content, max_bytes),
TruncationStrategy::Tail => truncate_tail(content, max_bytes),
TruncationStrategy::Both => truncate_both(content, max_bytes),
TruncationStrategy::Summary => {
truncate_tail(content, max_bytes)
}
};
(truncated, true)
}
fn truncate_head(content: &str, max_bytes: usize) -> String {
let safe_len = max_bytes.min(content.len());
let truncated_bytes = content.len() - safe_len;
format!(
"--- Output (showing first {} bytes of {}) ---\n{}\n--- [{} bytes truncated] ---",
safe_len,
content.len(),
&content[..safe_len],
truncated_bytes
)
}
fn truncate_tail(content: &str, max_bytes: usize) -> String {
let safe_len = max_bytes.min(content.len());
let start = content.len() - safe_len;
format!(
"--- [{} bytes truncated] ---\n{}\n--- Output (showing last {} bytes of {}) ---",
start,
&content[start..],
safe_len,
content.len()
)
}
fn truncate_both(content: &str, max_bytes: usize) -> String {
let half = max_bytes / 2;
let safe_half = half.min(content.len());
let end_start = content.len().saturating_sub(safe_half);
let truncated_bytes = content.len().saturating_sub(max_bytes);
format!(
"--- Output (showing first/last {} bytes of {}) ---\n{}\n--- [{} bytes truncated] ---\n{}",
safe_half,
content.len(),
&content[..safe_half],
truncated_bytes,
&content[end_start..]
)
}
pub fn create_task_output(
task_id: String,
output_type: OutputType,
content: String,
max_bytes: usize,
strategy: &TruncationStrategy,
) -> TaskOutput {
let original_size = content.len();
let (truncated_content, was_truncated) = truncate_output(&content, max_bytes, strategy);
TaskOutput::new(
task_id,
output_type,
truncated_content,
original_size,
was_truncated,
strategy.clone(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate_output_no_truncation_needed() {
let content = "short content";
let (result, truncated) = truncate_output(content, 100, &TruncationStrategy::Tail);
assert_eq!(result, content);
assert!(!truncated);
}
#[test]
fn test_truncate_head() {
let content = "0123456789".repeat(10); let (result, truncated) = truncate_output(&content, 50, &TruncationStrategy::Head);
assert!(truncated);
assert!(result.contains("showing first"));
assert!(result.contains("50 bytes truncated"));
}
#[test]
fn test_truncate_tail() {
let content = "0123456789".repeat(10); let (result, truncated) = truncate_output(&content, 50, &TruncationStrategy::Tail);
assert!(truncated);
assert!(result.contains("showing last"));
assert!(result.contains("50 bytes truncated"));
}
#[test]
fn test_truncate_both() {
let content = "0123456789".repeat(10); let (result, truncated) = truncate_output(&content, 50, &TruncationStrategy::Both);
assert!(truncated);
assert!(result.contains("showing first/last"));
assert!(result.contains("bytes truncated"));
}
#[test]
fn test_create_task_output_with_truncation() {
let content = "x".repeat(1000);
let output = create_task_output(
"task1".to_string(),
OutputType::Stdout,
content.clone(),
500,
&TruncationStrategy::Tail,
);
assert_eq!(output.task_id, "task1");
assert_eq!(output.output_type, OutputType::Stdout);
assert_eq!(output.original_size, 1000);
assert!(output.truncated);
assert_eq!(output.strategy, TruncationStrategy::Tail);
}
#[test]
fn test_create_task_output_no_truncation() {
let content = "small output";
let output = create_task_output(
"task1".to_string(),
OutputType::Stdout,
content.to_string(),
1000,
&TruncationStrategy::Tail,
);
assert!(!output.truncated);
assert_eq!(output.content, content);
assert_eq!(output.original_size, content.len());
}
#[test]
fn test_truncate_empty_content() {
let content = "";
let (result, truncated) = truncate_output(content, 100, &TruncationStrategy::Tail);
assert_eq!(result, "");
assert!(!truncated);
}
#[test]
fn test_truncate_exactly_max_bytes() {
let content = "x".repeat(100);
let (result, truncated) = truncate_output(&content, 100, &TruncationStrategy::Tail);
assert_eq!(result, content);
assert!(!truncated);
}
}