1pub mod parser;
2pub mod python;
11pub mod rst;
12
13use crate::error::BuildError;
14use std::collections::HashMap;
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum ReferenceType {
19 Document,
21 Section,
23 Function,
25 Class,
27 Module,
29 Method,
31 Attribute,
33 Data,
35 Exception,
37 Custom(String),
39}
40
41impl std::fmt::Display for ReferenceType {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 ReferenceType::Document => write!(f, "Document"),
45 ReferenceType::Section => write!(f, "Section"),
46 ReferenceType::Function => write!(f, "Function"),
47 ReferenceType::Class => write!(f, "Class"),
48 ReferenceType::Module => write!(f, "Module"),
49 ReferenceType::Method => write!(f, "Method"),
50 ReferenceType::Attribute => write!(f, "Attribute"),
51 ReferenceType::Data => write!(f, "Data"),
52 ReferenceType::Exception => write!(f, "Exception"),
53 ReferenceType::Custom(name) => write!(f, "{}", name),
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct CrossReference {
61 pub ref_type: ReferenceType,
63 pub target: String,
65 pub display_text: Option<String>,
67 pub source_location: ReferenceLocation,
69 pub is_external: bool,
71}
72
73#[derive(Debug, Clone)]
75pub struct ReferenceLocation {
76 pub docname: String,
78 pub lineno: Option<usize>,
80 pub column: Option<usize>,
82 pub source_path: Option<String>,
84}
85
86#[derive(Debug, Clone)]
88pub struct DomainObject {
89 pub id: String,
91 pub name: String,
93 pub object_type: String,
95 pub domain: String,
97 pub definition_location: ReferenceLocation,
99 pub qualified_name: String,
101 pub metadata: HashMap<String, String>,
103 pub signature: Option<String>,
105 pub docstring: Option<String>,
107}
108
109#[derive(Debug, Clone)]
111pub struct ReferenceValidationResult {
112 pub reference: CrossReference,
114 pub is_valid: bool,
116 pub target_object: Option<DomainObject>,
118 pub error_message: Option<String>,
120 pub suggestions: Vec<String>,
122}
123
124#[derive(Debug, Default)]
126pub struct DomainValidationStats {
127 pub total_references: usize,
129 pub valid_references: usize,
131 pub broken_references: usize,
133 pub external_references: usize,
135 pub references_by_type: HashMap<ReferenceType, usize>,
137}
138
139pub trait DomainValidator {
141 fn domain_name(&self) -> &str;
143
144 fn supported_reference_types(&self) -> Vec<ReferenceType>;
146
147 fn register_object(&mut self, object: DomainObject) -> Result<(), BuildError>;
149
150 fn resolve_reference(&self, reference: &CrossReference) -> Option<DomainObject>;
152
153 fn validate_reference(&self, reference: &CrossReference) -> ReferenceValidationResult;
155
156 fn get_all_objects(&self) -> Vec<&DomainObject>;
158
159 fn clear_objects(&mut self);
161}
162
163pub struct DomainRegistry {
165 domains: HashMap<String, Box<dyn DomainValidator>>,
167 cross_references: Vec<CrossReference>,
169 global_objects: HashMap<String, DomainObject>,
171}
172
173impl Default for DomainRegistry {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl DomainRegistry {
180 pub fn new() -> Self {
182 Self {
183 domains: HashMap::new(),
184 cross_references: Vec::new(),
185 global_objects: HashMap::new(),
186 }
187 }
188
189 pub fn register_domain(
191 &mut self,
192 validator: Box<dyn DomainValidator>,
193 ) -> Result<(), BuildError> {
194 let domain_name = validator.domain_name().to_string();
195
196 if self.domains.contains_key(&domain_name) {
197 return Err(BuildError::ValidationError(format!(
198 "Domain '{}' is already registered",
199 domain_name
200 )));
201 }
202
203 self.domains.insert(domain_name, validator);
204 Ok(())
205 }
206
207 pub fn add_cross_reference(&mut self, reference: CrossReference) {
209 self.cross_references.push(reference);
210 }
211
212 pub fn register_object(&mut self, object: DomainObject) -> Result<(), BuildError> {
214 let key = format!("{}:{}", object.domain, object.qualified_name);
216 self.global_objects.insert(key, object.clone());
217
218 if let Some(domain) = self.domains.get_mut(&object.domain) {
220 domain.register_object(object)?;
221 } else {
222 return Err(BuildError::ValidationError(format!(
223 "Domain '{}' not found for object '{}'",
224 object.domain, object.name
225 )));
226 }
227
228 Ok(())
229 }
230
231 pub fn validate_all_references(&self) -> Vec<ReferenceValidationResult> {
233 let mut results = Vec::new();
234
235 for reference in &self.cross_references {
236 let result = self.validate_reference(reference);
237 results.push(result);
238 }
239
240 results
241 }
242
243 pub fn validate_reference(&self, reference: &CrossReference) -> ReferenceValidationResult {
245 if reference.is_external {
247 return ReferenceValidationResult {
248 reference: reference.clone(),
249 is_valid: true, target_object: None,
251 error_message: None,
252 suggestions: Vec::new(),
253 };
254 }
255
256 for domain in self.domains.values() {
258 if domain
259 .supported_reference_types()
260 .contains(&reference.ref_type)
261 {
262 return domain.validate_reference(reference);
263 }
264 }
265
266 ReferenceValidationResult {
268 reference: reference.clone(),
269 is_valid: false,
270 target_object: None,
271 error_message: Some(format!(
272 "No domain validator found for reference type {:?}",
273 reference.ref_type
274 )),
275 suggestions: Vec::new(),
276 }
277 }
278
279 pub fn get_validation_stats(&self) -> DomainValidationStats {
281 let results = self.validate_all_references();
282 let mut stats = DomainValidationStats {
283 total_references: results.len(),
284 ..Default::default()
285 };
286
287 for result in results {
288 if result.reference.is_external {
289 stats.external_references += 1;
290 } else if result.is_valid {
291 stats.valid_references += 1;
292 } else {
293 stats.broken_references += 1;
294 }
295
296 *stats
297 .references_by_type
298 .entry(result.reference.ref_type.clone())
299 .or_insert(0) += 1;
300 }
301
302 stats
303 }
304
305 pub fn get_broken_references(&self) -> Vec<ReferenceValidationResult> {
307 self.validate_all_references()
308 .into_iter()
309 .filter(|r| !r.is_valid && !r.reference.is_external)
310 .collect()
311 }
312
313 pub fn clear(&mut self) {
315 self.cross_references.clear();
316 self.global_objects.clear();
317
318 for domain in self.domains.values_mut() {
319 domain.clear_objects();
320 }
321 }
322
323 pub fn get_object(&self, qualified_name: &str) -> Option<&DomainObject> {
325 if let Some(obj) = self
327 .global_objects
328 .values()
329 .find(|o| o.qualified_name == qualified_name)
330 {
331 return Some(obj);
332 }
333
334 self.global_objects
336 .values()
337 .find(|o| o.name == qualified_name)
338 }
339
340 pub fn search_objects(&self, pattern: &str) -> Vec<&DomainObject> {
342 self.global_objects
343 .values()
344 .filter(|obj| obj.name.contains(pattern) || obj.qualified_name.contains(pattern))
345 .collect()
346 }
347}