archidoc_engine/
validate.rs1use std::collections::HashSet;
2use std::path::Path;
3
4use archidoc_types::{GhostEntry, ModuleDoc, OrphanEntry, ValidationReport};
5
6pub fn validate_file_tables(docs: &[ModuleDoc]) -> ValidationReport {
14 let mut report = ValidationReport::default();
15
16 for doc in docs {
17 if doc.files.is_empty() {
18 continue;
19 }
20
21 let source_dir = match Path::new(&doc.source_file).parent() {
22 Some(dir) => dir,
23 None => continue,
24 };
25
26 let source_dir_str = source_dir.to_string_lossy().to_string();
27
28 let cataloged_names: HashSet<&str> = doc.files.iter().map(|f| f.name.as_str()).collect();
30
31 for file in &doc.files {
32 let file_path = source_dir.join(&file.name);
33 if !file_path.exists() {
34 report.ghosts.push(GhostEntry {
35 element: doc.module_path.clone(),
36 filename: file.name.clone(),
37 source_dir: source_dir_str.clone(),
38 });
39 }
40 }
41
42 let structural_files: HashSet<&str> =
45 ["mod.rs", "lib.rs", "main.rs"].iter().copied().collect();
46
47 if let Ok(entries) = std::fs::read_dir(source_dir) {
48 for entry in entries.filter_map(|e| e.ok()) {
49 let filename = entry.file_name();
50 let name = filename.to_string_lossy();
51
52 if name.ends_with(".rs")
53 && !structural_files.contains(name.as_ref())
54 && !cataloged_names.contains(name.as_ref())
55 {
56 report.orphans.push(OrphanEntry {
57 element: doc.module_path.clone(),
58 filename: name.to_string(),
59 source_dir: source_dir_str.clone(),
60 });
61 }
62 }
63 }
64 }
65
66 report
67}
68
69pub fn format_validation_report(report: &ValidationReport) -> String {
71 let mut out = String::new();
72
73 if report.is_clean() {
74 out.push_str("File validation: all clear\n");
75 return out;
76 }
77
78 if !report.ghosts.is_empty() {
79 out.push_str(&format!("Ghost entries ({} found):\n", report.ghosts.len()));
80 for ghost in &report.ghosts {
81 out.push_str(&format!(
82 " {} — '{}' listed in catalog but not found on disk\n",
83 ghost.element, ghost.filename
84 ));
85 }
86 }
87
88 if !report.orphans.is_empty() {
89 out.push_str(&format!(
90 "Orphan files ({} found):\n",
91 report.orphans.len()
92 ));
93 for orphan in &report.orphans {
94 out.push_str(&format!(
95 " {} — '{}' exists on disk but not in catalog\n",
96 orphan.element, orphan.filename
97 ));
98 }
99 }
100
101 out
102}