1use std::fmt::{Debug, Display, Formatter};
2use std::process::{ExitStatus, Output};
3
4use thiserror::Error;
5
6use crate::Vec8ToString;
7
8#[derive(Error, Clone, PartialEq, Eq)]
9pub struct CmdError {
10 pub status: Option<ExitStatus>,
11 pub stdout: Vec<u8>,
12 pub stderr: Vec<u8>,
13}
14
15impl Debug for CmdError {
16 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
17 f.debug_struct("CmdError")
18 .field("status", &self.status)
19 .field("stdout", &self.stdout.as_str())
20 .field("stderr", &self.stderr.as_str())
21 .finish()
22 }
23}
24
25impl From<Output> for CmdError {
26 fn from(value: Output) -> Self {
27 CmdError::from_err(value.status, value.stdout, value.stderr)
28 }
29}
30
31impl From<Output> for crate::Error {
32 fn from(value: Output) -> Self {
33 crate::Error::CommandError(value.into())
34 }
35}
36
37impl CmdError {
38 pub fn from_err(status: ExitStatus, stdout: Vec<u8>, stderr: Vec<u8>) -> Self {
39 CmdError {
40 status: Some(status),
41 stdout,
42 stderr,
43 }
44 }
45
46 pub fn from_status(status: ExitStatus) -> Self {
47 CmdError {
48 status: Some(status),
49 stdout: vec![],
50 stderr: vec![],
51 }
52 }
53
54 pub fn from_str(msg: &str) -> Self {
55 CmdError {
56 status: None,
57 stdout: vec![],
58 stderr: msg.to_owned().into_bytes(),
59 }
60 }
61
62 pub fn exit_code(&self) -> Option<i32> {
63 match self.status {
64 Some(s) => s.code(),
65 None => None,
66 }
67 }
68}
69
70impl Display for CmdError {
71 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
72 if let Some(status) = self.status {
73 if let Some(code) = status.code() {
74 let _ = write!(f, "exit code: {}", code);
75 } else {
76 let _ = write!(f, "exit status: {}", self.status.unwrap_or(ExitStatus::default()));
77 }
78 } else {
79 let _ = write!(f, "exit status: {}", self.status.unwrap_or(ExitStatus::default()));
80 }
81
82 if !self.stderr.is_empty() {
83 write!(f, ", stderr: {}", self.stderr.as_str().unwrap_or(""))
84 } else {
85 write!(f, ", stdout: {}", self.stdout.as_str().unwrap_or(""))
86 }
87 }
88}