pub mod error;
pub use error::SolventError;
#[cfg(feature = "deterministic")]
use indexmap::{map::IndexMap as HashMap, set::IndexSet as HashSet};
#[cfg(not(feature = "deterministic"))]
use std::collections::{HashMap, HashSet};
use std::iter::Iterator;
#[derive(Debug, Clone)]
pub struct DepGraph<T: Eq> {
nodes: Vec<T>,
dependencies: HashMap<usize, HashSet<usize>>,
satisfied: HashSet<usize>,
}
impl<T: Eq> Default for DepGraph<T> {
fn default() -> Self {
Self {
nodes: Vec::new(),
dependencies: HashMap::new(),
satisfied: HashSet::new(),
}
}
}
impl<T: Eq> DepGraph<T> {
pub fn new() -> DepGraph<T> {
Self::default()
}
fn _pos(&self, node: &T) -> Option<usize> {
self.nodes.iter().position(|x| x == node)
}
fn _register_node(&mut self, node: T) -> usize {
match self._pos(&node) {
Some(pos) => pos,
None => {
self.nodes.push(node);
self.nodes.len() - 1
}
}
}
pub fn register_nodes(&mut self, mut nodes: Vec<T>) {
for node in nodes.drain(..) {
self.register_node(node);
}
}
pub fn register_node(&mut self, node: T) {
self._register_node(node);
}
pub fn register_dependency(&mut self, node: T, depends_on: T) {
let node_pos = self._register_node(node);
let dep_pos = self._register_node(depends_on);
self.dependencies
.entry(node_pos)
.and_modify(|entry| {
entry.insert(dep_pos);
})
.or_insert_with(|| {
let mut deps = HashSet::with_capacity(1);
deps.insert(dep_pos);
deps
});
}
pub fn register_dependencies(&mut self, node: T, mut depends_on: Vec<T>) {
let node_pos = self._register_node(node);
let dep_poses = depends_on
.drain(..)
.map(|dp| self._register_node(dp))
.collect::<Vec<_>>();
self.dependencies
.entry(node_pos)
.and_modify(|entry| {
entry.extend(dep_poses.iter());
})
.or_insert_with(|| dep_poses.iter().cloned().collect::<HashSet<_>>());
}
pub fn mark_as_satisfied(&mut self, nodes: &[T]) -> Result<(), SolventError> {
for node in nodes.iter() {
let node_pos = self._pos(node).ok_or(SolventError::NoSuchNode)?;
self.satisfied.insert(node_pos);
}
Ok(())
}
pub fn dependencies_of<'a>(
&'a self,
target: &T,
) -> Result<DepGraphIterator<'a, T>, SolventError> {
let pos = self._pos(target).ok_or(SolventError::NoSuchNode)?;
Ok(DepGraphIterator {
depgraph: self,
target: pos,
satisfied: self.satisfied.clone(),
curpath: HashSet::new(),
halted: false,
})
}
}
pub struct DepGraphIterator<'a, T: Eq + 'a> {
depgraph: &'a DepGraph<T>,
target: usize,
satisfied: HashSet<usize>,
curpath: HashSet<usize>,
halted: bool,
}
impl<'a, T: Eq> DepGraphIterator<'a, T> {
fn get_next_dependency(&mut self, pos: usize) -> Result<usize, SolventError> {
if self.curpath.contains(&pos) {
return Err(SolventError::CycleDetected);
}
self.curpath.insert(pos);
let deplist = match self.depgraph.dependencies.get(&pos) {
None => return Ok(pos),
Some(deplist) => deplist,
};
for n in deplist.iter() {
if self.satisfied.contains(n) {
continue;
}
return self.get_next_dependency(*n);
}
Ok(pos)
}
}
impl<'a, T: Eq> Iterator for DepGraphIterator<'a, T> {
type Item = Result<&'a T, SolventError>;
fn next(&mut self) -> Option<Result<&'a T, SolventError>> {
if self.halted {
return None;
}
let npos = self.target;
if self.satisfied.contains(&npos) {
self.halted = true;
return None;
}
self.curpath.clear();
let next = match self.get_next_dependency(npos) {
Ok(d) => d,
Err(e) => {
self.halted = true;
return Some(Err(e));
}
};
self.satisfied.insert(next);
Some(Ok(&self.depgraph.nodes[next]))
}
}
#[cfg(test)]
mod test {
use super::DepGraph;
use super::HashSet;
use super::SolventError;
#[test]
fn solvent_test_branching() {
let mut depgraph: DepGraph<&str> = DepGraph::new();
depgraph.register_nodes(vec![
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
]);
depgraph.register_dependencies("a", vec!["b", "c", "d"]);
depgraph.register_dependency("b", "d");
depgraph.register_dependencies("c", vec!["e", "m", "g"]);
depgraph.register_dependency("e", "f");
depgraph.register_dependency("g", "h");
depgraph.register_dependency("h", "i");
depgraph.register_dependencies("i", vec!["j", "k"]);
depgraph.register_dependencies("k", vec!["l", "m"]);
depgraph.register_dependency("m", "n");
let mut results = Vec::new();
for node in depgraph.dependencies_of(&"a").unwrap() {
assert!(results.len() < 30);
let n = match node {
Err(e) => panic!("Solvent error detected: {:?}", e),
Ok(n) => n,
};
let pos = depgraph._pos(&n).unwrap();
let deps: Option<&HashSet<usize>> = depgraph.dependencies.get(&pos);
if let Some(deps) = deps {
for dep in deps.iter() {
assert!(results.contains(&depgraph.nodes[*dep]));
}
}
results.push(*n);
}
assert!(results.len() == 14);
for result in results.iter() {
let mut count: usize = 0;
for result2 in results.iter() {
if result == result2 {
count += 1;
}
}
assert!(count == 1);
}
}
#[test]
fn solvent_test_updating_dependencies() {
let mut depgraph: DepGraph<&str> = DepGraph::new();
depgraph.register_dependencies("a", vec!["b", "c"]);
depgraph.register_dependency("a", "d");
assert!(depgraph.dependencies.get(&0).unwrap().contains(&1));
assert!(depgraph.dependencies.get(&0).unwrap().contains(&2));
assert!(depgraph.dependencies.get(&0).unwrap().contains(&3));
}
#[test]
fn solvent_test_circular() {
let mut depgraph: DepGraph<&str> = DepGraph::new();
depgraph.register_dependency("a", "b");
depgraph.register_dependency("b", "c");
depgraph.register_dependency("c", "a");
for node in depgraph.dependencies_of(&"a").unwrap() {
assert!(node.is_err());
assert!(node.unwrap_err() == SolventError::CycleDetected);
}
}
#[test]
fn solvent_test_satisfied_stoppage() {
let mut depgraph: DepGraph<&str> = DepGraph::new();
depgraph.register_dependencies("superconn", vec![]);
depgraph.register_dependencies("owneruser", vec!["superconn"]);
depgraph.register_dependencies("appuser", vec!["superconn"]);
depgraph.register_dependencies("database", vec!["owneruser"]);
depgraph.register_dependencies("ownerconn", vec!["database", "owneruser"]);
depgraph.register_dependencies("adminconn", vec!["database"]);
depgraph.register_dependencies("extensions", vec!["database", "adminconn"]);
depgraph.register_dependencies("schema_table", vec!["database", "ownerconn"]);
depgraph.register_dependencies(
"schemas",
vec!["ownerconn", "extensions", "schema_table", "appuser"],
);
depgraph.register_dependencies("appconn", vec!["database", "appuser", "schemas"]);
depgraph
.mark_as_satisfied(&["owneruser", "appuser"])
.unwrap();
assert_eq!(depgraph.satisfied.len(), 2);
let mut results: Vec<&str> = Vec::new();
for node in depgraph.dependencies_of(&"appconn").unwrap() {
assert!(results.len() < 30);
match node {
Ok(n) => results.push(n),
Err(e) => panic!("Solvent error detected: {:?}", e),
};
}
assert!(!results.contains(&"appuser"));
assert!(!results.contains(&"owneruser"));
assert!(!results.contains(&"superconn"));
assert!(results.len() == 7);
for result in results.iter() {
let mut count: usize = 0;
for result2 in results.iter() {
if result == result2 {
count += 1;
}
}
assert!(count == 1);
}
}
}