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