1use std::collections::HashMap;
7use serde::{Deserialize, Serialize};
8use kotoba_errors::KotobaError;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct Node {
13 pub name: String,
15 pub path: String,
17 pub node_type: String,
19 pub description: String,
21 pub dependencies: Vec<String>,
23 pub provides: Vec<String>,
25 pub status: String,
27 pub build_order: u32,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33pub struct Edge {
34 pub from: String,
36 pub to: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TopologyGraph {
43 pub nodes: HashMap<String, Node>,
45 pub edges: Vec<Edge>,
47 pub topological_order: Vec<String>,
49 pub reverse_topological_order: Vec<String>,
51}
52
53impl TopologyGraph {
54 pub fn new() -> Self {
56 Self {
57 nodes: HashMap::new(),
58 edges: Vec::new(),
59 topological_order: Vec::new(),
60 reverse_topological_order: Vec::new(),
61 }
62 }
63
64 pub fn add_node(&mut self, node: Node) {
66 self.nodes.insert(node.name.clone(), node);
67 }
68
69 pub fn add_edge(&mut self, edge: Edge) {
71 self.edges.push(edge);
72 }
73
74 pub fn node_count(&self) -> usize {
76 self.nodes.len()
77 }
78
79 pub fn edge_count(&self) -> usize {
81 self.edges.len()
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ValidationCheck {
88 pub name: String,
90 pub is_valid: bool,
92 pub errors: Vec<String>,
94 pub warnings: Vec<String>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ValidationResult {
101 pub is_valid: bool,
103 pub checks: Vec<ValidationCheck>,
105 pub statistics: ValidationStatistics,
107}
108
109impl ValidationResult {
110 pub fn new() -> Self {
112 Self {
113 is_valid: true,
114 checks: Vec::new(),
115 statistics: ValidationStatistics::default(),
116 }
117 }
118
119 pub fn add_check(&mut self, check: ValidationCheck) {
121 if !check.is_valid {
122 self.is_valid = false;
123 }
124 self.checks.push(check);
125 }
126
127 pub fn format(&self) -> String {
129 let mut output = format!("Topology Validation Result: {}\n", if self.is_valid { "PASS" } else { "FAIL" });
130 output.push_str("================================\n\n");
131
132 for check in &self.checks {
133 let status = if check.is_valid { "✓" } else { "✗" };
134 output.push_str(&format!("{} {}\n", status, check.name));
135
136 for error in &check.errors {
137 output.push_str(&format!(" Error: {}\n", error));
138 }
139
140 for warning in &check.warnings {
141 output.push_str(&format!(" Warning: {}\n", warning));
142 }
143
144 output.push_str("\n");
145 }
146
147 output.push_str(&format!("Statistics:\n"));
148 output.push_str(&format!(" Total checks: {}\n", self.checks.len()));
149 output.push_str(&format!(" Passed: {}\n", self.checks.iter().filter(|c| c.is_valid).count()));
150 output.push_str(&format!(" Failed: {}\n", self.checks.iter().filter(|c| !c.is_valid).count()));
151
152 output
153 }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, Default)]
158pub struct ValidationStatistics {
159 pub node_count: usize,
161 pub edge_count: usize,
163 pub cycle_count: usize,
165 pub orphaned_nodes: usize,
167}
168
169pub struct TopologyValidator {
171 graph: TopologyGraph,
172}
173
174impl TopologyValidator {
175 pub fn new(graph: TopologyGraph) -> Self {
177 Self { graph }
178 }
179
180 pub fn validate_all(&self) -> Result<ValidationResult> {
182 let mut result = ValidationResult::new();
183
184 result.statistics.node_count = self.graph.node_count();
186 result.statistics.edge_count = self.graph.edge_count();
187
188 result.add_check(self.validate_topological_order()?);
190
191 result.add_check(self.validate_no_cycles()?);
193
194 result.add_check(self.validate_node_consistency()?);
196
197 result.add_check(self.validate_edge_consistency()?);
199
200 result.add_check(self.validate_build_order()?);
202
203 Ok(result)
204 }
205
206 fn validate_topological_order(&self) -> Result<ValidationCheck> {
208 let mut check = ValidationCheck {
209 name: "Topological Order".to_string(),
210 is_valid: true,
211 errors: Vec::new(),
212 warnings: Vec::new(),
213 };
214
215 let topo_order = &self.graph.topological_order;
216 let rev_topo_order = &self.graph.reverse_topological_order;
217
218 if topo_order.len() != self.graph.nodes.len() {
220 check.is_valid = false;
221 check.errors.push(format!(
222 "Topological order length mismatch: expected {}, got {}",
223 self.graph.nodes.len(),
224 topo_order.len()
225 ));
226 }
227
228 if rev_topo_order.len() != self.graph.nodes.len() {
229 check.is_valid = false;
230 check.errors.push(format!(
231 "Reverse topological order length mismatch: expected {}, got {}",
232 self.graph.nodes.len(),
233 rev_topo_order.len()
234 ));
235 }
236
237 for node_name in self.graph.nodes.keys() {
239 if !topo_order.contains(node_name) {
240 check.is_valid = false;
241 check.errors.push(format!("Node '{}' missing from topological order", node_name));
242 }
243 if !rev_topo_order.contains(node_name) {
244 check.is_valid = false;
245 check.errors.push(format!("Node '{}' missing from reverse topological order", node_name));
246 }
247 }
248
249 Ok(check)
250 }
251
252 fn validate_no_cycles(&self) -> Result<ValidationCheck> {
254 let mut check = ValidationCheck {
255 name: "No Cycles".to_string(),
256 is_valid: true,
257 errors: Vec::new(),
258 warnings: Vec::new(),
259 };
260
261 let mut visited = std::collections::HashSet::new();
263 let mut rec_stack = std::collections::HashSet::new();
264
265 for node_name in self.graph.nodes.keys() {
266 if !visited.contains(node_name) {
267 if self.has_cycle(node_name, &mut visited, &mut rec_stack) {
268 check.is_valid = false;
269 check.errors.push("Cycle detected in topology graph".to_string());
270 break;
271 }
272 }
273 }
274
275 Ok(check)
276 }
277
278 fn has_cycle(&self, node: &str, visited: &mut std::collections::HashSet<String>,
280 rec_stack: &mut std::collections::HashSet<String>) -> bool {
281 visited.insert(node.to_string());
282 rec_stack.insert(node.to_string());
283
284 for edge in &self.graph.edges {
286 if edge.from == node {
287 let neighbor = &edge.to;
288 if !visited.contains(neighbor) {
289 if self.has_cycle(neighbor, visited, rec_stack) {
290 return true;
291 }
292 } else if rec_stack.contains(neighbor) {
293 return true;
294 }
295 }
296 }
297
298 rec_stack.remove(node);
299 false
300 }
301
302 fn validate_node_consistency(&self) -> Result<ValidationCheck> {
304 let mut check = ValidationCheck {
305 name: "Node Consistency".to_string(),
306 is_valid: true,
307 errors: Vec::new(),
308 warnings: Vec::new(),
309 };
310
311 for (node_name, node) in &self.graph.nodes {
312 for dep in &node.dependencies {
314 if !self.graph.nodes.contains_key(dep) {
315 check.is_valid = false;
316 check.errors.push(format!(
317 "Node '{}' has dependency '{}' which does not exist",
318 node_name, dep
319 ));
320 }
321 }
322
323 if node.build_order == 0 {
325 check.warnings.push(format!("Node '{}' has build order 0", node_name));
326 }
327 }
328
329 Ok(check)
330 }
331
332 fn validate_edge_consistency(&self) -> Result<ValidationCheck> {
334 let mut check = ValidationCheck {
335 name: "Edge Consistency".to_string(),
336 is_valid: true,
337 errors: Vec::new(),
338 warnings: Vec::new(),
339 };
340
341 for edge in &self.graph.edges {
342 if !self.graph.nodes.contains_key(&edge.from) {
344 check.is_valid = false;
345 check.errors.push(format!("Edge references non-existent source node '{}'", edge.from));
346 }
347 if !self.graph.nodes.contains_key(&edge.to) {
348 check.is_valid = false;
349 check.errors.push(format!("Edge references non-existent target node '{}'", edge.to));
350 }
351
352 if edge.from == edge.to {
354 check.warnings.push(format!("Self-loop detected on node '{}'", edge.from));
355 }
356 }
357
358 Ok(check)
359 }
360
361 fn validate_build_order(&self) -> Result<ValidationCheck> {
363 let mut check = ValidationCheck {
364 name: "Build Order".to_string(),
365 is_valid: true,
366 errors: Vec::new(),
367 warnings: Vec::new(),
368 };
369
370 let mut build_orders: Vec<u32> = self.graph.nodes.values()
371 .map(|n| n.build_order)
372 .collect();
373 build_orders.sort();
374 build_orders.dedup();
375
376 if !build_orders.is_empty() {
378 let min_order = build_orders[0];
379 let _max_order = *build_orders.last().unwrap();
380
381 if min_order != 1 {
382 check.warnings.push(format!("Build order starts at {} instead of 1", min_order));
383 }
384
385 for i in 1..build_orders.len() {
387 let expected = build_orders[i-1] + 1;
388 if build_orders[i] != expected {
389 check.warnings.push(format!(
390 "Gap in build order: missing order {}",
391 expected
392 ));
393 }
394 }
395 }
396
397 Ok(check)
398 }
399}
400
401pub type Result<T> = std::result::Result<T, KotobaError>;
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_empty_topology() {
410 let graph = TopologyGraph::new();
411 assert_eq!(graph.node_count(), 0);
412 assert_eq!(graph.edge_count(), 0);
413 }
414
415 #[test]
416 fn test_add_node() {
417 let mut graph = TopologyGraph::new();
418 let node = Node {
419 name: "test".to_string(),
420 path: "/test".to_string(),
421 node_type: "crate".to_string(),
422 description: "Test node".to_string(),
423 dependencies: Vec::new(),
424 provides: Vec::new(),
425 status: "active".to_string(),
426 build_order: 1,
427 };
428
429 graph.add_node(node);
430 assert_eq!(graph.node_count(), 1);
431 assert!(graph.nodes.contains_key("test"));
432 }
433
434 #[test]
435 fn test_add_edge() {
436 let mut graph = TopologyGraph::new();
437 let edge = Edge {
438 from: "a".to_string(),
439 to: "b".to_string(),
440 };
441
442 graph.add_edge(edge);
443 assert_eq!(graph.edge_count(), 1);
444 }
445
446 #[test]
447 fn test_validation_result_formatting() {
448 let mut result = ValidationResult::new();
449 result.add_check(ValidationCheck {
450 name: "Test Check".to_string(),
451 is_valid: false,
452 errors: vec!["Test error".to_string()],
453 warnings: vec!["Test warning".to_string()],
454 });
455
456 let formatted = result.format();
457 assert!(formatted.contains("FAIL"));
458 assert!(formatted.contains("Test error"));
459 assert!(formatted.contains("Test warning"));
460 }
461}