command_error/
output_error.rs1use std::fmt::Debug;
2use std::fmt::Display;
3
4use crate::CommandDisplay;
5use crate::DebugDisplay;
6use crate::OutputLike;
7
8#[cfg(doc)]
9use crate::CommandExt;
10#[cfg(doc)]
11use crate::ExecError;
12#[cfg(feature = "miette")]
13use miette::Diagnostic;
14
15pub struct OutputError {
61 pub(crate) command: Box<dyn CommandDisplay + Send + Sync>,
63 pub(crate) output: Box<dyn OutputLike + Send + Sync>,
65 pub(crate) user_error: Option<Box<dyn DebugDisplay + Send + Sync>>,
67}
68
69impl OutputError {
70 pub fn new(
72 command: Box<dyn CommandDisplay + Send + Sync>,
73 output: Box<dyn OutputLike + Send + Sync>,
74 ) -> Self {
75 Self {
76 command,
77 output,
78 user_error: None,
79 }
80 }
81
82 pub fn with_message(mut self, message: Box<dyn DebugDisplay + Send + Sync>) -> Self {
84 self.user_error = Some(message);
85 self
86 }
87
88 pub fn without_message(mut self) -> Self {
90 self.user_error = None;
91 self
92 }
93}
94
95impl Debug for OutputError {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.debug_struct("OutputError")
98 .field("program", &self.command.program())
99 .field("status", &self.output.status())
100 .field("stdout_utf8", &self.output.stdout())
101 .field("stderr_utf8", &self.output.stderr())
102 .field("user_error", &self.user_error)
103 .finish()
104 }
105}
106
107impl Display for OutputError {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 write!(f, "`{}` failed: ", self.command.program_quoted())?;
110
111 match &self.user_error {
112 Some(user_error) => {
113 write!(f, "{user_error}\n{}", self.output.status())?;
116 }
117 None => {
118 write!(f, "{}", self.output.status())?;
120 }
121 }
122
123 write!(f, "\nCommand failed: `{}`", self.command,)?;
125
126 const INDENT: &str = " ";
127
128 let stdout = self.output.stdout();
129 let stdout = stdout.trim();
130 if !stdout.is_empty() {
131 writeln!(f, "\nStdout:")?;
132 write_indented(f, stdout, INDENT)?;
133 }
134
135 let stderr = self.output.stderr();
141 let stderr = stderr.trim();
142 if !stderr.is_empty() {
143 writeln!(f, "\nStderr:")?;
144 write_indented(f, stderr, INDENT)?;
145 }
146 Ok(())
147 }
148}
149
150impl std::error::Error for OutputError {}
151
152#[cfg(feature = "miette")]
153impl Diagnostic for OutputError {}
154
155fn write_indented(f: &mut std::fmt::Formatter<'_>, text: &str, indent: &str) -> std::fmt::Result {
156 let mut lines = text.lines();
157 if let Some(line) = lines.next() {
158 write!(f, "{indent}{line}")?;
159 for line in lines {
160 write!(f, "\n{indent}{line}")?;
161 }
162 }
163 Ok(())
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use static_assertions::assert_impl_all;
170
171 assert_impl_all!(OutputError: Send, Sync);
172}