use thiserror::Error;
#[derive(Debug, Error)]
pub enum GritError {
#[error("invalid arguments: {0}")]
InvalidArgs(String),
#[error("not found: {0}")]
NotFound(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("database busy: {0}")]
DbBusy(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("sled error: {0}")]
Sled(#[from] sled::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("TOML parse error: {0}")]
TomlParse(#[from] toml::de::Error),
#[error("TOML serialize error: {0}")]
TomlSerialize(#[from] toml::ser::Error),
#[error("ID parse error: {0}")]
IdParse(#[from] crate::types::ids::IdParseError),
#[error("internal error: {0}")]
Internal(String),
#[error("IPC error: {0}")]
Ipc(String),
}
impl GritError {
pub fn error_code(&self) -> &'static str {
match self {
GritError::InvalidArgs(_) => "invalid_args",
GritError::NotFound(_) => "not_found",
GritError::Conflict(_) => "conflict",
GritError::DbBusy(_) => "db_busy",
GritError::Io(_) => "io_error",
GritError::Sled(_) => "db_error",
GritError::Json(_) => "internal_error",
GritError::TomlParse(_) => "invalid_args",
GritError::TomlSerialize(_) => "internal_error",
GritError::IdParse(_) => "invalid_args",
GritError::Internal(_) => "internal_error",
GritError::Ipc(_) => "ipc_error",
}
}
pub fn exit_code(&self) -> i32 {
match self {
GritError::InvalidArgs(_) => 2,
GritError::NotFound(_) => 3,
GritError::Conflict(_) => 4,
GritError::DbBusy(_) => 5,
GritError::Io(_) => 5,
GritError::Sled(_) => 5,
GritError::IdParse(_) => 2,
GritError::Ipc(_) => 6,
_ => 1,
}
}
pub fn suggestions(&self) -> Vec<&'static str> {
match self {
GritError::NotFound(msg) => {
if msg.contains("issue") || msg.starts_with("Issue") {
vec!["Run 'grit issue list' to see available issues"]
} else if msg.contains("actor") {
vec!["Run 'grit actor init' to create an actor"]
} else {
vec![]
}
}
GritError::DbBusy(_) => vec![
"Try 'grit --no-daemon <command>' to bypass the daemon",
"Or wait for the other process to finish",
"Or run 'grit daemon stop' to stop the daemon",
],
GritError::Sled(_) => vec![
"Run 'grit doctor --fix' to rebuild the database",
"If problem persists, check disk space and permissions",
],
GritError::Ipc(_) => vec![
"Run 'grit daemon stop' and retry",
"Or use 'grit --no-daemon <command>' to bypass IPC",
],
GritError::Conflict(_) => vec![
"Run 'grit sync' to pull latest changes",
],
GritError::IdParse(_) => vec![
"IDs should be hex strings (e.g., 'abc123...')",
"Use 'grit issue list' to see valid issue IDs",
],
_ => vec![],
}
}
pub fn issue_not_found(issue_id: &str) -> Self {
GritError::NotFound(format!(
"Issue '{}' not found",
if issue_id.len() > 16 {
&issue_id[..16]
} else {
issue_id
}
))
}
pub fn database_locked(details: Option<&str>) -> Self {
let msg = match details {
Some(d) => format!("Database is locked ({})", d),
None => "Database is locked by another process".to_string(),
};
GritError::DbBusy(msg)
}
}