1 | use std::{env, ffi::OsString, fmt, io, path::PathBuf, process::ExitStatus, string::FromUtf8Error}; |
2 | |
3 | use crate::{Cmd, CmdData}; |
4 | |
5 | /// `Result` from std, with the error type defaulting to xshell's [`Error`]. |
6 | pub type Result<T, E = Error> = std::result::Result<T, E>; |
7 | |
8 | /// An error returned by an `xshell` operation. |
9 | pub struct Error { |
10 | kind: Box<ErrorKind>, |
11 | } |
12 | |
13 | /// Note: this is intentionally not public. |
14 | enum 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 | |
30 | impl From<ErrorKind> for Error { |
31 | fn from(kind: ErrorKind) -> Error { |
32 | let kind: Box = Box::new(kind); |
33 | Error { kind } |
34 | } |
35 | } |
36 | |
37 | impl 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 | |
111 | impl fmt::Debug for Error { |
112 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
113 | fmt::Display::fmt(self, f) |
114 | } |
115 | } |
116 | impl std::error::Error for Error {} |
117 | |
118 | /// `pub(crate)` constructors, visible only in this crate. |
119 | impl 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 ] |
178 | fn error_send_sync() { |
179 | fn f<T: Send + Sync>() {} |
180 | f::<Error>(); |
181 | } |
182 | |