use std::fmt::Debug;
use std::fmt::Display;
use crate::CommandDisplay;
use crate::DebugDisplay;
use crate::OutputLike;
#[cfg(doc)]
use crate::CommandExt;
#[cfg(doc)]
use crate::ExecError;
#[cfg(feature = "miette")]
use miette::Diagnostic;
pub struct OutputError {
command: Box<dyn CommandDisplay + Send + Sync>,
output: Box<dyn OutputLike + Send + Sync>,
user_error: Option<Box<dyn DebugDisplay + Send + Sync>>,
}
impl OutputError {
pub fn new(
command: Box<dyn CommandDisplay + Send + Sync>,
output: Box<dyn OutputLike + Send + Sync>,
) -> Self {
Self {
command,
output,
user_error: None,
}
}
pub fn with_message(mut self, message: Box<dyn DebugDisplay + Send + Sync>) -> Self {
self.user_error = Some(message);
self
}
pub fn without_message(mut self) -> Self {
self.user_error = None;
self
}
}
impl Debug for OutputError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OutputError")
.field("program", &self.command.program())
.field("status", &self.output.status())
.field("stdout_utf8", &self.output.stdout())
.field("stderr_utf8", &self.output.stderr())
.field("user_error", &self.user_error)
.finish()
}
}
impl Display for OutputError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "`{}` failed: ", self.command.program_quoted())?;
match &self.user_error {
Some(user_error) => {
write!(f, "{user_error}\n{}", self.output.status())?;
}
None => {
write!(f, "{}", self.output.status())?;
}
}
write!(f, "\nCommand failed: `{}`", self.command,)?;
const INDENT: &str = " ";
let stdout = self.output.stdout();
let stdout = stdout.trim();
if !stdout.is_empty() {
writeln!(f, "\nStdout:")?;
write_indented(f, stdout, INDENT)?;
}
let stderr = self.output.stderr();
let stderr = stderr.trim();
if !stderr.is_empty() {
writeln!(f, "\nStderr:")?;
write_indented(f, stderr, INDENT)?;
}
Ok(())
}
}
impl std::error::Error for OutputError {}
#[cfg(feature = "miette")]
impl Diagnostic for OutputError {}
fn write_indented(f: &mut std::fmt::Formatter<'_>, text: &str, indent: &str) -> std::fmt::Result {
let mut lines = text.lines();
if let Some(line) = lines.next() {
write!(f, "{indent}{line}")?;
for line in lines {
write!(f, "\n{indent}{line}")?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use static_assertions::assert_impl_all;
assert_impl_all!(OutputError: Send, Sync);
}