1use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum RagError {
9 #[error("Embedding error: {0}")]
11 Embedding(#[from] EmbeddingError),
12
13 #[error("Vector database error: {0}")]
15 VectorDb(#[from] VectorDbError),
16
17 #[error("Indexing error: {0}")]
19 Indexing(#[from] IndexingError),
20
21 #[error("Chunking error: {0}")]
23 Chunking(#[from] ChunkingError),
24
25 #[error("Configuration error: {0}")]
27 Config(#[from] ConfigError),
28
29 #[error("Validation error: {0}")]
31 Validation(#[from] ValidationError),
32
33 #[error("Git error: {0}")]
35 Git(#[from] GitError),
36
37 #[error("Cache error: {0}")]
39 Cache(#[from] CacheError),
40
41 #[error("IO error: {0}")]
43 Io(#[from] std::io::Error),
44
45 #[error("{0}")]
47 Other(String),
48}
49
50#[derive(Error, Debug)]
52pub enum EmbeddingError {
53 #[error("Failed to initialize embedding model: {0}")]
55 InitializationFailed(String),
56
57 #[error("Failed to generate embeddings: {0}")]
59 GenerationFailed(String),
60
61 #[error("Embedding batch is empty")]
63 EmptyBatch,
64
65 #[error("Embedding generation timed out after {0} seconds")]
67 Timeout(u64),
68
69 #[error("Invalid embedding dimension: expected {expected}, got {actual}")]
71 DimensionMismatch {
72 expected: usize,
74 actual: usize,
76 },
77
78 #[error("Model lock was poisoned: {0}")]
80 LockPoisoned(String),
81}
82
83#[derive(Error, Debug)]
85pub enum VectorDbError {
86 #[error("Failed to initialize vector database: {0}")]
88 InitializationFailed(String),
89
90 #[error("Failed to connect to vector database: {0}")]
92 ConnectionFailed(String),
93
94 #[error("Failed to create collection '{collection}': {reason}")]
96 CollectionCreationFailed {
97 collection: String,
99 reason: String,
101 },
102
103 #[error("Collection '{0}' not found")]
105 CollectionNotFound(String),
106
107 #[error("Failed to store embeddings: {0}")]
109 StoreFailed(String),
110
111 #[error("Failed to search embeddings: {0}")]
113 SearchFailed(String),
114
115 #[error("Failed to delete embeddings: {0}")]
117 DeleteFailed(String),
118
119 #[error("Failed to get statistics: {0}")]
121 StatisticsFailed(String),
122
123 #[error("Failed to clear database: {0}")]
125 ClearFailed(String),
126
127 #[error("Invalid search parameters: {0}")]
129 InvalidSearchParams(String),
130
131 #[error("Database is not initialized")]
133 NotInitialized,
134}
135
136#[derive(Error, Debug)]
138pub enum IndexingError {
139 #[error("Directory not found: {0}")]
141 DirectoryNotFound(String),
142
143 #[error("Path is not a directory: {0}")]
145 NotADirectory(String),
146
147 #[error("Failed to walk directory: {0}")]
149 WalkFailed(String),
150
151 #[error("Failed to read file '{file}': {reason}")]
153 FileReadFailed {
154 file: String,
156 reason: String,
158 },
159
160 #[error("File is not valid UTF-8: {0}")]
162 InvalidUtf8(String),
163
164 #[error("File is binary and cannot be indexed: {0}")]
166 BinaryFile(String),
167
168 #[error("File size exceeds maximum: {size} > {max}")]
170 FileTooLarge {
171 size: usize,
173 max: usize,
175 },
176
177 #[error("Failed to calculate file hash: {0}")]
179 HashCalculationFailed(String),
180
181 #[error("No files found to index")]
183 NoFilesFound,
184
185 #[error("Indexing was cancelled")]
187 Cancelled,
188}
189
190#[derive(Error, Debug)]
192pub enum ChunkingError {
193 #[error("Failed to parse code: {0}")]
195 ParseFailed(String),
196
197 #[error("Unsupported language: {0}")]
199 UnsupportedLanguage(String),
200
201 #[error("Invalid chunk size: {0}")]
203 InvalidChunkSize(String),
204
205 #[error("No chunks generated from file: {0}")]
207 NoChunksGenerated(String),
208
209 #[error("AST parsing failed: {0}")]
211 AstParsingFailed(String),
212}
213
214#[derive(Error, Debug)]
216pub enum ConfigError {
217 #[error("Failed to load configuration file: {0}")]
219 LoadFailed(String),
220
221 #[error("Failed to parse configuration: {0}")]
223 ParseFailed(String),
224
225 #[error("Invalid configuration value for '{key}': {reason}")]
227 InvalidValue {
228 key: String,
230 reason: String,
232 },
233
234 #[error("Missing required configuration: {0}")]
236 MissingRequired(String),
237
238 #[error("Failed to save configuration: {0}")]
240 SaveFailed(String),
241
242 #[error("Configuration file not found: {0}")]
244 FileNotFound(String),
245}
246
247#[derive(Error, Debug)]
249pub enum ValidationError {
250 #[error("Path does not exist: {0}")]
252 PathNotFound(String),
253
254 #[error("Path is not absolute: {0}")]
256 PathNotAbsolute(String),
257
258 #[error("Invalid path: {0}")]
260 InvalidPath(String),
261
262 #[error("Invalid project name: {0}")]
264 InvalidProjectName(String),
265
266 #[error("Invalid pattern: {0}")]
268 InvalidPattern(String),
269
270 #[error("{field} must be {constraint}, got {actual}")]
272 ConstraintViolation {
273 field: String,
275 constraint: String,
277 actual: String,
279 },
280
281 #[error("Invalid value for {0}: {1}")]
283 InvalidValue(String, String),
284
285 #[error("Empty {0}")]
287 Empty(String),
288}
289
290#[derive(Error, Debug)]
292pub enum GitError {
293 #[error("Git repository not found at: {0}")]
295 RepoNotFound(String),
296
297 #[error("Failed to open git repository: {0}")]
299 OpenFailed(String),
300
301 #[error("Failed to get git reference: {0}")]
303 RefNotFound(String),
304
305 #[error("Failed to iterate commits: {0}")]
307 IterFailed(String),
308
309 #[error("Invalid commit hash: {0}")]
311 InvalidCommitHash(String),
312
313 #[error("Failed to parse commit: {0}")]
315 ParseFailed(String),
316
317 #[error("Branch not found: {0}")]
319 BranchNotFound(String),
320
321 #[error("No commits found matching criteria")]
323 NoCommitsFound,
324}
325
326#[derive(Error, Debug)]
328pub enum CacheError {
329 #[error("Failed to load cache from '{path}': {reason}")]
331 LoadFailed {
332 path: String,
334 reason: String,
336 },
337
338 #[error("Failed to save cache to '{path}': {reason}")]
340 SaveFailed {
341 path: String,
343 reason: String,
345 },
346
347 #[error("Failed to parse cache file: {0}")]
349 ParseFailed(String),
350
351 #[error("Cache is corrupted: {0}")]
353 Corrupted(String),
354
355 #[error("Failed to create cache directory: {0}")]
357 DirectoryCreationFailed(String),
358}
359
360impl From<anyhow::Error> for RagError {
362 fn from(err: anyhow::Error) -> Self {
363 RagError::Other(format!("{:#}", err))
364 }
365}
366
367impl RagError {
369 pub fn other(msg: impl Into<String>) -> Self {
371 RagError::Other(msg.into())
372 }
373
374 pub fn to_user_string(&self) -> String {
376 format!("{}", self)
377 }
378
379 pub fn is_user_error(&self) -> bool {
381 matches!(
382 self,
383 RagError::Validation(_) | RagError::Config(ConfigError::InvalidValue { .. })
384 )
385 }
386
387 pub fn is_retryable(&self) -> bool {
389 matches!(
390 self,
391 RagError::VectorDb(VectorDbError::ConnectionFailed(_))
392 | RagError::Embedding(EmbeddingError::Timeout(_))
393 | RagError::Io(_)
394 )
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn test_error_display() {
404 let err = RagError::Validation(ValidationError::PathNotFound("/test".to_string()));
405 assert_eq!(
406 err.to_string(),
407 "Validation error: Path does not exist: /test"
408 );
409 }
410
411 #[test]
412 fn test_error_from_io() {
413 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
414 let rag_err: RagError = io_err.into();
415 assert!(matches!(rag_err, RagError::Io(_)));
416 }
417
418 #[test]
419 fn test_error_from_anyhow() {
420 let anyhow_err = anyhow::anyhow!("test error");
421 let rag_err: RagError = anyhow_err.into();
422 assert!(matches!(rag_err, RagError::Other(_)));
423 }
424
425 #[test]
426 fn test_is_user_error() {
427 let user_err = RagError::Validation(ValidationError::InvalidPath("test".to_string()));
428 assert!(user_err.is_user_error());
429
430 let system_err = RagError::Io(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
431 assert!(!system_err.is_user_error());
432 }
433
434 #[test]
435 fn test_is_retryable() {
436 let retryable = RagError::VectorDb(VectorDbError::ConnectionFailed("test".to_string()));
437 assert!(retryable.is_retryable());
438
439 let not_retryable = RagError::Validation(ValidationError::InvalidPath("test".to_string()));
440 assert!(!not_retryable.is_retryable());
441 }
442
443 #[test]
444 fn test_embedding_error_timeout() {
445 let err = EmbeddingError::Timeout(30);
446 assert_eq!(
447 err.to_string(),
448 "Embedding generation timed out after 30 seconds"
449 );
450 }
451
452 #[test]
453 fn test_embedding_error_dimension_mismatch() {
454 let err = EmbeddingError::DimensionMismatch {
455 expected: 384,
456 actual: 512,
457 };
458 assert_eq!(
459 err.to_string(),
460 "Invalid embedding dimension: expected 384, got 512"
461 );
462 }
463
464 #[test]
465 fn test_vector_db_error_collection_creation() {
466 let err = VectorDbError::CollectionCreationFailed {
467 collection: "test_collection".to_string(),
468 reason: "already exists".to_string(),
469 };
470 assert_eq!(
471 err.to_string(),
472 "Failed to create collection 'test_collection': already exists"
473 );
474 }
475
476 #[test]
477 fn test_indexing_error_file_too_large() {
478 let err = IndexingError::FileTooLarge {
479 size: 1000000,
480 max: 500000,
481 };
482 assert_eq!(
483 err.to_string(),
484 "File size exceeds maximum: 1000000 > 500000"
485 );
486 }
487
488 #[test]
489 fn test_validation_error_constraint() {
490 let err = ValidationError::ConstraintViolation {
491 field: "max_file_size".to_string(),
492 constraint: "less than 100MB".to_string(),
493 actual: "200MB".to_string(),
494 };
495 assert_eq!(
496 err.to_string(),
497 "max_file_size must be less than 100MB, got 200MB"
498 );
499 }
500
501 #[test]
502 fn test_config_error_invalid_value() {
503 let err = ConfigError::InvalidValue {
504 key: "port".to_string(),
505 reason: "must be between 1-65535".to_string(),
506 };
507 assert_eq!(
508 err.to_string(),
509 "Invalid configuration value for 'port': must be between 1-65535"
510 );
511 }
512
513 #[test]
514 fn test_cache_error_load_failed() {
515 let err = CacheError::LoadFailed {
516 path: "/tmp/cache.json".to_string(),
517 reason: "permission denied".to_string(),
518 };
519 assert_eq!(
520 err.to_string(),
521 "Failed to load cache from '/tmp/cache.json': permission denied"
522 );
523 }
524
525 #[test]
526 fn test_rag_error_other() {
527 let err = RagError::other("custom error message");
528 assert_eq!(err.to_string(), "custom error message");
529 }
530
531 #[test]
532 fn test_error_chain() {
533 let embedding_err = EmbeddingError::GenerationFailed("model error".to_string());
534 let rag_err: RagError = embedding_err.into();
535 assert!(matches!(rag_err, RagError::Embedding(_)));
536 assert_eq!(
537 rag_err.to_string(),
538 "Embedding error: Failed to generate embeddings: model error"
539 );
540 }
541}