1use thiserror::Error;
8
9#[derive(Error, Debug)]
11pub enum CtxError {
12 #[error("Database error: {0}")]
15 Database(#[from] rusqlite::Error),
16
17 #[cfg(feature = "duckdb")]
19 #[error("Analytics error: {0}")]
20 Analytics(#[from] duckdb::Error),
21
22 #[error("IO error: {0}")]
25 Io(#[from] std::io::Error),
26
27 #[error("Readline error: {0}")]
29 Readline(#[from] rustyline::error::ReadlineError),
30
31 #[error("Serialization error: {0}")]
34 Serialization(#[from] serde_json::Error),
35
36 #[error("Network error: {0}")]
39 Network(#[from] reqwest::Error),
40
41 #[error("Embedding error: {0}")]
44 Embedding(String),
45
46 #[error("Rate limited: retry after {0} seconds")]
48 RateLimited(u64),
49
50 #[error("Invalid API key")]
52 InvalidApiKey,
53
54 #[error("Model not found: {0}")]
56 ModelNotFound(String),
57
58 #[error("Dimension mismatch: expected {expected}, got {actual}")]
60 DimensionMismatch { expected: usize, actual: usize },
61
62 #[error("Git error: {0}")]
65 Git(String),
66
67 #[error("Not a git repository")]
69 NotGitRepo,
70
71 #[error("Invalid revision: {0}")]
73 InvalidRevision(String),
74
75 #[error("No changes found")]
77 NoChanges,
78
79 #[error("No relevant files found")]
82 NoMatches,
83
84 #[error("Index not found: {0}")]
86 #[allow(dead_code)] IndexNotFound(String),
88
89 #[error("Parse error: {0}")]
91 #[allow(dead_code)] ParseError(String),
93
94 #[error("Token counting error: {0}")]
96 #[allow(dead_code)] TokenCount(String),
98
99 #[error("Failed to read file '{path}': {message}")]
101 #[allow(dead_code)] FileRead { path: String, message: String },
103
104 #[error("{0}")]
107 Other(String),
108}
109
110pub type Result<T> = std::result::Result<T, CtxError>;
112
113impl CtxError {
114 pub fn embedding(msg: impl Into<String>) -> Self {
116 CtxError::Embedding(msg.into())
117 }
118
119 pub fn git(msg: impl Into<String>) -> Self {
121 CtxError::Git(msg.into())
122 }
123
124 #[allow(dead_code)] pub fn parse(msg: impl Into<String>) -> Self {
127 CtxError::ParseError(msg.into())
128 }
129
130 #[allow(dead_code)] pub fn token_count(msg: impl Into<String>) -> Self {
133 CtxError::TokenCount(msg.into())
134 }
135
136 #[allow(dead_code)] pub fn file_read(path: impl Into<String>, msg: impl Into<String>) -> Self {
139 CtxError::FileRead {
140 path: path.into(),
141 message: msg.into(),
142 }
143 }
144
145 #[allow(dead_code)] pub fn other(msg: impl Into<String>) -> Self {
148 CtxError::Other(msg.into())
149 }
150}
151
152impl From<String> for CtxError {
155 fn from(s: String) -> Self {
156 CtxError::Other(s)
157 }
158}
159
160impl From<&str> for CtxError {
161 fn from(s: &str) -> Self {
162 CtxError::Other(s.to_string())
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_error_display() {
172 let err = CtxError::Database(rusqlite::Error::InvalidQuery);
173 assert!(err.to_string().contains("Database error"));
174
175 let err = CtxError::DimensionMismatch {
176 expected: 1536,
177 actual: 384,
178 };
179 assert_eq!(
180 err.to_string(),
181 "Dimension mismatch: expected 1536, got 384"
182 );
183
184 let err = CtxError::RateLimited(60);
185 assert_eq!(err.to_string(), "Rate limited: retry after 60 seconds");
186 }
187
188 #[test]
189 fn test_error_constructors() {
190 let err = CtxError::embedding("test error");
191 assert!(matches!(err, CtxError::Embedding(_)));
192
193 let err = CtxError::git("not a repo");
194 assert!(matches!(err, CtxError::Git(_)));
195
196 let err = CtxError::file_read("/path/to/file", "permission denied");
197 assert!(matches!(err, CtxError::FileRead { .. }));
198 }
199
200 #[test]
201 fn test_from_string() {
202 let err: CtxError = "some error".into();
203 assert!(matches!(err, CtxError::Other(_)));
204
205 let err: CtxError = String::from("another error").into();
206 assert!(matches!(err, CtxError::Other(_)));
207 }
208}