use std::{env, ffi::OsString, fmt, io, path::PathBuf, process::ExitStatus, string::FromUtf8Error};
use crate::{Cmd, CmdData};
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct Error {
kind: Box<ErrorKind>,
}
enum ErrorKind {
CurrentDir { err: io::Error, path: Option<PathBuf> },
Var { err: env::VarError, var: OsString },
ReadFile { err: io::Error, path: PathBuf },
ReadDir { err: io::Error, path: PathBuf },
WriteFile { err: io::Error, path: PathBuf },
CopyFile { err: io::Error, src: PathBuf, dst: PathBuf },
HardLink { err: io::Error, src: PathBuf, dst: PathBuf },
CreateDir { err: io::Error, path: PathBuf },
RemovePath { err: io::Error, path: PathBuf },
CmdStatus { cmd: CmdData, status: ExitStatus },
CmdIo { err: io::Error, cmd: CmdData },
CmdUtf8 { err: FromUtf8Error, cmd: CmdData },
CmdStdin { err: io::Error, cmd: CmdData },
}
impl From<ErrorKind> for Error {
fn from(kind: ErrorKind) -> Error {
let kind = Box::new(kind);
Error { kind }
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &*self.kind {
ErrorKind::CurrentDir { err, path } => {
let suffix =
path.as_ref().map_or(String::new(), |path| format!(" `{}`", path.display()));
write!(f, "failed to get current directory{suffix}: {err}")
}
ErrorKind::Var { err, var } => {
let var = var.to_string_lossy();
write!(f, "failed to get environment variable `{var}`: {err}")
}
ErrorKind::ReadFile { err, path } => {
let path = path.display();
write!(f, "failed to read file `{path}`: {err}")
}
ErrorKind::ReadDir { err, path } => {
let path = path.display();
write!(f, "failed read directory `{path}`: {err}")
}
ErrorKind::WriteFile { err, path } => {
let path = path.display();
write!(f, "failed to write file `{path}`: {err}")
}
ErrorKind::CopyFile { err, src, dst } => {
let src = src.display();
let dst = dst.display();
write!(f, "failed to copy `{src}` to `{dst}`: {err}")
}
ErrorKind::HardLink { err, src, dst } => {
let src = src.display();
let dst = dst.display();
write!(f, "failed hard link `{src}` to `{dst}`: {err}")
}
ErrorKind::CreateDir { err, path } => {
let path = path.display();
write!(f, "failed to create directory `{path}`: {err}")
}
ErrorKind::RemovePath { err, path } => {
let path = path.display();
write!(f, "failed to remove path `{path}`: {err}")
}
ErrorKind::CmdStatus { cmd, status } => match status.code() {
Some(code) => write!(f, "command exited with non-zero code `{cmd}`: {code}"),
#[cfg(unix)]
None => {
use std::os::unix::process::ExitStatusExt;
match status.signal() {
Some(sig) => write!(f, "command was terminated by a signal `{cmd}`: {sig}"),
None => write!(f, "command was terminated by a signal `{cmd}`"),
}
}
#[cfg(not(unix))]
None => write!(f, "command was terminated by a signal `{cmd}`"),
},
ErrorKind::CmdIo { err, cmd } => {
if err.kind() == io::ErrorKind::NotFound {
let prog = cmd.prog.display();
write!(f, "command not found: `{prog}`")
} else {
write!(f, "io error when running command `{cmd}`: {err}")
}
}
ErrorKind::CmdUtf8 { err, cmd } => {
write!(f, "failed to decode output of command `{cmd}`: {err}")
}
ErrorKind::CmdStdin { err, cmd } => {
write!(f, "failed to write to stdin of command `{cmd}`: {err}")
}
}?;
Ok(())
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl std::error::Error for Error {}
impl Error {
pub(crate) fn new_current_dir(err: io::Error, path: Option<PathBuf>) -> Error {
ErrorKind::CurrentDir { err, path }.into()
}
pub(crate) fn new_var(err: env::VarError, var: OsString) -> Error {
ErrorKind::Var { err, var }.into()
}
pub(crate) fn new_read_file(err: io::Error, path: PathBuf) -> Error {
ErrorKind::ReadFile { err, path }.into()
}
pub(crate) fn new_read_dir(err: io::Error, path: PathBuf) -> Error {
ErrorKind::ReadDir { err, path }.into()
}
pub(crate) fn new_write_file(err: io::Error, path: PathBuf) -> Error {
ErrorKind::WriteFile { err, path }.into()
}
pub(crate) fn new_copy_file(err: io::Error, src: PathBuf, dst: PathBuf) -> Error {
ErrorKind::CopyFile { err, src, dst }.into()
}
pub(crate) fn new_hard_link(err: io::Error, src: PathBuf, dst: PathBuf) -> Error {
ErrorKind::HardLink { err, src, dst }.into()
}
pub(crate) fn new_create_dir(err: io::Error, path: PathBuf) -> Error {
ErrorKind::CreateDir { err, path }.into()
}
pub(crate) fn new_remove_path(err: io::Error, path: PathBuf) -> Error {
ErrorKind::RemovePath { err, path }.into()
}
pub(crate) fn new_cmd_status(cmd: &Cmd<'_>, status: ExitStatus) -> Error {
let cmd = cmd.data.clone();
ErrorKind::CmdStatus { cmd, status }.into()
}
pub(crate) fn new_cmd_io(cmd: &Cmd<'_>, err: io::Error) -> Error {
let cmd = cmd.data.clone();
ErrorKind::CmdIo { err, cmd }.into()
}
pub(crate) fn new_cmd_utf8(cmd: &Cmd<'_>, err: FromUtf8Error) -> Error {
let cmd = cmd.data.clone();
ErrorKind::CmdUtf8 { err, cmd }.into()
}
pub(crate) fn new_cmd_stdin(cmd: &Cmd<'_>, err: io::Error) -> Error {
let cmd = cmd.data.clone();
ErrorKind::CmdStdin { err, cmd }.into()
}
}
#[test]
fn error_send_sync() {
fn f<T: Send + Sync>() {}
f::<Error>();
}