1use crate::domains::{
2 CrossReference, DomainObject, DomainValidator, ReferenceType, ReferenceValidationResult,
3};
4use crate::error::BuildError;
5use std::collections::HashMap;
9
10pub struct PythonDomain {
12 objects: HashMap<String, DomainObject>,
14}
15
16impl Default for PythonDomain {
17 fn default() -> Self {
18 Self::new()
19 }
20}
21
22impl PythonDomain {
23 pub fn new() -> Self {
25 Self {
26 objects: HashMap::new(),
27 }
28 }
29
30 pub fn register_function(
32 &mut self,
33 name: String,
34 qualified_name: String,
35 signature: Option<String>,
36 docstring: Option<String>,
37 location: crate::domains::ReferenceLocation,
38 ) -> Result<(), BuildError> {
39 let object = DomainObject {
40 id: format!("py:func:{}", qualified_name),
41 name: name.clone(),
42 object_type: "function".to_string(),
43 domain: "python".to_string(),
44 definition_location: location,
45 qualified_name: qualified_name.clone(),
46 metadata: HashMap::new(),
47 signature,
48 docstring,
49 };
50
51 self.register_object(object)
52 }
53
54 pub fn register_class(
56 &mut self,
57 name: String,
58 qualified_name: String,
59 docstring: Option<String>,
60 location: crate::domains::ReferenceLocation,
61 ) -> Result<(), BuildError> {
62 let object = DomainObject {
63 id: format!("py:class:{}", qualified_name),
64 name: name.clone(),
65 object_type: "class".to_string(),
66 domain: "python".to_string(),
67 definition_location: location,
68 qualified_name: qualified_name.clone(),
69 metadata: HashMap::new(),
70 signature: None,
71 docstring,
72 };
73
74 self.register_object(object)
75 }
76
77 pub fn register_module(
79 &mut self,
80 name: String,
81 qualified_name: String,
82 docstring: Option<String>,
83 location: crate::domains::ReferenceLocation,
84 ) -> Result<(), BuildError> {
85 let object = DomainObject {
86 id: format!("py:mod:{}", qualified_name),
87 name: name.clone(),
88 object_type: "module".to_string(),
89 domain: "python".to_string(),
90 definition_location: location,
91 qualified_name: qualified_name.clone(),
92 metadata: HashMap::new(),
93 signature: None,
94 docstring,
95 };
96
97 self.register_object(object)
98 }
99
100 fn find_suggestions(&self, target: &str) -> Vec<String> {
102 let mut suggestions = Vec::new();
103
104 for obj in self.objects.values() {
106 if obj.name == target
108 || obj.name.contains(target)
109 || target.contains(&obj.name)
110 || obj.qualified_name.contains(target)
111 {
112 suggestions.push(obj.qualified_name.clone());
113 }
114 }
115
116 suggestions.sort();
117 suggestions.dedup();
118 suggestions.truncate(5); suggestions
121 }
122}
123
124impl DomainValidator for PythonDomain {
125 fn domain_name(&self) -> &str {
126 "python"
127 }
128
129 fn supported_reference_types(&self) -> Vec<ReferenceType> {
130 vec![
131 ReferenceType::Function,
132 ReferenceType::Class,
133 ReferenceType::Module,
134 ReferenceType::Method,
135 ReferenceType::Attribute,
136 ReferenceType::Data,
137 ReferenceType::Exception,
138 ]
139 }
140
141 fn register_object(&mut self, object: DomainObject) -> Result<(), BuildError> {
142 let key = object.qualified_name.clone();
143 self.objects.insert(key, object);
144 Ok(())
145 }
146
147 fn resolve_reference(&self, reference: &CrossReference) -> Option<DomainObject> {
148 if let Some(obj) = self.objects.get(&reference.target) {
150 return Some(obj.clone());
151 }
152
153 for obj in self.objects.values() {
155 if obj.name == reference.target {
156 return Some(obj.clone());
157 }
158 }
159
160 None
161 }
162
163 fn validate_reference(&self, reference: &CrossReference) -> ReferenceValidationResult {
164 if let Some(target_object) = self.resolve_reference(reference) {
165 ReferenceValidationResult {
166 reference: reference.clone(),
167 is_valid: true,
168 target_object: Some(target_object),
169 error_message: None,
170 suggestions: Vec::new(),
171 }
172 } else {
173 let suggestions = self.find_suggestions(&reference.target);
174 let error_message = if suggestions.is_empty() {
175 format!("Python object '{}' not found", reference.target)
176 } else {
177 format!(
178 "Python object '{}' not found. Did you mean: {}?",
179 reference.target,
180 suggestions.join(", ")
181 )
182 };
183
184 ReferenceValidationResult {
185 reference: reference.clone(),
186 is_valid: false,
187 target_object: None,
188 error_message: Some(error_message),
189 suggestions,
190 }
191 }
192 }
193
194 fn get_all_objects(&self) -> Vec<&DomainObject> {
195 self.objects.values().collect()
196 }
197
198 fn clear_objects(&mut self) {
199 self.objects.clear();
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::domains::ReferenceLocation;
207
208 fn create_test_location() -> ReferenceLocation {
209 ReferenceLocation {
210 docname: "test.rst".to_string(),
211 lineno: Some(10),
212 column: Some(5),
213 source_path: Some("test.rst".to_string()),
214 }
215 }
216
217 #[test]
218 fn test_python_domain_creation() {
219 let domain = PythonDomain::new();
220 assert_eq!(domain.domain_name(), "python");
221 assert!(domain
222 .supported_reference_types()
223 .contains(&ReferenceType::Function));
224 assert!(domain
225 .supported_reference_types()
226 .contains(&ReferenceType::Class));
227 }
228
229 #[test]
230 fn test_register_function() {
231 let mut domain = PythonDomain::new();
232
233 let result = domain.register_function(
234 "test_func".to_string(),
235 "module.test_func".to_string(),
236 Some("test_func(x, y)".to_string()),
237 Some("Test function".to_string()),
238 create_test_location(),
239 );
240
241 assert!(result.is_ok());
242 assert_eq!(domain.objects.len(), 1);
243
244 let obj = domain.objects.get("module.test_func").unwrap();
245 assert_eq!(obj.name, "test_func");
246 assert_eq!(obj.object_type, "function");
247 assert_eq!(obj.domain, "python");
248 }
249
250 #[test]
251 fn test_reference_resolution() {
252 let mut domain = PythonDomain::new();
253
254 domain
255 .register_function(
256 "example".to_string(),
257 "mymodule.example".to_string(),
258 None,
259 None,
260 create_test_location(),
261 )
262 .unwrap();
263
264 let reference = CrossReference {
265 ref_type: ReferenceType::Function,
266 target: "mymodule.example".to_string(),
267 display_text: None,
268 source_location: create_test_location(),
269 is_external: false,
270 };
271
272 let resolved = domain.resolve_reference(&reference);
273 assert!(resolved.is_some());
274
275 let obj = resolved.unwrap();
276 assert_eq!(obj.qualified_name, "mymodule.example");
277 assert_eq!(obj.object_type, "function");
278 }
279
280 #[test]
281 fn test_reference_validation() {
282 let mut domain = PythonDomain::new();
283
284 domain
285 .register_class(
286 "TestClass".to_string(),
287 "module.TestClass".to_string(),
288 None,
289 create_test_location(),
290 )
291 .unwrap();
292
293 let valid_ref = CrossReference {
295 ref_type: ReferenceType::Class,
296 target: "module.TestClass".to_string(),
297 display_text: None,
298 source_location: create_test_location(),
299 is_external: false,
300 };
301
302 let result = domain.validate_reference(&valid_ref);
303 assert!(result.is_valid);
304 assert!(result.target_object.is_some());
305 assert!(result.error_message.is_none());
306
307 let invalid_ref = CrossReference {
309 ref_type: ReferenceType::Class,
310 target: "nonexistent.Class".to_string(),
311 display_text: None,
312 source_location: create_test_location(),
313 is_external: false,
314 };
315
316 let result = domain.validate_reference(&invalid_ref);
317 assert!(!result.is_valid);
318 assert!(result.target_object.is_none());
319 assert!(result.error_message.is_some());
320 }
321
322 #[test]
323 fn test_suggestions() {
324 let mut domain = PythonDomain::new();
325
326 domain
327 .register_function(
328 "similar_function".to_string(),
329 "module.similar_function".to_string(),
330 None,
331 None,
332 create_test_location(),
333 )
334 .unwrap();
335
336 let reference = CrossReference {
337 ref_type: ReferenceType::Function,
338 target: "similar".to_string(),
339 display_text: None,
340 source_location: create_test_location(),
341 is_external: false,
342 };
343
344 let result = domain.validate_reference(&reference);
345 assert!(!result.is_valid);
346 assert!(!result.suggestions.is_empty());
347 assert!(result
348 .suggestions
349 .contains(&"module.similar_function".to_string()));
350 }
351}