#![warn(missing_docs)]
use std::process::{Command, Output};
use tracing::debug;
use once_cell::sync::Lazy;
static SHOULD_LOG: Lazy<bool> = Lazy::new(|| {
std::env::var("FLUVIO_CMD")
.map(|it| it.eq_ignore_ascii_case("true"))
.unwrap_or(false)
});
pub type CommandResult = Result<Output, CommandError>;
#[derive(thiserror::Error, Debug)]
#[error("Failed to run \"{command}\"")]
pub struct CommandError {
pub command: String,
pub source: CommandErrorKind,
}
#[derive(thiserror::Error, Debug)]
pub enum CommandErrorKind {
#[error("Child process was terminated and has no exit code")]
Terminated,
#[error("\
Child process completed with non-zero exit code {0}
stdout: {}
stderr: {}",
String::from_utf8_lossy(&.1.stdout).to_string(),
String::from_utf8_lossy(&.1.stderr).to_string())]
ExitError(i32, Output),
#[error("An error occurred while invoking child process")]
IoError(#[from] std::io::Error),
}
pub trait CommandExt {
fn inherit(&mut self) -> &mut Self;
fn display(&mut self) -> String;
fn result(&mut self) -> CommandResult;
}
impl CommandExt for Command {
fn inherit(&mut self) -> &mut Self {
use std::process::Stdio;
self.stdout(Stdio::inherit()).stderr(Stdio::inherit())
}
fn display(&mut self) -> String {
format!("{:?}", self).replace("\"", "")
}
fn result(&mut self) -> CommandResult {
if *SHOULD_LOG {
debug!("Executing> {}", self.display());
}
self.output()
.map_err(|e| CommandError {
command: self.display(),
source: CommandErrorKind::IoError(e),
})
.and_then(|output| match output.status.code() {
Some(0i32) => Ok(output),
None => Err(CommandError {
command: self.display(),
source: CommandErrorKind::Terminated,
}),
Some(code) => Err(CommandError {
command: self.display(),
source: CommandErrorKind::ExitError(code, output),
}),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_output_display() {
let error = Command::new("ls")
.arg("does-not-exist")
.result()
.unwrap_err();
let error_display = format!("{}", error.source);
assert!(error_display.starts_with("Child process completed with non-zero exit code"));
assert!(error_display.contains("stdout:"));
assert!(error_display.contains("stderr:"));
}
}