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
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::graph::node::Position;
79
80 fn make_test_span() -> Span {
81 Span::new(Position::new(10, 5), Position::new(10, 25))
82 }
83
84 #[test]
86 fn test_graph_result_ok() {
87 let result: GraphResult<i32> = Ok(42);
88 assert!(result.is_ok());
89 if let Ok(value) = result {
90 assert_eq!(value, 42);
91 }
92 }
93
94 #[test]
95 fn test_graph_result_err() {
96 let result: GraphResult<i32> = Err(GraphBuilderError::ParseError {
97 span: make_test_span(),
98 reason: "test error".to_string(),
99 });
100 assert!(result.is_err());
101 }
102
103 #[test]
105 fn test_parse_error_display() {
106 let err = GraphBuilderError::ParseError {
107 span: make_test_span(),
108 reason: "unexpected token".to_string(),
109 };
110 let msg = format!("{err}");
111 assert!(msg.contains("Failed to parse AST node"));
112 assert!(msg.contains("unexpected token"));
113 }
114
115 #[test]
116 fn test_parse_error_debug() {
117 let err = GraphBuilderError::ParseError {
118 span: make_test_span(),
119 reason: "test".to_string(),
120 };
121 let debug = format!("{err:?}");
122 assert!(debug.contains("ParseError"));
123 assert!(debug.contains("span"));
124 assert!(debug.contains("reason"));
125 }
126
127 #[test]
129 fn test_unsupported_construct_display() {
130 let err = GraphBuilderError::UnsupportedConstruct {
131 construct: "async generator".to_string(),
132 span: make_test_span(),
133 };
134 let msg = format!("{err}");
135 assert!(msg.contains("Unsupported language construct"));
136 assert!(msg.contains("async generator"));
137 }
138
139 #[test]
140 fn test_unsupported_construct_debug() {
141 let err = GraphBuilderError::UnsupportedConstruct {
142 construct: "macro".to_string(),
143 span: make_test_span(),
144 };
145 let debug = format!("{err:?}");
146 assert!(debug.contains("UnsupportedConstruct"));
147 assert!(debug.contains("macro"));
148 }
149
150 #[test]
152 fn test_symbol_resolution_error_display() {
153 let err = GraphBuilderError::SymbolResolutionError {
154 symbol: "MyClass".to_string(),
155 file: PathBuf::from("src/main.rs"),
156 };
157 let msg = format!("{err}");
158 assert!(msg.contains("Failed to resolve symbol"));
159 assert!(msg.contains("MyClass"));
160 assert!(msg.contains("src/main.rs"));
161 }
162
163 #[test]
164 fn test_symbol_resolution_error_debug() {
165 let err = GraphBuilderError::SymbolResolutionError {
166 symbol: "helper_fn".to_string(),
167 file: PathBuf::from("lib.rs"),
168 };
169 let debug = format!("{err:?}");
170 assert!(debug.contains("SymbolResolutionError"));
171 assert!(debug.contains("helper_fn"));
172 }
173
174 #[test]
176 fn test_cross_language_error_display() {
177 let err = GraphBuilderError::CrossLanguageError {
178 reason: "incompatible metadata formats".to_string(),
179 };
180 let msg = format!("{err}");
181 assert!(msg.contains("Invalid cross-language edge"));
182 assert!(msg.contains("incompatible metadata formats"));
183 }
184
185 #[test]
186 fn test_cross_language_error_debug() {
187 let err = GraphBuilderError::CrossLanguageError {
188 reason: "test reason".to_string(),
189 };
190 let debug = format!("{err:?}");
191 assert!(debug.contains("CrossLanguageError"));
192 assert!(debug.contains("test reason"));
193 }
194
195 #[test]
197 fn test_io_error_display() {
198 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
199 let err = GraphBuilderError::IoError {
200 file: PathBuf::from("/tmp/missing.rs"),
201 source: io_err,
202 };
203 let msg = format!("{err}");
204 assert!(msg.contains("IO error reading"));
205 assert!(msg.contains("/tmp/missing.rs"));
206 }
207
208 #[test]
209 fn test_io_error_source() {
210 use std::error::Error;
211
212 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
213 let err = GraphBuilderError::IoError {
214 file: PathBuf::from("/etc/passwd"),
215 source: io_err,
216 };
217
218 let source = err.source();
220 assert!(source.is_some());
221 }
222
223 #[test]
224 fn test_io_error_debug() {
225 let io_err = std::io::Error::other("test");
226 let err = GraphBuilderError::IoError {
227 file: PathBuf::from("test.rs"),
228 source: io_err,
229 };
230 let debug = format!("{err:?}");
231 assert!(debug.contains("IoError"));
232 assert!(debug.contains("test.rs"));
233 }
234
235 #[test]
236 fn test_build_timed_out_display() {
237 let err = GraphBuilderError::BuildTimedOut {
238 file: PathBuf::from("large.cpp"),
239 phase: "walk_tree_for_graph",
240 timeout_ms: 10_000,
241 };
242 let msg = format!("{err}");
243 assert!(msg.contains("Graph build timed out"));
244 assert!(msg.contains("large.cpp"));
245 assert!(msg.contains("walk_tree_for_graph"));
246 }
247
248 #[test]
250 fn test_error_pattern_matching() {
251 let err = GraphBuilderError::ParseError {
252 span: make_test_span(),
253 reason: "test".to_string(),
254 };
255
256 match err {
257 GraphBuilderError::ParseError { span, reason } => {
258 assert_eq!(span.start.line, 10);
259 assert_eq!(reason, "test");
260 }
261 _ => panic!("Expected ParseError variant"),
262 }
263 }
264
265 #[test]
266 fn test_all_variants_are_error() {
267 use std::error::Error;
268
269 let errors: Vec<GraphBuilderError> = vec![
270 GraphBuilderError::ParseError {
271 span: make_test_span(),
272 reason: "test".to_string(),
273 },
274 GraphBuilderError::UnsupportedConstruct {
275 construct: "test".to_string(),
276 span: make_test_span(),
277 },
278 GraphBuilderError::SymbolResolutionError {
279 symbol: "test".to_string(),
280 file: PathBuf::from("test.rs"),
281 },
282 GraphBuilderError::CrossLanguageError {
283 reason: "test".to_string(),
284 },
285 GraphBuilderError::IoError {
286 file: PathBuf::from("test.rs"),
287 source: std::io::Error::other("test"),
288 },
289 GraphBuilderError::BuildTimedOut {
290 file: PathBuf::from("test.rs"),
291 phase: "test-phase",
292 timeout_ms: 1_000,
293 },
294 ];
295
296 for err in errors {
297 let _: &dyn Error = &err;
299 assert!(!format!("{err}").is_empty());
301 }
302 }
303}