1use thiserror::Error;
2
3#[derive(Debug, Error)]
5pub enum GriteError {
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 GriteError {
44 pub fn error_code(&self) -> &'static str {
46 match self {
47 GriteError::InvalidArgs(_) => "invalid_args",
48 GriteError::NotFound(_) => "not_found",
49 GriteError::Conflict(_) => "conflict",
50 GriteError::DbBusy(_) => "db_busy",
51 GriteError::Io(_) => "io_error",
52 GriteError::Sled(_) => "db_error",
53 GriteError::Json(_) => "internal_error",
54 GriteError::TomlParse(_) => "invalid_args",
55 GriteError::TomlSerialize(_) => "internal_error",
56 GriteError::IdParse(_) => "invalid_args",
57 GriteError::Internal(_) => "internal_error",
58 GriteError::Ipc(_) => "ipc_error",
59 }
60 }
61
62 pub fn exit_code(&self) -> i32 {
64 match self {
65 GriteError::InvalidArgs(_) => 2,
66 GriteError::NotFound(_) => 3,
67 GriteError::Conflict(_) => 4,
68 GriteError::DbBusy(_) => 5,
69 GriteError::Io(_) => 5,
70 GriteError::Sled(_) => 5,
71 GriteError::IdParse(_) => 2,
72 GriteError::Ipc(_) => 6,
73 _ => 1,
74 }
75 }
76
77 pub fn suggestions(&self) -> Vec<&'static str> {
79 match self {
80 GriteError::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 GriteError::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 GriteError::Sled(_) => vec![
95 "Run 'grit doctor --fix' to rebuild the database",
96 "If problem persists, check disk space and permissions",
97 ],
98 GriteError::Ipc(_) => vec![
99 "Run 'grit daemon stop' and retry",
100 "Or use 'grit --no-daemon <command>' to bypass IPC",
101 ],
102 GriteError::Conflict(_) => vec![
103 "Run 'grit sync' to pull latest changes",
104 ],
105 GriteError::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 GriteError::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 GriteError::DbBusy(msg)
132 }
133}