use std::collections::{BTreeMap, BTreeSet};
use super::{Graph, GraphId};
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphImport {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
pub enum GraphImportError {
#[error("import references missing graph: from={from} to={to}")]
MissingGraph { from: GraphId, to: GraphId },
#[error("import cycle detected")]
Cycle { cycle: Vec<GraphId> },
}
#[derive(Debug, Clone)]
pub struct GraphImportClosure {
pub reachable: BTreeSet<GraphId>,
pub order: Vec<GraphId>,
}
impl GraphImportClosure {
pub fn contains(&self, graph: GraphId) -> bool {
self.reachable.contains(&graph)
}
}
pub fn resolve_import_closure<'a>(
root_graph: &'a Graph,
resolver: impl FnMut(GraphId) -> Option<&'a Graph>,
) -> Result<GraphImportClosure, GraphImportError> {
ImportClosureResolver::new(root_graph, resolver).finish()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ImportVisitMark {
Temporary,
Permanent,
}
struct ImportClosureResolver<'a, R>
where
R: FnMut(GraphId) -> Option<&'a Graph>,
{
root: GraphId,
root_graph: &'a Graph,
resolver: R,
marks: BTreeMap<GraphId, ImportVisitMark>,
stack: Vec<GraphId>,
closure: GraphImportClosure,
}
impl<'a, R> ImportClosureResolver<'a, R>
where
R: FnMut(GraphId) -> Option<&'a Graph>,
{
fn new(root_graph: &'a Graph, resolver: R) -> Self {
Self {
root: root_graph.graph_id,
root_graph,
resolver,
marks: BTreeMap::new(),
stack: Vec::new(),
closure: GraphImportClosure {
reachable: BTreeSet::new(),
order: Vec::new(),
},
}
}
fn finish(mut self) -> Result<GraphImportClosure, GraphImportError> {
self.visit(self.root, self.root)?;
Ok(self.closure)
}
fn visit(&mut self, node: GraphId, from: GraphId) -> Result<(), GraphImportError> {
match self.marks.get(&node).copied() {
Some(ImportVisitMark::Permanent) => return Ok(()),
Some(ImportVisitMark::Temporary) => return Err(self.cycle_error(node)),
None => {}
}
self.marks.insert(node, ImportVisitMark::Temporary);
self.stack.push(node);
for dep in self.dependencies(node, from)? {
self.closure.reachable.insert(dep);
self.visit(dep, node)?;
}
self.stack.pop();
self.marks.insert(node, ImportVisitMark::Permanent);
if node != self.root {
self.closure.order.push(node);
}
Ok(())
}
fn dependencies(
&mut self,
node: GraphId,
from: GraphId,
) -> Result<Vec<GraphId>, GraphImportError> {
let imports = if node == self.root {
&self.root_graph.imports
} else {
let Some(graph) = (self.resolver)(node) else {
return Err(GraphImportError::MissingGraph { from, to: node });
};
&graph.imports
};
Ok(imports.keys().copied().collect())
}
fn cycle_error(&self, node: GraphId) -> GraphImportError {
if let Some(pos) = self.stack.iter().position(|id| *id == node) {
let mut cycle = self.stack[pos..].to_vec();
cycle.push(node);
return GraphImportError::Cycle { cycle };
}
GraphImportError::Cycle {
cycle: vec![node, node],
}
}
}