use std::path::PathBuf;
use thiserror::Error;
pub type PluginResult<T> = Result<T, PluginError>;
#[derive(Error, Debug)]
pub enum PluginError {
#[error("no plugin found for extension '{0}'")]
NotFound(String),
#[error("failed to load plugin from {path}: {reason}")]
LoadFailed {
path: PathBuf,
reason: String,
},
#[error("invalid plugin: {0}")]
InvalidPlugin(String),
#[error("AST parsing failed: {0}")]
Parse(#[from] ParseError),
#[error("scope extraction failed: {0}")]
Scope(#[from] ScopeError),
#[error("symbol resolution failed: {0}")]
Resolution(#[from] ResolutionError),
#[error("Type mismatch for field '{field}': expected {expected_type}, got {got_type}")]
TypeMismatch {
field: String,
expected_type: String,
got_type: String,
},
}
#[derive(Error, Debug)]
pub enum ParseError {
#[error("tree-sitter parsing failed")]
TreeSitterFailed,
#[error("failed to set language: {0}")]
LanguageSetFailed(String),
#[error("invalid source code (not UTF-8)")]
InvalidSource,
#[error("input too large: {size} bytes exceeds limit of {max} bytes{}", file.as_ref().map(|f| format!(" (file: {})", f.display())).unwrap_or_default())]
InputTooLarge {
size: usize,
max: usize,
file: Option<PathBuf>,
},
#[error("parse timed out after {} ms{}", timeout_micros / 1000, file.as_ref().map(|f| format!(" (file: {})", f.display())).unwrap_or_default())]
ParseTimedOut {
timeout_micros: u64,
file: Option<PathBuf>,
},
#[error("parse cancelled: {reason}{}", file.as_ref().map(|f| format!(" (file: {})", f.display())).unwrap_or_default())]
ParseCancelled {
reason: String,
file: Option<PathBuf>,
},
#[error("parse error: {0}")]
Other(String),
}
#[derive(Error, Debug)]
pub enum ScopeError {
#[error("failed to compile scope query: {0}")]
QueryCompilationFailed(String),
#[error("failed to extract scopes: {0}")]
ExtractionFailed(String),
#[error("invalid scope structure: {0}")]
InvalidStructure(String),
#[error("scope error: {0}")]
Other(String),
}
#[derive(Error, Debug)]
pub enum ResolutionError {
#[error("symbol '{0}' not found")]
NotFound(String),
#[error("symbol '{0}' is ambiguous (found {1} definitions)")]
Ambiguous(String, usize),
#[error("resolution requires {0}")]
RequiresData(String),
#[error("resolution error: {0}")]
Other(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_error_not_found() {
let err = PluginError::NotFound("rs".to_string());
assert_eq!(err.to_string(), "no plugin found for extension 'rs'");
}
#[test]
fn test_plugin_error_load_failed() {
let err = PluginError::LoadFailed {
path: PathBuf::from("/path/to/plugin.so"),
reason: "symbol not found".to_string(),
};
assert!(err.to_string().contains("failed to load plugin"));
assert!(err.to_string().contains("/path/to/plugin.so"));
}
#[test]
fn test_parse_error_display() {
let err = ParseError::TreeSitterFailed;
assert_eq!(err.to_string(), "tree-sitter parsing failed");
}
#[test]
fn test_scope_error_display() {
let err = ScopeError::ExtractionFailed("invalid node".to_string());
assert!(err.to_string().contains("failed to extract scopes"));
}
#[test]
fn test_resolution_error_ambiguous() {
let err = ResolutionError::Ambiguous("foo".to_string(), 3);
assert!(err.to_string().contains("ambiguous"));
assert!(err.to_string().contains("3 definitions"));
}
#[test]
fn test_parse_error_input_too_large_without_file() {
let err = ParseError::InputTooLarge {
size: 15_000_000,
max: 10_000_000,
file: None,
};
let msg = err.to_string();
assert!(msg.contains("15000000 bytes"));
assert!(msg.contains("10000000 bytes"));
assert!(!msg.contains("file:"));
}
#[test]
fn test_parse_error_input_too_large_with_file() {
let err = ParseError::InputTooLarge {
size: 15_000_000,
max: 10_000_000,
file: Some(PathBuf::from("/path/to/large.rs")),
};
let msg = err.to_string();
assert!(msg.contains("15000000 bytes"));
assert!(msg.contains("10000000 bytes"));
assert!(msg.contains("/path/to/large.rs"));
}
#[test]
fn test_parse_error_timed_out() {
let err = ParseError::ParseTimedOut {
timeout_micros: 2_000_000,
file: Some(PathBuf::from("/path/to/slow.rs")),
};
let msg = err.to_string();
assert!(msg.contains("2000 ms"));
assert!(msg.contains("/path/to/slow.rs"));
}
#[test]
fn test_parse_error_cancelled() {
let err = ParseError::ParseCancelled {
reason: "indexer shutdown".to_string(),
file: Some(PathBuf::from("/path/to/file.rs")),
};
let msg = err.to_string();
assert!(msg.contains("indexer shutdown"));
assert!(msg.contains("/path/to/file.rs"));
}
}