use std::io::{self, Write};
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Plain,
Json,
Ndjson,
}
pub fn resolve_format(json: bool, ndjson: bool) -> OutputFormat {
if ndjson {
OutputFormat::Ndjson
} else if json {
OutputFormat::Json
} else {
OutputFormat::Plain
}
}
pub struct NdjsonWriter<W: Write> {
writer: W,
}
impl<W: Write> NdjsonWriter<W> {
pub fn new(writer: W) -> Self {
Self { writer }
}
pub fn emit<T: Serialize>(&mut self, item: &T) -> io::Result<()> {
serde_json::to_writer(&mut self.writer, item)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
self.writer.write_all(b"\n")?;
self.writer.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(serde::Serialize)]
struct TestRecord {
id: u32,
value: String,
}
#[test]
fn ndjson_writer_round_trip() {
let mut buf = Vec::new();
let mut writer = NdjsonWriter::new(&mut buf);
writer
.emit(&TestRecord {
id: 1,
value: "hello".into(),
})
.expect("emit 1");
writer
.emit(&TestRecord {
id: 2,
value: "world".into(),
})
.expect("emit 2");
let text = String::from_utf8(buf).expect("utf8");
let lines: Vec<&str> = text.trim_end().split('\n').collect();
assert_eq!(lines.len(), 2);
let v1: serde_json::Value = serde_json::from_str(lines[0]).expect("json line 1");
assert_eq!(v1["id"], 1);
let v2: serde_json::Value = serde_json::from_str(lines[1]).expect("json line 2");
assert_eq!(v2["id"], 2);
}
#[test]
fn resolve_format_ndjson_wins() {
assert_eq!(resolve_format(true, true), OutputFormat::Ndjson);
assert_eq!(resolve_format(false, true), OutputFormat::Ndjson);
assert_eq!(resolve_format(true, false), OutputFormat::Json);
assert_eq!(resolve_format(false, false), OutputFormat::Plain);
}
}