//! Single-line JSON output.
//!
//! Ideal for piping into tools like `jq`, `fx`, or a downstream log shipper.
//! Always emits ASCII-escaped strings so the output is safe for byte streams
//! that don't tolerate raw UTF-8 escapes.
use std::io::{self, Write};
use serde::Serialize;
use serde_json::{json, ser::PrettyFormatter, to_writer, Serializer};
#[derive(Debug, Serialize)]
pub struct JsonLine<'a> {
pub origin: &'a str,
pub raw: &'a str,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<(&'a str, &'a str)>,
pub seq: u64,
}
pub struct JsonRenderer<W: Write> {
sink: W,
pretty: bool,
}
impl<W: Write> JsonRenderer<W> {
pub fn new(sink: W, pretty: bool) -> Self {
Self { sink, pretty }
}
pub fn render(&mut self, row: &JsonLine<'_>) -> io::Result<()> {
if self.pretty {
let formatter = PrettyFormatter::with_indent(b" ");
let mut ser = Serializer::with_formatter(&mut self.sink, formatter);
row.serialize(&mut ser).map_err(io_err)?;
} else {
to_writer(&mut self.sink, row).map_err(io_err)?;
}
self.sink.write_all(b"\n")
}
pub fn render_raw(
&mut self,
origin: &str,
line: &str,
fields: &[(String, String)],
seq: u64,
) -> io::Result<()> {
let fields: Vec<(&str, &str)> = fields
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
self.render(&JsonLine {
origin,
raw: line,
fields,
seq,
})
}
pub fn write_error(&mut self, message: &str) -> io::Result<()> {
let payload = json!({ "error": message });
to_writer(&mut self.sink, &payload).map_err(io_err)?;
self.sink.write_all(b"\n")
}
}
fn io_err(e: serde_json::Error) -> io::Error {
io::Error::new(io::ErrorKind::Other, e)
}