solvent/
lib.rs

1//! Solvent is a dependency resolver library written in rust.
2//!
3//! Solvent helps you to resolve dependency orderings by building up a dependency graph and then
4//! resolving the dependences of some target node in an order such that each output depends only
5//! upon the previous outputs.
6//!
7//! It is currently quite simple, but is still useful.
8//!
9//! #Example
10//!
11//! ```rust
12//! extern crate solvent;
13//!
14//! use solvent::DepGraph;
15//!
16//! fn main() {
17//!     // Create a new empty DepGraph.
18//!     let mut depgraph: DepGraph<&str> = DepGraph::new();
19//!
20//!     // You can register a dependency like this. Solvent will automatically create nodes for
21//!     // any term it has not seen before. This means 'b' depends on 'd'
22//!     depgraph.register_dependency("b","d");
23//!
24//!     // You can also register multiple dependencies at once
25//!     depgraph.register_dependencies("a",vec!["b","c","d"]);
26//!     depgraph.register_dependencies("c",vec!["e"]);
27//!
28//!     // Iterate through each dependency of "a". The dependencies will be returned in an order
29//!     // such that each output only depends on the previous outputs (or nothing). The target
30//!     // itself will be output last.
31//!     for node in depgraph.dependencies_of(&"a").unwrap() {
32//!         match node {
33//!             Ok(n) => print!("{} ", n),
34//!             Err(e) => panic!("Solvent error detected: {:?}", e),
35//!         }
36//!     }
37//! }
38//! ```
39//!
40//! The above will output:  `d b e c a` or `e c d b a` or some other valid dependency order.
41//!
42//! The algorithm is not deterministic, and may give a different answer each time it is run. Beware.
43//!
44//! The iterator dependencies_of() returns an `Option<Result<T ,SolventError>>`.  The for loop
45//! handles the `Option` part for you, but you may want to check the result for `SolventError`s.
46//! Once an error is returned, all subsequent calls to the iterator `next()` will yield `None`.
47//!
48//! You can also mark some nodes as already satisfied, and the iterator will take that into
49//! account
50//!
51//! ```ignore
52//! depgraph.mark_as_satisfied(["e","c"]).unwrap();
53//! ```
54//!
55//! Dependency cycles are detected and will return `SolventError::CycleDetected`.
56
57pub mod error;
58pub use error::SolventError;
59
60#[cfg(feature = "deterministic")]
61use indexmap::{map::IndexMap as HashMap, set::IndexSet as HashSet};
62#[cfg(not(feature = "deterministic"))]
63use std::collections::{HashMap, HashSet};
64
65use std::iter::Iterator;
66
67/// This is the dependency graph. The type `T` is intended to be a small type, or a
68/// reference to a larger type that implements `Eq` (you will need to supply the type
69/// and vectors of the type to functions).
70#[derive(Debug, Clone)]
71pub struct DepGraph<T: Eq> {
72    // The nodes in the graph.  Each one is assigned a unique number.
73    nodes: Vec<T>,
74
75    // List of dependencies. The first node depends on the set of additional nodes.
76    // We store indices into the nodes array.  This way we can have Eq + Copy + Hash
77    // without any requirements on type T.
78    dependencies: HashMap<usize, HashSet<usize>>,
79
80    // The set of nodes already satisfied (by index into the nodes array).
81    satisfied: HashSet<usize>,
82}
83
84impl<T: Eq> Default for DepGraph<T> {
85    fn default() -> Self {
86        Self {
87            nodes: Vec::new(),
88            dependencies: HashMap::new(),
89            satisfied: HashSet::new(),
90        }
91    }
92}
93
94impl<T: Eq> DepGraph<T> {
95    /// Create an empty DepGraph.
96    pub fn new() -> DepGraph<T> {
97        Self::default()
98    }
99
100    fn _pos(&self, node: &T) -> Option<usize> {
101        self.nodes.iter().position(|x| x == node)
102    }
103
104    fn _register_node(&mut self, node: T) -> usize {
105        match self._pos(&node) {
106            Some(pos) => pos,
107            None => {
108                self.nodes.push(node);
109                self.nodes.len() - 1
110            }
111        }
112    }
113
114    /// Register nodes in the graph. The `nodes` are added to any existing nodes,
115    /// after checking to avoid duplicates.
116    pub fn register_nodes(&mut self, mut nodes: Vec<T>) {
117        for node in nodes.drain(..) {
118            self.register_node(node);
119        }
120    }
121
122    /// Register a node in the graph. The `node` is added to any existing nodes,
123    /// after checking to avoid duplicates.
124    pub fn register_node(&mut self, node: T) {
125        self._register_node(node);
126    }
127
128    /// Add a dependency to a DepGraph. The node does not need to pre-exist, nor does the
129    /// dependency node. If the node does pre-exist, the depends_on will be added to
130    /// its existing dependency list. Otherwise it will be created.
131    pub fn register_dependency(&mut self, node: T, depends_on: T) {
132        let node_pos = self._register_node(node);
133        let dep_pos = self._register_node(depends_on);
134
135        self.dependencies
136            .entry(node_pos)
137            .and_modify(|entry| {
138                entry.insert(dep_pos);
139            })
140            .or_insert_with(|| {
141                let mut deps = HashSet::with_capacity(1);
142                deps.insert(dep_pos);
143                deps
144            });
145    }
146
147    /// Add multiple dependencies of one node to a DepGraph. The node does not need to
148    /// pre-exist, nor does the dependency node. If the node does pre-exist, the
149    /// depends_on will be added to its existing dependency list. Otherwise it will
150    /// be created.
151    pub fn register_dependencies(&mut self, node: T, mut depends_on: Vec<T>) {
152        let node_pos = self._register_node(node);
153
154        let dep_poses = depends_on
155            .drain(..)
156            .map(|dp| self._register_node(dp))
157            .collect::<Vec<_>>();
158
159        self.dependencies
160            .entry(node_pos)
161            .and_modify(|entry| {
162                entry.extend(dep_poses.iter());
163            })
164            .or_insert_with(|| dep_poses.iter().cloned().collect::<HashSet<_>>());
165    }
166
167    /// This marks a node as satisfied. Iterators will not output such nodes. Nodes
168    /// must exist.
169    pub fn mark_as_satisfied(&mut self, nodes: &[T]) -> Result<(), SolventError> {
170        for node in nodes.iter() {
171            let node_pos = self._pos(node).ok_or(SolventError::NoSuchNode)?;
172            self.satisfied.insert(node_pos);
173        }
174
175        Ok(())
176    }
177
178    /// Get an iterator to iterate through the dependencies of the target node. Target
179    /// node must exist.
180    pub fn dependencies_of<'a>(
181        &'a self,
182        target: &T,
183    ) -> Result<DepGraphIterator<'a, T>, SolventError> {
184        let pos = self._pos(target).ok_or(SolventError::NoSuchNode)?;
185
186        Ok(DepGraphIterator {
187            depgraph: self,
188            target: pos,
189            satisfied: self.satisfied.clone(),
190            curpath: HashSet::new(),
191            halted: false,
192        })
193    }
194}
195
196/// This iterates through the dependencies of the DepGraph's target
197pub struct DepGraphIterator<'a, T: Eq + 'a> {
198    depgraph: &'a DepGraph<T>,
199
200    // Target we are trying to satisfy
201    target: usize,
202
203    // Node positions already satisfied during this iterator's walk
204    satisfied: HashSet<usize>,
205
206    // Current path, for cycle detection
207    curpath: HashSet<usize>,
208
209    // Halted.  Used so that it can return None after an Err is returned.
210    halted: bool,
211}
212
213impl<'a, T: Eq> DepGraphIterator<'a, T> {
214    fn get_next_dependency(&mut self, pos: usize) -> Result<usize, SolventError> {
215        if self.curpath.contains(&pos) {
216            return Err(SolventError::CycleDetected);
217        }
218        self.curpath.insert(pos);
219
220        let deplist = match self.depgraph.dependencies.get(&pos) {
221            None => return Ok(pos),
222            Some(deplist) => deplist,
223        };
224
225        for n in deplist.iter() {
226            // Prune satisfied nodes
227            if self.satisfied.contains(n) {
228                continue;
229            }
230            return self.get_next_dependency(*n);
231        }
232
233        // nodes dependencies are satisfied
234        Ok(pos)
235    }
236}
237
238impl<'a, T: Eq> Iterator for DepGraphIterator<'a, T> {
239    type Item = Result<&'a T, SolventError>;
240
241    // Get next dependency.  Returns None when finished.  If Some(Err(SolventError)) occurs,
242    // all subsequent calls will return None.
243    fn next(&mut self) -> Option<Result<&'a T, SolventError>> {
244        if self.halted {
245            return None;
246        }
247
248        let npos = self.target;
249        if self.satisfied.contains(&npos) {
250            self.halted = true;
251            return None;
252        }
253
254        self.curpath.clear();
255        let next = match self.get_next_dependency(npos) {
256            Ok(d) => d,
257            Err(e) => {
258                self.halted = true;
259                return Some(Err(e));
260            }
261        };
262        self.satisfied.insert(next);
263        Some(Ok(&self.depgraph.nodes[next]))
264    }
265}
266
267#[cfg(test)]
268mod test {
269    use super::DepGraph;
270    use super::HashSet;
271    use super::SolventError;
272
273    #[test]
274    fn solvent_test_branching() {
275        let mut depgraph: DepGraph<&str> = DepGraph::new();
276
277        depgraph.register_nodes(vec![
278            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
279        ]);
280
281        depgraph.register_dependencies("a", vec!["b", "c", "d"]);
282        depgraph.register_dependency("b", "d");
283        depgraph.register_dependencies("c", vec!["e", "m", "g"]);
284        depgraph.register_dependency("e", "f");
285        depgraph.register_dependency("g", "h");
286        depgraph.register_dependency("h", "i");
287        depgraph.register_dependencies("i", vec!["j", "k"]);
288        depgraph.register_dependencies("k", vec!["l", "m"]);
289        depgraph.register_dependency("m", "n");
290
291        let mut results = Vec::new();
292
293        for node in depgraph.dependencies_of(&"a").unwrap() {
294            // detect infinite looping bugs
295            assert!(results.len() < 30);
296
297            let n = match node {
298                Err(e) => panic!("Solvent error detected: {:?}", e),
299                Ok(n) => n,
300            };
301
302            // Check that all of that nodes dependencies have already been output
303            let pos = depgraph._pos(&n).unwrap();
304            let deps: Option<&HashSet<usize>> = depgraph.dependencies.get(&pos);
305            if let Some(deps) = deps {
306                for dep in deps.iter() {
307                    assert!(results.contains(&depgraph.nodes[*dep]));
308                }
309            }
310
311            results.push(*n);
312        }
313
314        // Be sure we actually output enough stuff
315        assert!(results.len() == 14);
316
317        // Be sure each output is unique
318        for result in results.iter() {
319            let mut count: usize = 0;
320            for result2 in results.iter() {
321                if result == result2 {
322                    count += 1;
323                }
324            }
325            assert!(count == 1);
326        }
327    }
328
329    #[test]
330    fn solvent_test_updating_dependencies() {
331        let mut depgraph: DepGraph<&str> = DepGraph::new();
332
333        depgraph.register_dependencies("a", vec!["b", "c"]);
334        depgraph.register_dependency("a", "d");
335        assert!(depgraph.dependencies.get(&0).unwrap().contains(&1));
336        assert!(depgraph.dependencies.get(&0).unwrap().contains(&2));
337        assert!(depgraph.dependencies.get(&0).unwrap().contains(&3));
338    }
339
340    #[test]
341    fn solvent_test_circular() {
342        let mut depgraph: DepGraph<&str> = DepGraph::new();
343        depgraph.register_dependency("a", "b");
344        depgraph.register_dependency("b", "c");
345        depgraph.register_dependency("c", "a");
346
347        for node in depgraph.dependencies_of(&"a").unwrap() {
348            assert!(node.is_err());
349            assert!(node.unwrap_err() == SolventError::CycleDetected);
350        }
351    }
352
353    #[test]
354    fn solvent_test_satisfied_stoppage() {
355        let mut depgraph: DepGraph<&str> = DepGraph::new();
356        depgraph.register_dependencies("superconn", vec![]);
357        depgraph.register_dependencies("owneruser", vec!["superconn"]);
358        depgraph.register_dependencies("appuser", vec!["superconn"]);
359        depgraph.register_dependencies("database", vec!["owneruser"]);
360        depgraph.register_dependencies("ownerconn", vec!["database", "owneruser"]);
361        depgraph.register_dependencies("adminconn", vec!["database"]);
362        depgraph.register_dependencies("extensions", vec!["database", "adminconn"]);
363        depgraph.register_dependencies("schema_table", vec!["database", "ownerconn"]);
364        depgraph.register_dependencies(
365            "schemas",
366            vec!["ownerconn", "extensions", "schema_table", "appuser"],
367        );
368        depgraph.register_dependencies("appconn", vec!["database", "appuser", "schemas"]);
369
370        depgraph
371            .mark_as_satisfied(&["owneruser", "appuser"])
372            .unwrap();
373        assert_eq!(depgraph.satisfied.len(), 2);
374
375        let mut results: Vec<&str> = Vec::new();
376
377        for node in depgraph.dependencies_of(&"appconn").unwrap() {
378            assert!(results.len() < 30);
379            match node {
380                Ok(n) => results.push(n),
381                Err(e) => panic!("Solvent error detected: {:?}", e),
382            };
383        }
384
385        // Be sure we did not depend on these
386        assert!(!results.contains(&"appuser"));
387        assert!(!results.contains(&"owneruser"));
388        assert!(!results.contains(&"superconn"));
389
390        // Be sure we actually output enough stuff
391        assert!(results.len() == 7);
392
393        // Be sure each output is unique
394        for result in results.iter() {
395            let mut count: usize = 0;
396            for result2 in results.iter() {
397                if result == result2 {
398                    count += 1;
399                }
400            }
401            assert!(count == 1);
402        }
403    }
404}