use std::{
fmt,
io::{IsTerminal, Stdout, Write, stdout},
};
use anyhow::{Context, Result, bail};
use serde::Serialize;
use serde_json::Value;
use crate::{clap::args::JsonFlag, output::Bytes};
pub trait Printer {
fn out<T: fmt::Display + Serialize>(&mut self, data: T) -> Result<()>;
fn event<T: fmt::Display + Serialize>(&mut self, event: T) -> Result<()>;
fn out_bytes(&mut self, bytes: Bytes) -> Result<()>;
fn is_json(&self) -> bool {
false
}
}
pub struct StdoutPrinter {
stdout: Stdout,
json: bool,
streaming: bool,
}
impl StdoutPrinter {
pub fn new(json: &JsonFlag) -> Self {
Self {
stdout: stdout(),
json: json.enabled,
streaming: false,
}
}
pub fn streaming(mut self) -> Self {
self.streaming = true;
self
}
fn write_json_line<T: Serialize>(&mut self, data: &T) -> Result<()> {
serde_json::to_writer(&mut self.stdout, data).context("Print JSON to stdout error")?;
writeln!(self.stdout)?;
self.stdout.flush()?;
Ok(())
}
}
impl Printer for StdoutPrinter {
fn out<T: fmt::Display + Serialize>(&mut self, data: T) -> Result<()> {
if !self.json {
write!(self.stdout, "{data}")?;
return Ok(());
}
if self.stdout.is_terminal() {
serde_json::to_writer_pretty(&mut self.stdout, &data)
.context("Print pretty JSON to stdout error")?;
writeln!(self.stdout)?;
return Ok(());
}
self.write_json_line(&data)
}
fn event<T: fmt::Display + Serialize>(&mut self, event: T) -> Result<()> {
if self.json {
return self.write_json_line(&event);
}
if self.streaming || self.stdout.is_terminal() {
write!(self.stdout, "{event}")?;
self.stdout.flush()?;
}
Ok(())
}
fn out_bytes(&mut self, bytes: Bytes) -> Result<()> {
if self.json {
return self.out(bytes);
}
self.stdout
.write_all(&bytes.data)
.context("Write bytes to stdout error")?;
self.stdout.flush().context("Flush stdout error")?;
Ok(())
}
fn is_json(&self) -> bool {
self.json
}
}
#[derive(Debug, Default)]
pub struct CapturePrinter {
pub values: Vec<Value>,
pub events: Vec<Value>,
}
impl CapturePrinter {
pub fn into_single(mut self) -> Result<Value> {
if self.values.len() != 1 {
let n = self.values.len();
bail!("The command printed {n} values, one expected");
}
Ok(self.values.remove(0))
}
}
impl Printer for CapturePrinter {
fn out<T: fmt::Display + Serialize>(&mut self, data: T) -> Result<()> {
self.values.push(serde_json::to_value(&data)?);
Ok(())
}
fn event<T: fmt::Display + Serialize>(&mut self, event: T) -> Result<()> {
self.events.push(serde_json::to_value(&event)?);
Ok(())
}
fn out_bytes(&mut self, bytes: Bytes) -> Result<()> {
self.out(bytes)
}
fn is_json(&self) -> bool {
true
}
}
#[cfg(test)]
#[derive(Default)]
pub struct TestPrinter {
pub output: String,
pub events: Vec<String>,
}
#[cfg(test)]
impl Printer for TestPrinter {
fn out<T: fmt::Display + Serialize>(&mut self, data: T) -> Result<()> {
self.output = data.to_string();
Ok(())
}
fn event<T: fmt::Display + Serialize>(&mut self, event: T) -> Result<()> {
self.events.push(event.to_string());
Ok(())
}
fn out_bytes(&mut self, bytes: Bytes) -> Result<()> {
self.output = String::from_utf8_lossy(&bytes.data).into_owned();
Ok(())
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::printer::{CapturePrinter, Printer};
#[test]
fn a_single_value_is_taken_and_events_kept_apart() {
let mut printer = CapturePrinter::default();
printer.event(json!({ "event": "a" })).unwrap();
printer.out(json!({ "id": 1 })).unwrap();
assert_eq!(printer.events, [json!({ "event": "a" })]);
assert_eq!(printer.into_single().unwrap(), json!({ "id": 1 }));
}
#[test]
fn the_count_of_values_is_the_one_printed() {
let none = CapturePrinter::default().into_single().unwrap_err();
assert_eq!(
none.to_string(),
"The command printed 0 values, one expected"
);
let mut printer = CapturePrinter::default();
printer.out(json!(1)).unwrap();
printer.out(json!(2)).unwrap();
let two = printer.into_single().unwrap_err();
assert_eq!(
two.to_string(),
"The command printed 2 values, one expected"
);
}
}