use std::collections::HashSet;
use super::{GraphValidator, PackValidationError};
use crate::tree::{EdgeKind, PackGraph};
pub struct CycleValidator;
impl GraphValidator for CycleValidator {
fn name(&self) -> &'static str {
"graph_cycle"
}
fn check(&self, graph: &PackGraph) -> Vec<PackValidationError> {
let mut errs = Vec::new();
let mut visited: HashSet<usize> = HashSet::new();
for start in 0..graph.nodes().len() {
if visited.contains(&start) {
continue;
}
let mut on_stack = Vec::new();
detect_from(graph, start, &mut visited, &mut on_stack, &mut errs);
}
errs
}
}
fn detect_from(
graph: &PackGraph,
id: usize,
visited: &mut HashSet<usize>,
on_stack: &mut Vec<usize>,
errs: &mut Vec<PackValidationError>,
) {
if on_stack.contains(&id) {
errs.push(build_cycle_error(graph, on_stack, id));
return;
}
if !visited.insert(id) {
return;
}
on_stack.push(id);
for edge in graph.edges() {
if edge.from == id && edge.kind == EdgeKind::Child {
detect_from(graph, edge.to, visited, on_stack, errs);
}
}
on_stack.pop();
}
fn build_cycle_error(
graph: &PackGraph,
on_stack: &[usize],
back_edge_target: usize,
) -> PackValidationError {
let mut chain: Vec<String> =
on_stack.iter().filter_map(|id| graph.node(*id).map(|n| n.name.clone())).collect();
if let Some(n) = graph.node(back_edge_target) {
chain.push(n.name.clone());
}
PackValidationError::GraphCycle { chain }
}