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 = 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}
182