use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SessionError {
#[error("failed to create PTY: {0}")]
PtyCreation(#[source] std::io::Error),
#[error("session limit reached (max: {max})")]
LimitReached {
max: usize,
},
#[error("session not found: {id}")]
NotFound {
id: String,
},
#[error("failed to spawn process: {0}")]
SpawnFailed(#[source] std::io::Error),
#[error("invalid session ID: {id}")]
InvalidSessionId {
id: String,
},
#[error("session already exists: {id}")]
AlreadyExists {
id: String,
},
#[error("worktree not found at: {}", path.display())]
WorktreeNotFound {
path: PathBuf,
},
#[error("worktree manager not configured")]
WorktreeNotConfigured,
#[error("worktree operation failed: {0}")]
Worktree(#[from] WorktreeError),
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("failed to read config file at {path}: {source}")]
ReadFailed {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to parse config: {0}")]
ParseFailed(#[source] toml::de::Error),
#[error("invalid config value: {field} - {message}")]
InvalidValue {
field: String,
message: String,
},
}
#[derive(Debug, Error)]
pub enum WorktreeError {
#[error("git worktree command failed: {0}")]
GitFailed(String),
#[error("invalid worktree path: {0}")]
InvalidPath(PathBuf),
#[error("not a git repository: {0}")]
NotGitRepo(PathBuf),
#[error("branch already exists: {0}")]
BranchExists(String),
#[error("worktree already exists at: {}", path.display())]
WorktreeExists {
path: PathBuf,
},
#[error("worktree not found at: {}", path.display())]
WorktreeNotFound {
path: PathBuf,
},
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
#[derive(Debug, Error)]
pub enum HooksError {
#[error("IPC communication failed: {0}")]
IpcFailed(#[source] std::io::Error),
#[error("failed to generate hooks script: {0}")]
ScriptGenFailed(#[source] std::io::Error),
#[error("failed to parse hook event: {0}")]
ParseFailed(String),
#[error("missing environment variable: {0}")]
MissingEnv(String),
#[error("socket not found: {}", .0.display())]
SocketNotFound(PathBuf),
#[error("socket in use by another tazuna instance: {}", .0.display())]
SocketInUse(PathBuf),
#[error("invalid session ID: {0}")]
InvalidSessionId(String),
}
#[derive(Debug, Error)]
pub enum NotificationError {
#[error("OS notification failed: {0}")]
OsFailed(String),
#[error("webhook request failed: {0}")]
WebhookFailed(#[source] reqwest::Error),
}
#[derive(Debug, Error)]
pub enum GitHubError {
#[error("{command} failed: {source}")]
CommandFailed {
command: String,
#[source]
source: std::io::Error,
},
#[error("failed to parse {context}: {message}")]
ParseFailed {
context: String,
message: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_error_limit_reached_display() {
let err = SessionError::LimitReached { max: 10 };
assert_eq!(err.to_string(), "session limit reached (max: 10)");
}
#[test]
fn session_error_not_found_display() {
let err = SessionError::NotFound {
id: "abc123".to_string(),
};
assert_eq!(err.to_string(), "session not found: abc123");
}
#[test]
fn config_error_invalid_value_display() {
let err = ConfigError::InvalidValue {
field: "max_sessions".to_string(),
message: "must be positive".to_string(),
};
assert!(err.to_string().contains("max_sessions"));
assert!(err.to_string().contains("must be positive"));
}
#[test]
fn worktree_error_git_failed_display() {
let err = WorktreeError::GitFailed("branch already exists".to_string());
assert!(err.to_string().contains("branch already exists"));
}
#[test]
fn worktree_error_invalid_path_display() {
let err = WorktreeError::InvalidPath(PathBuf::from("/invalid/path"));
assert!(err.to_string().contains("/invalid/path"));
}
#[test]
fn worktree_error_not_git_repo_display() {
let err = WorktreeError::NotGitRepo(PathBuf::from("/not/a/repo"));
assert!(err.to_string().contains("not a git repository"));
assert!(err.to_string().contains("/not/a/repo"));
}
#[test]
fn worktree_error_branch_exists_display() {
let err = WorktreeError::BranchExists("tazuna/session-123".to_string());
assert!(err.to_string().contains("branch already exists"));
assert!(err.to_string().contains("tazuna/session-123"));
}
#[test]
fn worktree_error_worktree_exists_display() {
let err = WorktreeError::WorktreeExists {
path: PathBuf::from("/some/worktree"),
};
assert!(err.to_string().contains("worktree already exists"));
assert!(err.to_string().contains("/some/worktree"));
}
#[test]
fn worktree_error_worktree_not_found_display() {
let err = WorktreeError::WorktreeNotFound {
path: PathBuf::from("/some/path"),
};
assert!(err.to_string().contains("worktree not found"));
assert!(err.to_string().contains("/some/path"));
}
#[test]
fn worktree_error_io_display() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err = WorktreeError::Io(io_err);
assert!(err.to_string().contains("IO error"));
}
#[test]
fn notification_error_os_failed_display() {
let err = NotificationError::OsFailed("dbus error".to_string());
assert!(err.to_string().contains("dbus error"));
}
#[test]
fn session_error_invalid_session_id_display() {
let err = SessionError::InvalidSessionId {
id: "not-a-uuid".to_string(),
};
assert!(err.to_string().contains("invalid session ID"));
assert!(err.to_string().contains("not-a-uuid"));
}
#[test]
fn session_error_already_exists_display() {
let err = SessionError::AlreadyExists {
id: "abc-123".to_string(),
};
assert!(err.to_string().contains("session already exists"));
assert!(err.to_string().contains("abc-123"));
}
#[test]
fn session_error_worktree_not_found_display() {
let err = SessionError::WorktreeNotFound {
path: PathBuf::from("/some/path"),
};
assert!(err.to_string().contains("worktree not found"));
assert!(err.to_string().contains("/some/path"));
}
#[test]
fn session_error_worktree_not_configured_display() {
let err = SessionError::WorktreeNotConfigured;
assert!(err.to_string().contains("worktree manager not configured"));
}
#[test]
fn session_error_worktree_display() {
let wt_err = WorktreeError::WorktreeNotFound {
path: PathBuf::from("/test/path"),
};
let err = SessionError::Worktree(wt_err);
assert!(err.to_string().contains("worktree operation failed"));
}
#[test]
fn hooks_error_socket_in_use_display() {
let err = HooksError::SocketInUse(PathBuf::from("/tmp/1234.sock"));
assert!(
err.to_string()
.contains("socket in use by another tazuna instance")
);
assert!(err.to_string().contains("/tmp/1234.sock"));
}
#[test]
fn github_error_command_failed_display() {
let err = GitHubError::CommandFailed {
command: "gh issue list".to_string(),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "not found"),
};
assert!(err.to_string().contains("gh issue list"));
assert!(err.to_string().contains("failed"));
}
#[test]
fn github_error_parse_failed_display() {
let err = GitHubError::ParseFailed {
context: "issue #22".to_string(),
message: "invalid JSON".to_string(),
};
assert!(err.to_string().contains("issue #22"));
assert!(err.to_string().contains("invalid JSON"));
}
}