use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("git binary not found in PATH")]
GitNotFound,
#[error("git version {found} is not supported (minimum: {minimum})")]
UnsupportedVersion {
found: String,
minimum: String,
},
#[error("git command failed: {command}")]
CommandFailed {
command: String,
exit_code: i32,
stdout: String,
stderr: String,
},
#[error("failed to parse git output: {message}")]
ParseError {
message: String,
},
#[error("invalid configuration: {message}")]
InvalidConfig {
message: String,
},
#[error("not a git repository: {path}")]
NotARepository {
path: String,
},
#[error("io error: {message}")]
Io {
message: String,
#[source]
source: std::io::Error,
},
#[error("operation timed out after {timeout_seconds} seconds")]
Timeout {
timeout_seconds: u64,
},
#[error("{message}")]
Custom {
message: String,
},
}
impl Error {
pub fn command_failed(
command: impl Into<String>,
exit_code: i32,
stdout: impl Into<String>,
stderr: impl Into<String>,
) -> Self {
Self::CommandFailed {
command: command.into(),
exit_code,
stdout: stdout.into(),
stderr: stderr.into(),
}
}
pub fn parse_error(message: impl Into<String>) -> Self {
Self::ParseError {
message: message.into(),
}
}
pub fn invalid_config(message: impl Into<String>) -> Self {
Self::InvalidConfig {
message: message.into(),
}
}
pub fn not_a_repository(path: impl Into<String>) -> Self {
Self::NotARepository { path: path.into() }
}
#[must_use]
pub fn timeout(timeout_seconds: u64) -> Self {
Self::Timeout { timeout_seconds }
}
pub fn custom(message: impl Into<String>) -> Self {
Self::Custom {
message: message.into(),
}
}
#[must_use]
pub fn category(&self) -> &'static str {
match self {
Self::GitNotFound | Self::UnsupportedVersion { .. } => "prerequisites",
Self::CommandFailed { .. } | Self::Timeout { .. } => "command",
Self::ParseError { .. } => "parsing",
Self::InvalidConfig { .. } => "config",
Self::NotARepository { .. } => "repository",
Self::Io { .. } => "io",
Self::Custom { .. } => "custom",
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io {
message: err.to_string(),
source: err,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn categories() {
assert_eq!(Error::GitNotFound.category(), "prerequisites");
assert_eq!(
Error::command_failed("git status", 1, "", "").category(),
"command"
);
assert_eq!(Error::parse_error("x").category(), "parsing");
assert_eq!(Error::not_a_repository("/tmp").category(), "repository");
}
#[test]
fn from_io_error() {
let io = std::io::Error::new(std::io::ErrorKind::NotFound, "nope");
let err: Error = io.into();
assert!(matches!(err, Error::Io { .. }));
}
}