use serde_json::Value;
#[derive(Debug, Clone, PartialEq)]
pub enum PlainLine {
Empty,
Structured(Value),
Text(String),
}
impl PlainLine {
pub fn render(&self) -> String {
match self {
PlainLine::Empty => String::new(),
PlainLine::Structured(value) => {
serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string())
}
PlainLine::Text(text) => text.clone(),
}
}
pub fn is_empty(&self) -> bool {
matches!(self, PlainLine::Empty)
}
}
pub fn classify_plain_line(line: &str) -> PlainLine {
let trimmed = line.trim();
if trimmed.is_empty() {
return PlainLine::Empty;
}
match serde_json::from_str::<Value>(trimmed) {
Ok(value) if value.is_object() || value.is_array() => PlainLine::Structured(value),
_ => PlainLine::Text(trimmed.to_string()),
}
}
pub fn parse_plain_line(line: &str) -> String {
classify_plain_line(line).render()
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn test_classify_structured_object() {
let classified = classify_plain_line(r#"{"event":"new_email","from":"john"}"#);
assert!(matches!(classified, PlainLine::Structured(_)));
let rendered = classified.render();
assert!(rendered.contains("new_email"));
assert!(rendered.contains("john"));
assert!(rendered.contains('\n')); }
#[test]
fn test_classify_structured_does_not_extract_body_field() {
let classified = classify_plain_line(r#"{"body":"hello world","extra":1}"#);
assert!(matches!(classified, PlainLine::Structured(_)));
let rendered = classified.render();
assert!(rendered.contains("hello world"));
assert!(rendered.contains("extra"));
assert_ne!(rendered, "hello world");
}
#[test]
fn test_classify_plain_text() {
let classified = classify_plain_line("plain text message");
assert_eq!(
classified,
PlainLine::Text("plain text message".to_string())
);
assert_eq!(classified.render(), "plain text message");
}
#[test]
fn test_classify_empty() {
assert_eq!(classify_plain_line(""), PlainLine::Empty);
assert_eq!(classify_plain_line(" "), PlainLine::Empty);
assert!(classify_plain_line("").is_empty());
}
#[test]
fn test_classify_trims_whitespace() {
let classified = classify_plain_line(" hello ");
assert_eq!(classified, PlainLine::Text("hello".to_string()));
}
#[test]
fn test_classify_json_string_literal_is_text() {
let classified = classify_plain_line(r#""just a string""#);
assert_eq!(
classified,
PlainLine::Text(r#""just a string""#.to_string())
);
}
#[test]
fn test_classify_json_array_is_structured() {
let classified = classify_plain_line(r"[1, 2, 3]");
assert!(matches!(classified, PlainLine::Structured(_)));
let rendered = classified.render();
assert!(rendered.contains('1'));
assert!(rendered.contains('2'));
assert!(rendered.contains('3'));
}
#[test]
fn test_classify_malformed_json_is_text() {
let classified = classify_plain_line(r#"{"broken": json"#);
assert_eq!(
classified,
PlainLine::Text(r#"{"broken": json"#.to_string())
);
}
#[test]
fn test_classify_body_scalar_object_is_structured() {
let classified = classify_plain_line(r#"{"body": 42}"#);
assert!(matches!(classified, PlainLine::Structured(_)));
assert!(classified.render().contains("42"));
}
}