use crate::{FileId, SymbolId};
use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum IndexError {
#[error("Failed to read file '{path}': {source}")]
FileRead {
path: PathBuf,
source: std::io::Error,
},
#[error("Failed to write file '{path}': {source}")]
FileWrite {
path: PathBuf,
source: std::io::Error,
},
#[error("Failed to parse {language} file '{path}': {reason}")]
ParseError {
path: PathBuf,
language: String,
reason: String,
},
#[error(
"Unsupported file type '{extension}' for file '{path}'. Supported types: .rs, .go, .py, .js, .ts, .java"
)]
UnsupportedFileType { path: PathBuf, extension: String },
#[error("Failed to persist index to '{path}': {source}")]
PersistenceError {
path: PathBuf,
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Failed to load index from '{path}': {source}")]
LoadError {
path: PathBuf,
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Symbol '{name}' not found. Did you mean to index the file first?")]
SymbolNotFound { name: String },
#[error("File ID {id:?} not found in index. The file may have been removed or not indexed.")]
FileNotFound { id: FileId },
#[error("Failed to create file ID: maximum file count reached")]
FileIdExhausted,
#[error("Failed to create symbol ID: maximum symbol count reached")]
SymbolIdExhausted,
#[error("Invalid configuration: {reason}")]
ConfigError { reason: String },
#[error("Tantivy operation failed during {operation}: {cause}")]
TantivyError { operation: String, cause: String },
#[error("Transaction failed after operations: {operations:?}. Cause: {cause}")]
TransactionFailed {
operations: Vec<String>,
cause: String,
},
#[error("Internal mutex was poisoned, likely due to panic in another thread")]
MutexPoisoned,
#[error("Index appears to be corrupted: {reason}")]
IndexCorrupted { reason: String },
#[error("{0}")]
General(String),
#[error("Failed to acquire lock: {0}")]
LockError(String),
#[error("Semantic search is not enabled. Enable it in settings.toml or with --semantic flag")]
SemanticSearchNotEnabled,
#[error("Storage error: {0}")]
Storage(#[from] crate::storage::StorageError),
#[error("Semantic search error: {0}")]
SemanticSearch(#[from] crate::semantic::SemanticSearchError),
#[error("Pipeline error: {0}")]
Pipeline(Box<crate::indexing::pipeline::PipelineError>),
}
impl IndexError {
pub fn lock_error() -> Self {
Self::LockError("mutex poisoned".to_string())
}
}
impl From<std::io::Error> for IndexError {
fn from(err: std::io::Error) -> Self {
IndexError::General(err.to_string())
}
}
impl From<crate::indexing::pipeline::PipelineError> for IndexError {
fn from(err: crate::indexing::pipeline::PipelineError) -> Self {
IndexError::Pipeline(Box::new(err))
}
}
impl IndexError {
pub fn status_code(&self) -> String {
match self {
Self::FileRead { .. } => "FILE_READ_ERROR",
Self::FileWrite { .. } => "FILE_WRITE_ERROR",
Self::ParseError { .. } => "PARSE_ERROR",
Self::UnsupportedFileType { .. } => "UNSUPPORTED_FILE_TYPE",
Self::PersistenceError { .. } => "PERSISTENCE_ERROR",
Self::LoadError { .. } => "LOAD_ERROR",
Self::SymbolNotFound { .. } => "SYMBOL_NOT_FOUND",
Self::FileNotFound { .. } => "FILE_NOT_FOUND",
Self::FileIdExhausted => "FILE_ID_EXHAUSTED",
Self::SymbolIdExhausted => "SYMBOL_ID_EXHAUSTED",
Self::ConfigError { .. } => "CONFIG_ERROR",
Self::TantivyError { .. } => "TANTIVY_ERROR",
Self::TransactionFailed { .. } => "TRANSACTION_FAILED",
Self::MutexPoisoned => "MUTEX_POISONED",
Self::IndexCorrupted { .. } => "INDEX_CORRUPTED",
Self::General(_) => "GENERAL_ERROR",
Self::LockError(_) => "LOCK_ERROR",
Self::SemanticSearchNotEnabled => "SEMANTIC_SEARCH_NOT_ENABLED",
Self::Storage(_) => "STORAGE_ERROR",
Self::SemanticSearch(_) => "SEMANTIC_SEARCH_ERROR",
Self::Pipeline(_) => "PIPELINE_ERROR",
}
.to_string()
}
pub fn recovery_suggestions(&self) -> Vec<&'static str> {
match self {
Self::TantivyError { .. } => vec![
"Try running 'codanna index --force' to rebuild the index",
"Check disk space and permissions in the index directory",
],
Self::TransactionFailed { .. } => vec![
"The operation was rolled back, your index is in a consistent state",
"Try the operation again, it may succeed on retry",
],
Self::MutexPoisoned => vec![
"Restart the application to clear the poisoned state",
"If the problem persists, run 'codanna index --force'",
],
Self::IndexCorrupted { .. } => vec![
"Run 'codanna index --force' to rebuild from scratch",
"Check for disk errors or filesystem corruption",
],
Self::LoadError { .. } | Self::PersistenceError { .. } => vec![
"The index will be loaded from Tantivy on next start",
"Run 'codanna index --force' if you continue to have issues",
],
Self::FileRead { .. } => vec![
"Check that the file exists and you have read permissions",
"Ensure the file is not locked by another process",
],
Self::UnsupportedFileType { .. } => vec![
"Currently only Rust files (.rs) are supported",
"Support for other languages is coming soon",
],
_ => vec![],
}
}
}
#[derive(Error, Debug)]
pub enum ParseError {
#[error("Failed to initialize {language} parser: {reason}")]
ParserInit { language: String, reason: String },
#[error("Failed to parse code at line {line}, column {column}: {reason}")]
SyntaxError {
line: u32,
column: u32,
reason: String,
},
#[error("Invalid UTF-8 in source file")]
InvalidUtf8,
}
#[derive(Error, Debug)]
pub enum StorageError {
#[error("Tantivy index error: {0}")]
TantivyError(#[from] tantivy::TantivyError),
#[error("Database error: {0}")]
DatabaseError(String),
#[error("Document not found for symbol {id:?}")]
DocumentNotFound { id: SymbolId },
}
#[derive(Error, Debug)]
pub enum McpError {
#[error("Failed to initialize MCP server: {reason}")]
ServerInitError { reason: String },
#[error("MCP client error: {reason}")]
ClientError { reason: String },
#[error("Invalid tool arguments: {reason}")]
InvalidArguments { reason: String },
}
pub type IndexResult<T> = Result<T, IndexError>;
pub type ParseResult<T> = Result<T, ParseError>;
pub type StorageResult<T> = Result<T, StorageError>;
pub type McpResult<T> = Result<T, McpError>;
pub trait ErrorContext<T> {
fn context(self, msg: &str) -> Result<T, IndexError>;
fn with_path(self, path: &std::path::Path) -> Result<T, IndexError>;
}
impl<T, E> ErrorContext<T> for Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn context(self, msg: &str) -> Result<T, IndexError> {
self.map_err(|e| IndexError::General(format!("{msg}: {e}")))
}
fn with_path(self, path: &std::path::Path) -> Result<T, IndexError> {
self.map_err(|e| {
IndexError::General(format!("Error processing '{}': {}", path.display(), e))
})
}
}