1use std::path::PathBuf;
9
10use super::node::Span;
11use thiserror::Error;
12
13pub type GraphResult<T> = Result<T, GraphBuilderError>;
15
16#[derive(Debug, Error)]
18pub enum GraphBuilderError {
19 #[error("Failed to parse AST node at {span:?}: {reason}")]
21 ParseError {
22 span: Span,
24 reason: String,
26 },
27
28 #[error("Unsupported language construct: {construct} at {span:?}")]
30 UnsupportedConstruct {
31 construct: String,
33 span: Span,
35 },
36
37 #[error("Failed to resolve symbol '{symbol}' in {file}")]
39 SymbolResolutionError {
40 symbol: String,
42 file: PathBuf,
44 },
45
46 #[error("Invalid cross-language edge: {reason}")]
48 CrossLanguageError {
49 reason: String,
51 },
52
53 #[error("IO error reading {file}: {source}")]
55 IoError {
56 file: PathBuf,
58 #[source]
60 source: std::io::Error,
61 },
62
63 #[error("Graph build timed out after {timeout_ms} ms during {phase} in {file}")]
65 BuildTimedOut {
66 file: PathBuf,
68 phase: &'static str,
70 timeout_ms: u64,
72 },
73
74 #[error("Internal graph builder error: {reason}")]
82 Internal {
83 reason: String,
85 },
86
87 #[error("graph build cancelled")]
103 Cancelled,
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::graph::node::Position;
110
111 fn make_test_span() -> Span {
112 Span::new(Position::new(10, 5), Position::new(10, 25))
113 }
114
115 #[test]
117 fn test_graph_result_ok() {
118 let result: GraphResult<i32> = Ok(42);
119 assert!(result.is_ok());
120 if let Ok(value) = result {
121 assert_eq!(value, 42);
122 }
123 }
124
125 #[test]
126 fn test_graph_result_err() {
127 let result: GraphResult<i32> = Err(GraphBuilderError::ParseError {
128 span: make_test_span(),
129 reason: "test error".to_string(),
130 });
131 assert!(result.is_err());
132 }
133
134 #[test]
136 fn test_parse_error_display() {
137 let err = GraphBuilderError::ParseError {
138 span: make_test_span(),
139 reason: "unexpected token".to_string(),
140 };
141 let msg = format!("{err}");
142 assert!(msg.contains("Failed to parse AST node"));
143 assert!(msg.contains("unexpected token"));
144 }
145
146 #[test]
147 fn test_parse_error_debug() {
148 let err = GraphBuilderError::ParseError {
149 span: make_test_span(),
150 reason: "test".to_string(),
151 };
152 let debug = format!("{err:?}");
153 assert!(debug.contains("ParseError"));
154 assert!(debug.contains("span"));
155 assert!(debug.contains("reason"));
156 }
157
158 #[test]
160 fn test_unsupported_construct_display() {
161 let err = GraphBuilderError::UnsupportedConstruct {
162 construct: "async generator".to_string(),
163 span: make_test_span(),
164 };
165 let msg = format!("{err}");
166 assert!(msg.contains("Unsupported language construct"));
167 assert!(msg.contains("async generator"));
168 }
169
170 #[test]
171 fn test_unsupported_construct_debug() {
172 let err = GraphBuilderError::UnsupportedConstruct {
173 construct: "macro".to_string(),
174 span: make_test_span(),
175 };
176 let debug = format!("{err:?}");
177 assert!(debug.contains("UnsupportedConstruct"));
178 assert!(debug.contains("macro"));
179 }
180
181 #[test]
183 fn test_symbol_resolution_error_display() {
184 let err = GraphBuilderError::SymbolResolutionError {
185 symbol: "MyClass".to_string(),
186 file: PathBuf::from("src/main.rs"),
187 };
188 let msg = format!("{err}");
189 assert!(msg.contains("Failed to resolve symbol"));
190 assert!(msg.contains("MyClass"));
191 assert!(msg.contains("src/main.rs"));
192 }
193
194 #[test]
195 fn test_symbol_resolution_error_debug() {
196 let err = GraphBuilderError::SymbolResolutionError {
197 symbol: "helper_fn".to_string(),
198 file: PathBuf::from("lib.rs"),
199 };
200 let debug = format!("{err:?}");
201 assert!(debug.contains("SymbolResolutionError"));
202 assert!(debug.contains("helper_fn"));
203 }
204
205 #[test]
207 fn test_cross_language_error_display() {
208 let err = GraphBuilderError::CrossLanguageError {
209 reason: "incompatible metadata formats".to_string(),
210 };
211 let msg = format!("{err}");
212 assert!(msg.contains("Invalid cross-language edge"));
213 assert!(msg.contains("incompatible metadata formats"));
214 }
215
216 #[test]
217 fn test_cross_language_error_debug() {
218 let err = GraphBuilderError::CrossLanguageError {
219 reason: "test reason".to_string(),
220 };
221 let debug = format!("{err:?}");
222 assert!(debug.contains("CrossLanguageError"));
223 assert!(debug.contains("test reason"));
224 }
225
226 #[test]
228 fn test_io_error_display() {
229 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
230 let err = GraphBuilderError::IoError {
231 file: PathBuf::from("/tmp/missing.rs"),
232 source: io_err,
233 };
234 let msg = format!("{err}");
235 assert!(msg.contains("IO error reading"));
236 assert!(msg.contains("/tmp/missing.rs"));
237 }
238
239 #[test]
240 fn test_io_error_source() {
241 use std::error::Error;
242
243 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
244 let err = GraphBuilderError::IoError {
245 file: PathBuf::from("/etc/passwd"),
246 source: io_err,
247 };
248
249 let source = err.source();
251 assert!(source.is_some());
252 }
253
254 #[test]
255 fn test_io_error_debug() {
256 let io_err = std::io::Error::other("test");
257 let err = GraphBuilderError::IoError {
258 file: PathBuf::from("test.rs"),
259 source: io_err,
260 };
261 let debug = format!("{err:?}");
262 assert!(debug.contains("IoError"));
263 assert!(debug.contains("test.rs"));
264 }
265
266 #[test]
267 fn test_build_timed_out_display() {
268 let err = GraphBuilderError::BuildTimedOut {
269 file: PathBuf::from("large.cpp"),
270 phase: "walk_tree_for_graph",
271 timeout_ms: 10_000,
272 };
273 let msg = format!("{err}");
274 assert!(msg.contains("Graph build timed out"));
275 assert!(msg.contains("large.cpp"));
276 assert!(msg.contains("walk_tree_for_graph"));
277 }
278
279 #[test]
281 fn test_error_pattern_matching() {
282 let err = GraphBuilderError::ParseError {
283 span: make_test_span(),
284 reason: "test".to_string(),
285 };
286
287 match err {
288 GraphBuilderError::ParseError { span, reason } => {
289 assert_eq!(span.start.line, 10);
290 assert_eq!(reason, "test");
291 }
292 _ => panic!("Expected ParseError variant"),
293 }
294 }
295
296 #[test]
297 fn test_all_variants_are_error() {
298 use std::error::Error;
299
300 let errors: Vec<GraphBuilderError> = vec![
301 GraphBuilderError::ParseError {
302 span: make_test_span(),
303 reason: "test".to_string(),
304 },
305 GraphBuilderError::UnsupportedConstruct {
306 construct: "test".to_string(),
307 span: make_test_span(),
308 },
309 GraphBuilderError::SymbolResolutionError {
310 symbol: "test".to_string(),
311 file: PathBuf::from("test.rs"),
312 },
313 GraphBuilderError::CrossLanguageError {
314 reason: "test".to_string(),
315 },
316 GraphBuilderError::IoError {
317 file: PathBuf::from("test.rs"),
318 source: std::io::Error::other("test"),
319 },
320 GraphBuilderError::BuildTimedOut {
321 file: PathBuf::from("test.rs"),
322 phase: "test-phase",
323 timeout_ms: 1_000,
324 },
325 GraphBuilderError::Internal {
326 reason: "test".to_string(),
327 },
328 GraphBuilderError::Cancelled,
329 ];
330
331 for err in errors {
332 let _: &dyn Error = &err;
334 assert!(!format!("{err}").is_empty());
336 }
337 }
338
339 #[test]
341 fn test_cancelled_display() {
342 let err = GraphBuilderError::Cancelled;
343 let msg = format!("{err}");
344 assert_eq!(msg, "graph build cancelled");
345 }
346
347 #[test]
348 fn test_cancelled_debug() {
349 let err = GraphBuilderError::Cancelled;
350 let debug = format!("{err:?}");
351 assert_eq!(debug, "Cancelled");
352 }
353}