sqry_core/plugin/
error.rs1use std::path::PathBuf;
4use thiserror::Error;
5
6pub type PluginResult<T> = Result<T, PluginError>;
8
9#[derive(Error, Debug)]
11pub enum PluginError {
12 #[error("no plugin found for extension '{0}'")]
14 NotFound(String),
15
16 #[error("failed to load plugin from {path}: {reason}")]
18 LoadFailed {
19 path: PathBuf,
21 reason: String,
23 },
24
25 #[error("invalid plugin: {0}")]
27 InvalidPlugin(String),
28
29 #[error("AST parsing failed: {0}")]
31 Parse(#[from] ParseError),
32
33 #[error("scope extraction failed: {0}")]
35 Scope(#[from] ScopeError),
36
37 #[error("symbol resolution failed: {0}")]
39 Resolution(#[from] ResolutionError),
40
41 #[error("Type mismatch for field '{field}': expected {expected_type}, got {got_type}")]
43 TypeMismatch {
44 field: String,
46 expected_type: String,
48 got_type: String,
50 },
51}
52
53#[derive(Error, Debug)]
55pub enum ParseError {
56 #[error("tree-sitter parsing failed")]
58 TreeSitterFailed,
59
60 #[error("failed to set language: {0}")]
62 LanguageSetFailed(String),
63
64 #[error("invalid source code (not UTF-8)")]
66 InvalidSource,
67
68 #[error("input too large: {size} bytes exceeds limit of {max} bytes{}", file.as_ref().map(|f| format!(" (file: {})", f.display())).unwrap_or_default())]
79 InputTooLarge {
80 size: usize,
82 max: usize,
84 file: Option<PathBuf>,
86 },
87
88 #[error("parse timed out after {} ms{}", timeout_micros / 1000, file.as_ref().map(|f| format!(" (file: {})", f.display())).unwrap_or_default())]
99 ParseTimedOut {
100 timeout_micros: u64,
102 file: Option<PathBuf>,
104 },
105
106 #[error("parse cancelled: {reason}{}", file.as_ref().map(|f| format!(" (file: {})", f.display())).unwrap_or_default())]
111 ParseCancelled {
112 reason: String,
114 file: Option<PathBuf>,
116 },
117
118 #[error("parse error: {0}")]
120 Other(String),
121}
122
123#[derive(Error, Debug)]
125pub enum ScopeError {
126 #[error("failed to compile scope query: {0}")]
128 QueryCompilationFailed(String),
129
130 #[error("failed to extract scopes: {0}")]
132 ExtractionFailed(String),
133
134 #[error("invalid scope structure: {0}")]
136 InvalidStructure(String),
137
138 #[error("scope error: {0}")]
140 Other(String),
141}
142
143#[derive(Error, Debug)]
145pub enum ResolutionError {
146 #[error("symbol '{0}' not found")]
148 NotFound(String),
149
150 #[error("symbol '{0}' is ambiguous (found {1} definitions)")]
152 Ambiguous(String, usize),
153
154 #[error("resolution requires {0}")]
156 RequiresData(String),
157
158 #[error("resolution error: {0}")]
160 Other(String),
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_plugin_error_not_found() {
169 let err = PluginError::NotFound("rs".to_string());
170 assert_eq!(err.to_string(), "no plugin found for extension 'rs'");
171 }
172
173 #[test]
174 fn test_plugin_error_load_failed() {
175 let err = PluginError::LoadFailed {
176 path: PathBuf::from("/path/to/plugin.so"),
177 reason: "symbol not found".to_string(),
178 };
179 assert!(err.to_string().contains("failed to load plugin"));
180 assert!(err.to_string().contains("/path/to/plugin.so"));
181 }
182
183 #[test]
184 fn test_parse_error_display() {
185 let err = ParseError::TreeSitterFailed;
186 assert_eq!(err.to_string(), "tree-sitter parsing failed");
187 }
188
189 #[test]
190 fn test_scope_error_display() {
191 let err = ScopeError::ExtractionFailed("invalid node".to_string());
192 assert!(err.to_string().contains("failed to extract scopes"));
193 }
194
195 #[test]
196 fn test_resolution_error_ambiguous() {
197 let err = ResolutionError::Ambiguous("foo".to_string(), 3);
198 assert!(err.to_string().contains("ambiguous"));
199 assert!(err.to_string().contains("3 definitions"));
200 }
201
202 #[test]
203 fn test_parse_error_input_too_large_without_file() {
204 let err = ParseError::InputTooLarge {
205 size: 15_000_000,
206 max: 10_000_000,
207 file: None,
208 };
209 let msg = err.to_string();
210 assert!(msg.contains("15000000 bytes"));
211 assert!(msg.contains("10000000 bytes"));
212 assert!(!msg.contains("file:"));
213 }
214
215 #[test]
216 fn test_parse_error_input_too_large_with_file() {
217 let err = ParseError::InputTooLarge {
218 size: 15_000_000,
219 max: 10_000_000,
220 file: Some(PathBuf::from("/path/to/large.rs")),
221 };
222 let msg = err.to_string();
223 assert!(msg.contains("15000000 bytes"));
224 assert!(msg.contains("10000000 bytes"));
225 assert!(msg.contains("/path/to/large.rs"));
226 }
227
228 #[test]
229 fn test_parse_error_timed_out() {
230 let err = ParseError::ParseTimedOut {
231 timeout_micros: 2_000_000,
232 file: Some(PathBuf::from("/path/to/slow.rs")),
233 };
234 let msg = err.to_string();
235 assert!(msg.contains("2000 ms"));
236 assert!(msg.contains("/path/to/slow.rs"));
237 }
238
239 #[test]
240 fn test_parse_error_cancelled() {
241 let err = ParseError::ParseCancelled {
242 reason: "indexer shutdown".to_string(),
243 file: Some(PathBuf::from("/path/to/file.rs")),
244 };
245 let msg = err.to_string();
246 assert!(msg.contains("indexer shutdown"));
247 assert!(msg.contains("/path/to/file.rs"));
248 }
249}