#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContentKind {
Json,
Xml,
Html,
PlainText,
Binary,
Unknown,
}
#[derive(Debug, Clone)]
pub struct TruncationConfig {
pub max_size: usize,
pub suffix: String,
}
impl Default for TruncationConfig {
fn default() -> Self {
Self {
max_size: 262_144, suffix: "... [truncated]".to_string(),
}
}
}
pub fn truncate_body(body: &str, max_size: usize) -> (String, bool) {
if body.len() <= max_size {
return (body.to_string(), false);
}
let mut end = max_size;
while end > 0 && !body.is_char_boundary(end) {
end -= 1;
}
let mut truncated = body[..end].to_string();
truncated.push_str("... [truncated]");
(truncated, true)
}
pub fn content_sniff(bytes: &[u8]) -> ContentKind {
if bytes.is_empty() {
return ContentKind::Unknown;
}
let trimmed = bytes
.iter()
.position(|&b| !b.is_ascii_whitespace())
.map(|i| &bytes[i..])
.unwrap_or(bytes);
if trimmed.is_empty() {
return ContentKind::PlainText;
}
match trimmed[0] {
b'{' | b'[' => ContentKind::Json,
b'<' => {
let prefix = std::str::from_utf8(&trimmed[..trimmed.len().min(100)])
.unwrap_or("")
.to_lowercase();
if prefix.contains("<!doctype html") || prefix.contains("<html") {
ContentKind::Html
} else {
ContentKind::Xml
}
}
_ => {
let is_text = trimmed.iter().take(512).all(|&b| b.is_ascii() || b > 0x7F);
if is_text {
ContentKind::PlainText
} else {
ContentKind::Binary
}
}
}
}
pub fn try_parse_json(body: &str) -> Option<serde_json::Value> {
serde_json::from_str(body).ok()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate_body_within_limit() {
let body = "hello world";
let (result, truncated) = truncate_body(body, 100);
assert_eq!(result, "hello world");
assert!(!truncated);
}
#[test]
fn test_truncate_body_exceeds_limit() {
let body = "hello world, this is a long string";
let (result, truncated) = truncate_body(body, 11);
assert!(result.starts_with("hello world"));
assert!(result.ends_with("... [truncated]"));
assert!(truncated);
}
#[test]
fn test_truncate_body_exact_limit() {
let body = "hello";
let (result, truncated) = truncate_body(body, 5);
assert_eq!(result, "hello");
assert!(!truncated);
}
#[test]
fn test_truncate_body_utf8_boundary() {
let body = "hëllo wörld"; let (_, truncated) = truncate_body(body, 3);
assert!(truncated);
}
#[test]
fn test_content_sniff_json_object() {
assert_eq!(content_sniff(b"{\"key\":\"value\"}"), ContentKind::Json);
}
#[test]
fn test_content_sniff_json_array() {
assert_eq!(content_sniff(b"[1, 2, 3]"), ContentKind::Json);
}
#[test]
fn test_content_sniff_json_with_whitespace() {
assert_eq!(
content_sniff(b" \n {\"key\":\"value\"}"),
ContentKind::Json
);
}
#[test]
fn test_content_sniff_xml() {
assert_eq!(
content_sniff(b"<?xml version=\"1.0\"?><root/>"),
ContentKind::Xml
);
}
#[test]
fn test_content_sniff_html() {
assert_eq!(content_sniff(b"<!DOCTYPE html><html>"), ContentKind::Html);
}
#[test]
fn test_content_sniff_plain_text() {
assert_eq!(
content_sniff(b"Hello, this is plain text"),
ContentKind::PlainText
);
}
#[test]
fn test_content_sniff_empty() {
assert_eq!(content_sniff(b""), ContentKind::Unknown);
}
#[test]
fn test_try_parse_json_valid() {
let result = try_parse_json(r#"{"key":"value"}"#);
assert!(result.is_some());
assert_eq!(result.unwrap()["key"], "value");
}
#[test]
fn test_try_parse_json_invalid() {
assert!(try_parse_json("not json").is_none());
}
#[test]
fn test_try_parse_json_array() {
let result = try_parse_json("[1, 2, 3]");
assert!(result.is_some());
}
}