src/render/mode/json.rs

//! 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)
}