| 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 | |