use std::io;
use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("home directory could not be determined")]
HomeDirNotFound,
#[error("failed to read {path}: {source}")]
FileRead {
path: PathBuf,
source: io::Error,
},
#[error("failed to write {path}: {source}")]
FileWrite {
path: PathBuf,
source: io::Error,
},
#[error("failed to create directory {path}: {source}")]
DirCreate {
path: PathBuf,
source: io::Error,
},
#[error("failed to create backup of {path}: {source}")]
BackupFailed {
path: PathBuf,
source: io::Error,
},
#[error(
"cannot derive env_dir: {dir} has no parent directory (use .env_dir() to set explicitly)"
)]
EnvDirNotResolvable {
dir: PathBuf,
},
#[error("invalid tool name {name:?}: {reason}")]
InvalidToolName {
name: String,
reason: String,
},
#[error("directory must be an absolute path, got: {dir}")]
RelativePath {
dir: PathBuf,
},
#[error("directory path contains unsafe characters for shell embedding: {dir}")]
UnsafePath {
dir: PathBuf,
},
#[error("directory path is not valid UTF-8: {}", dir.display())]
NonUtf8Path {
dir: PathBuf,
},
#[cfg(unix)]
#[error("failed to acquire lock on {path}: {source}")]
LockFailed {
path: PathBuf,
source: io::Error,
},
#[cfg(unix)]
#[error("timed out waiting for lock on {path} (another process may be holding it)")]
LockTimeout {
path: PathBuf,
},
#[error("no shells detected on this system")]
NoShellsDetected,
#[cfg(windows)]
#[error("Windows registry error: {0}")]
Registry(#[from] RegistryError),
}
#[cfg(windows)]
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum RegistryError {
#[error("failed to open registry key: {0}")]
OpenKey(io::Error),
#[error("failed to read PATH from registry: {0}")]
ReadPath(io::Error),
#[error("failed to write PATH to registry: {0}")]
WritePath(io::Error),
#[error("failed to acquire registry lock: {0}")]
LockFailed(io::Error),
#[error("timed out waiting for registry lock (another process may be holding it)")]
LockTimeout,
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_display_all_variants() {
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
assert_eq!(
Error::HomeDirNotFound.to_string(),
"home directory could not be determined"
);
let env_dir_err = Error::EnvDirNotResolvable {
dir: PathBuf::from("/"),
};
assert!(env_dir_err.to_string().contains("cannot derive env_dir"));
assert_eq!(
Error::NoShellsDetected.to_string(),
"no shells detected on this system"
);
let file_read = Error::FileRead {
path: PathBuf::from("/tmp/test"),
source: io_err,
};
assert!(file_read.to_string().contains("failed to read"));
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
let file_write = Error::FileWrite {
path: PathBuf::from("/tmp/test"),
source: io_err,
};
assert!(file_write.to_string().contains("failed to write"));
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
let dir_create = Error::DirCreate {
path: PathBuf::from("/tmp/dir"),
source: io_err,
};
assert!(dir_create
.to_string()
.contains("failed to create directory"));
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
let backup = Error::BackupFailed {
path: PathBuf::from("/tmp/file"),
source: io_err,
};
assert!(backup.to_string().contains("failed to create backup"));
let invalid_tool = Error::InvalidToolName {
name: "bad name!".to_owned(),
reason: "contains invalid chars".to_owned(),
};
assert!(invalid_tool.to_string().contains("invalid tool name"));
let relative = Error::RelativePath {
dir: PathBuf::from("relative/path"),
};
assert!(relative.to_string().contains("absolute path"));
let unsafe_path = Error::UnsafePath {
dir: PathBuf::from("/path/with\"quote"),
};
assert!(unsafe_path.to_string().contains("unsafe characters"));
let non_utf8 = Error::NonUtf8Path {
dir: PathBuf::from("/some/path"),
};
assert!(non_utf8.to_string().contains("not valid UTF-8"));
#[cfg(unix)]
{
let io_err = io::Error::new(io::ErrorKind::Other, "lock error");
let lock_err = Error::LockFailed {
path: PathBuf::from("/tmp/.bashrc.onpath.lock"),
source: io_err,
};
assert!(lock_err.to_string().contains("failed to acquire lock"));
let timeout_err = Error::LockTimeout {
path: PathBuf::from("/tmp/.bashrc.onpath.lock"),
};
assert!(timeout_err.to_string().contains("timed out"));
}
}
#[cfg(windows)]
#[test]
fn registry_error_display() {
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
let open = RegistryError::OpenKey(io_err);
assert!(open.to_string().contains("failed to open registry key"));
let io_err = io::Error::new(io::ErrorKind::NotFound, "not found");
let read = RegistryError::ReadPath(io_err);
assert!(read.to_string().contains("failed to read PATH"));
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
let write = RegistryError::WritePath(io_err);
assert!(write.to_string().contains("failed to write PATH"));
let io_err = io::Error::new(io::ErrorKind::Other, "mutex failed");
let lock = RegistryError::LockFailed(io_err);
assert!(lock.to_string().contains("failed to acquire registry lock"));
let timeout = RegistryError::LockTimeout;
assert!(timeout.to_string().contains("timed out"));
}
}