xshell/
error.rs

1use std::{env, ffi::OsString, fmt, io, path::PathBuf, process::ExitStatus, string::FromUtf8Error};
2
3use crate::{Cmd, CmdData};
4
5/// `Result` from std, with the error type defaulting to xshell's [`Error`].
6pub type Result<T, E = Error> = std::result::Result<T, E>;
7
8/// An error returned by an `xshell` operation.
9pub struct Error {
10    kind: Box<ErrorKind>,
11}
12
13/// Note: this is intentionally not public.
14enum ErrorKind {
15    CurrentDir { err: io::Error, path: Option<PathBuf> },
16    Var { err: env::VarError, var: OsString },
17    ReadFile { err: io::Error, path: PathBuf },
18    ReadDir { err: io::Error, path: PathBuf },
19    WriteFile { err: io::Error, path: PathBuf },
20    CopyFile { err: io::Error, src: PathBuf, dst: PathBuf },
21    HardLink { err: io::Error, src: PathBuf, dst: PathBuf },
22    CreateDir { err: io::Error, path: PathBuf },
23    RemovePath { err: io::Error, path: PathBuf },
24    CmdStatus { cmd: CmdData, status: ExitStatus },
25    CmdIo { err: io::Error, cmd: CmdData },
26    CmdUtf8 { err: FromUtf8Error, cmd: CmdData },
27    CmdStdin { err: io::Error, cmd: CmdData },
28}
29
30impl From<ErrorKind> for Error {
31    fn from(kind: ErrorKind) -> Error {
32        let kind = Box::new(kind);
33        Error { kind }
34    }
35}
36
37impl fmt::Display for Error {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match &*self.kind {
40            ErrorKind::CurrentDir { err, path } => {
41                let suffix =
42                    path.as_ref().map_or(String::new(), |path| format!(" `{}`", path.display()));
43                write!(f, "failed to get current directory{suffix}: {err}")
44            }
45            ErrorKind::Var { err, var } => {
46                let var = var.to_string_lossy();
47                write!(f, "failed to get environment variable `{var}`: {err}")
48            }
49            ErrorKind::ReadFile { err, path } => {
50                let path = path.display();
51                write!(f, "failed to read file `{path}`: {err}")
52            }
53            ErrorKind::ReadDir { err, path } => {
54                let path = path.display();
55                write!(f, "failed read directory `{path}`: {err}")
56            }
57            ErrorKind::WriteFile { err, path } => {
58                let path = path.display();
59                write!(f, "failed to write file `{path}`: {err}")
60            }
61            ErrorKind::CopyFile { err, src, dst } => {
62                let src = src.display();
63                let dst = dst.display();
64                write!(f, "failed to copy `{src}` to `{dst}`: {err}")
65            }
66            ErrorKind::HardLink { err, src, dst } => {
67                let src = src.display();
68                let dst = dst.display();
69                write!(f, "failed hard link `{src}` to `{dst}`: {err}")
70            }
71            ErrorKind::CreateDir { err, path } => {
72                let path = path.display();
73                write!(f, "failed to create directory `{path}`: {err}")
74            }
75            ErrorKind::RemovePath { err, path } => {
76                let path = path.display();
77                write!(f, "failed to remove path `{path}`: {err}")
78            }
79            ErrorKind::CmdStatus { cmd, status } => match status.code() {
80                Some(code) => write!(f, "command exited with non-zero code `{cmd}`: {code}"),
81                #[cfg(unix)]
82                None => {
83                    use std::os::unix::process::ExitStatusExt;
84                    match status.signal() {
85                        Some(sig) => write!(f, "command was terminated by a signal `{cmd}`: {sig}"),
86                        None => write!(f, "command was terminated by a signal `{cmd}`"),
87                    }
88                }
89                #[cfg(not(unix))]
90                None => write!(f, "command was terminated by a signal `{cmd}`"),
91            },
92            ErrorKind::CmdIo { err, cmd } => {
93                if err.kind() == io::ErrorKind::NotFound {
94                    let prog = cmd.prog.display();
95                    write!(f, "command not found: `{prog}`")
96                } else {
97                    write!(f, "io error when running command `{cmd}`: {err}")
98                }
99            }
100            ErrorKind::CmdUtf8 { err, cmd } => {
101                write!(f, "failed to decode output of command `{cmd}`: {err}")
102            }
103            ErrorKind::CmdStdin { err, cmd } => {
104                write!(f, "failed to write to stdin of command `{cmd}`: {err}")
105            }
106        }?;
107        Ok(())
108    }
109}
110
111impl fmt::Debug for Error {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        fmt::Display::fmt(self, f)
114    }
115}
116impl std::error::Error for Error {}
117
118/// `pub(crate)` constructors, visible only in this crate.
119impl Error {
120    pub(crate) fn new_current_dir(err: io::Error, path: Option<PathBuf>) -> Error {
121        ErrorKind::CurrentDir { err, path }.into()
122    }
123
124    pub(crate) fn new_var(err: env::VarError, var: OsString) -> Error {
125        ErrorKind::Var { err, var }.into()
126    }
127
128    pub(crate) fn new_read_file(err: io::Error, path: PathBuf) -> Error {
129        ErrorKind::ReadFile { err, path }.into()
130    }
131
132    pub(crate) fn new_read_dir(err: io::Error, path: PathBuf) -> Error {
133        ErrorKind::ReadDir { err, path }.into()
134    }
135
136    pub(crate) fn new_write_file(err: io::Error, path: PathBuf) -> Error {
137        ErrorKind::WriteFile { err, path }.into()
138    }
139
140    pub(crate) fn new_copy_file(err: io::Error, src: PathBuf, dst: PathBuf) -> Error {
141        ErrorKind::CopyFile { err, src, dst }.into()
142    }
143
144    pub(crate) fn new_hard_link(err: io::Error, src: PathBuf, dst: PathBuf) -> Error {
145        ErrorKind::HardLink { err, src, dst }.into()
146    }
147
148    pub(crate) fn new_create_dir(err: io::Error, path: PathBuf) -> Error {
149        ErrorKind::CreateDir { err, path }.into()
150    }
151
152    pub(crate) fn new_remove_path(err: io::Error, path: PathBuf) -> Error {
153        ErrorKind::RemovePath { err, path }.into()
154    }
155
156    pub(crate) fn new_cmd_status(cmd: &Cmd<'_>, status: ExitStatus) -> Error {
157        let cmd = cmd.data.clone();
158        ErrorKind::CmdStatus { cmd, status }.into()
159    }
160
161    pub(crate) fn new_cmd_io(cmd: &Cmd<'_>, err: io::Error) -> Error {
162        let cmd = cmd.data.clone();
163        ErrorKind::CmdIo { err, cmd }.into()
164    }
165
166    pub(crate) fn new_cmd_utf8(cmd: &Cmd<'_>, err: FromUtf8Error) -> Error {
167        let cmd = cmd.data.clone();
168        ErrorKind::CmdUtf8 { err, cmd }.into()
169    }
170
171    pub(crate) fn new_cmd_stdin(cmd: &Cmd<'_>, err: io::Error) -> Error {
172        let cmd = cmd.data.clone();
173        ErrorKind::CmdStdin { err, cmd }.into()
174    }
175}
176
177#[test]
178fn error_send_sync() {
179    fn f<T: Send + Sync>() {}
180    f::<Error>();
181}