use std::fmt;
use std::io;
use std::path::PathBuf;
#[derive(Debug)]
pub enum BallError {
Io(io::Error),
Json(serde_json::Error),
Git(String),
TaskNotFound(String),
InvalidTask(String),
NotInitialized,
NotARepo,
AlreadyClaimed(String),
DepsUnmet(String),
NotClaimable(String),
Cycle(String),
WorktreeExists(PathBuf),
Conflict(String),
Other(String),
}
impl fmt::Display for BallError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BallError::Io(e) => write!(f, "io error: {}", e),
BallError::Json(e) => write!(f, "json error: {}", e),
BallError::Git(s) => write!(f, "git error: {}", s),
BallError::TaskNotFound(id) => write!(f, "task not found: {}", id),
BallError::InvalidTask(s) => write!(f, "invalid task: {}", s),
BallError::NotInitialized => {
write!(f, "not initialized. Run `bl init`")
}
BallError::NotARepo => write!(f, "not a git repository"),
BallError::AlreadyClaimed(id) => write!(f, "task {} is already claimed", id),
BallError::DepsUnmet(id) => write!(f, "task {} has unmet dependencies", id),
BallError::NotClaimable(id) => write!(f, "task {} is not claimable", id),
BallError::Cycle(s) => write!(f, "dependency cycle: {}", s),
BallError::WorktreeExists(p) => {
write!(f, "worktree already exists: {} (try `bl drop`)", p.display())
}
BallError::Conflict(s) => write!(f, "conflict: {}", s),
BallError::Other(s) => write!(f, "{}", s),
}
}
}
impl std::error::Error for BallError {}
impl From<io::Error> for BallError {
fn from(e: io::Error) -> Self {
BallError::Io(e)
}
}
impl From<serde_json::Error> for BallError {
fn from(e: serde_json::Error) -> Self {
BallError::Json(e)
}
}
pub type Result<T> = std::result::Result<T, BallError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_all_variants() {
let cases: Vec<BallError> = vec![
BallError::Io(io::Error::other("boom")),
BallError::Json(serde_json::from_str::<i32>("x").unwrap_err()),
BallError::Git("fatal".into()),
BallError::TaskNotFound("bl-x".into()),
BallError::InvalidTask("bad".into()),
BallError::NotInitialized,
BallError::NotARepo,
BallError::AlreadyClaimed("bl-x".into()),
BallError::DepsUnmet("bl-x".into()),
BallError::NotClaimable("bl-x".into()),
BallError::Cycle("loop".into()),
BallError::WorktreeExists(PathBuf::from("/tmp/x")),
BallError::Conflict("merge".into()),
BallError::Other("misc".into()),
];
for e in &cases {
let s = format!("{}", e);
assert!(!s.is_empty());
}
}
#[test]
fn from_io_error() {
let e: BallError = io::Error::other("x").into();
assert!(matches!(e, BallError::Io(_)));
}
#[test]
fn from_json_error() {
let e: BallError = serde_json::from_str::<i32>("oops").unwrap_err().into();
assert!(matches!(e, BallError::Json(_)));
}
#[test]
fn error_is_std_error() {
let e = BallError::NotARepo;
let s: &dyn std::error::Error = &e;
assert!(!s.to_string().is_empty());
}
}