1use thiserror::Error;
2
3#[derive(Debug, Error)]
5pub enum GritError {
6 #[error("invalid arguments: {0}")]
7 InvalidArgs(String),
8
9 #[error("not found: {0}")]
10 NotFound(String),
11
12 #[error("conflict: {0}")]
13 Conflict(String),
14
15 #[error("database busy: {0}")]
16 DbBusy(String),
17
18 #[error("IO error: {0}")]
19 Io(#[from] std::io::Error),
20
21 #[error("sled error: {0}")]
22 Sled(#[from] sled::Error),
23
24 #[error("JSON error: {0}")]
25 Json(#[from] serde_json::Error),
26
27 #[error("TOML parse error: {0}")]
28 TomlParse(#[from] toml::de::Error),
29
30 #[error("TOML serialize error: {0}")]
31 TomlSerialize(#[from] toml::ser::Error),
32
33 #[error("ID parse error: {0}")]
34 IdParse(#[from] crate::types::ids::IdParseError),
35
36 #[error("internal error: {0}")]
37 Internal(String),
38
39 #[error("IPC error: {0}")]
40 Ipc(String),
41}
42
43impl GritError {
44 pub fn error_code(&self) -> &'static str {
46 match self {
47 GritError::InvalidArgs(_) => "invalid_args",
48 GritError::NotFound(_) => "not_found",
49 GritError::Conflict(_) => "conflict",
50 GritError::DbBusy(_) => "db_busy",
51 GritError::Io(_) => "io_error",
52 GritError::Sled(_) => "db_error",
53 GritError::Json(_) => "internal_error",
54 GritError::TomlParse(_) => "invalid_args",
55 GritError::TomlSerialize(_) => "internal_error",
56 GritError::IdParse(_) => "invalid_args",
57 GritError::Internal(_) => "internal_error",
58 GritError::Ipc(_) => "ipc_error",
59 }
60 }
61
62 pub fn exit_code(&self) -> i32 {
64 match self {
65 GritError::InvalidArgs(_) => 2,
66 GritError::NotFound(_) => 3,
67 GritError::Conflict(_) => 4,
68 GritError::DbBusy(_) => 5,
69 GritError::Io(_) => 5,
70 GritError::Sled(_) => 5,
71 GritError::IdParse(_) => 2,
72 GritError::Ipc(_) => 6,
73 _ => 1,
74 }
75 }
76
77 pub fn suggestions(&self) -> Vec<&'static str> {
79 match self {
80 GritError::NotFound(msg) => {
81 if msg.contains("issue") || msg.starts_with("Issue") {
82 vec!["Run 'grit issue list' to see available issues"]
83 } else if msg.contains("actor") {
84 vec!["Run 'grit actor init' to create an actor"]
85 } else {
86 vec![]
87 }
88 }
89 GritError::DbBusy(_) => vec![
90 "Try 'grit --no-daemon <command>' to bypass the daemon",
91 "Or wait for the other process to finish",
92 "Or run 'grit daemon stop' to stop the daemon",
93 ],
94 GritError::Sled(_) => vec![
95 "Run 'grit doctor --fix' to rebuild the database",
96 "If problem persists, check disk space and permissions",
97 ],
98 GritError::Ipc(_) => vec![
99 "Run 'grit daemon stop' and retry",
100 "Or use 'grit --no-daemon <command>' to bypass IPC",
101 ],
102 GritError::Conflict(_) => vec![
103 "Run 'grit sync' to pull latest changes",
104 ],
105 GritError::IdParse(_) => vec![
106 "IDs should be hex strings (e.g., 'abc123...')",
107 "Use 'grit issue list' to see valid issue IDs",
108 ],
109 _ => vec![],
110 }
111 }
112
113 pub fn issue_not_found(issue_id: &str) -> Self {
115 GritError::NotFound(format!(
116 "Issue '{}' not found",
117 if issue_id.len() > 16 {
118 &issue_id[..16]
119 } else {
120 issue_id
121 }
122 ))
123 }
124
125 pub fn database_locked(details: Option<&str>) -> Self {
127 let msg = match details {
128 Some(d) => format!("Database is locked ({})", d),
129 None => "Database is locked by another process".to_string(),
130 };
131 GritError::DbBusy(msg)
132 }
133}