Skip to main content

libgrite_core/
error.rs

1use thiserror::Error;
2
3/// Main error type for grit operations
4#[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    /// Get the error code for JSON output (from cli-json.md)
45    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    /// Get the exit code for CLI (from cli-json.md)
63    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    /// Get actionable suggestions for fixing the error
78    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    /// Create a NotFound error for an issue with helpful context
114    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    /// Create a DbBusy error with process info
126    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}