use std::fmt::Arguments;
use std::io::{self, Write};
use spec_spine_types::{Error, Verdict};
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Outcome {
Wrote,
ReaderGone,
Failed,
}
pub(crate) fn classify(res: &io::Result<()>) -> Outcome {
match res {
Ok(()) => Outcome::Wrote,
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => Outcome::ReaderGone,
Err(_) => Outcome::Failed,
}
}
pub(crate) fn line(args: Arguments<'_>) {
let stdout = io::stdout();
let mut handle = stdout.lock();
finish(writeln!(handle, "{args}"));
}
pub(crate) fn block(args: Arguments<'_>) {
let stdout = io::stdout();
let mut handle = stdout.lock();
finish(write!(handle, "{args}").and_then(|()| handle.flush()));
}
pub(crate) fn verdict(v: &Verdict) -> Result<(), Error> {
block(format_args!("{}", v.to_canonical_json()?));
Ok(())
}
fn finish(res: io::Result<()>) {
match (classify(&res), res) {
(Outcome::Wrote, _) => {}
(Outcome::ReaderGone, _) => std::process::exit(0),
(Outcome::Failed, Err(e)) => {
eprintln!("spec-spine: cannot write to stdout: {e}");
std::process::exit(3);
}
(Outcome::Failed, Ok(())) => std::process::exit(3),
}
}
#[cfg(test)]
mod tests {
use super::{Outcome, classify};
use std::io::{self, ErrorKind};
#[test]
fn ok_is_wrote() {
assert_eq!(classify(&Ok(())), Outcome::Wrote);
}
#[test]
fn broken_pipe_is_reader_gone() {
let e = io::Error::new(ErrorKind::BrokenPipe, "closed");
assert_eq!(classify(&Err(e)), Outcome::ReaderGone);
}
#[test]
fn other_io_errors_are_failures() {
for kind in [ErrorKind::PermissionDenied, ErrorKind::WriteZero] {
let e = io::Error::new(kind, "x");
assert_eq!(classify(&Err(e)), Outcome::Failed, "{kind:?}");
}
}
}