use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceDependency {
pub from: String,
pub to: String,
pub version_req: String,
pub is_dev: bool,
pub is_build: bool,
}
impl WorkspaceDependency {
#[must_use]
pub const fn new(from: String, to: String, version_req: String) -> Self {
Self {
from,
to,
version_req,
is_dev: false,
is_build: false,
}
}
#[must_use]
pub fn as_tuple(&self) -> (&str, &str) {
(&self.from, &self.to)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DependencyGraph {
pub dependencies: Vec<WorkspaceDependency>,
}
impl DependencyGraph {
#[must_use]
pub const fn new() -> Self {
Self {
dependencies: Vec::new(),
}
}
pub fn add(&mut self, dep: WorkspaceDependency) {
self.dependencies.push(dep);
}
#[must_use]
pub fn dependencies_for(&self, crate_name: &str) -> Vec<&WorkspaceDependency> {
self.dependencies
.iter()
.filter(|d| d.from == crate_name)
.collect()
}
#[must_use]
pub fn dependents_of(&self, crate_name: &str) -> Vec<&WorkspaceDependency> {
self.dependencies
.iter()
.filter(|d| d.to == crate_name)
.collect()
}
#[must_use]
pub fn has_dependency(&self, from: &str, to: &str) -> bool {
self.dependencies
.iter()
.any(|d| d.from == from && d.to == to)
}
#[must_use]
pub fn all_crates(&self) -> std::collections::HashSet<String> {
let mut crates = std::collections::HashSet::new();
for dep in &self.dependencies {
crates.insert(dep.from.clone());
crates.insert(dep.to.clone());
}
crates
}
#[must_use]
pub fn has_cycles(&self) -> bool {
use petgraph::algo::is_cyclic_directed;
use petgraph::graph::DiGraph;
let mut graph = DiGraph::new();
let mut indices = std::collections::HashMap::new();
for name in self.all_crates() {
let idx = graph.add_node(name.clone());
indices.insert(name, idx);
}
for dep in &self.dependencies {
if let (Some(&from), Some(&to)) = (indices.get(&dep.from), indices.get(&dep.to)) {
graph.add_edge(from, to, ());
}
}
is_cyclic_directed(&graph)
}
pub fn publish_order(&self) -> Result<Vec<String>, CycleError> {
use petgraph::algo::toposort;
use petgraph::graph::DiGraph;
let mut graph = DiGraph::new();
let mut indices = std::collections::HashMap::new();
for name in self.all_crates() {
let idx = graph.add_node(name.clone());
indices.insert(name, idx);
}
for dep in &self.dependencies {
if let (Some(&from), Some(&to)) = (indices.get(&dep.from), indices.get(&dep.to)) {
graph.add_edge(to, from, ());
}
}
toposort(&graph, None).map_or(Err(CycleError), |order| {
let mut crates = Vec::new();
for idx in order {
if let Some(name) = graph.node_weight(idx) {
crates.push(name.clone());
}
}
Ok(crates)
})
}
}
impl Default for DependencyGraph {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("Dependency cycle detected")]
pub struct CycleError;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dependency_graph() {
let mut graph = DependencyGraph::new();
graph.add(WorkspaceDependency::new(
"crate-a".to_string(),
"crate-b".to_string(),
"^1.0".to_string(),
));
graph.add(WorkspaceDependency::new(
"crate-b".to_string(),
"crate-c".to_string(),
"^1.0".to_string(),
));
assert!(graph.has_dependency("crate-a", "crate-b"));
assert!(!graph.has_dependency("crate-b", "crate-a"));
let deps_a = graph.dependencies_for("crate-a");
assert_eq!(deps_a.len(), 1);
assert_eq!(deps_a[0].to, "crate-b");
let deps_of_c = graph.dependents_of("crate-c");
assert_eq!(deps_of_c.len(), 1);
assert_eq!(deps_of_c[0].from, "crate-b");
}
#[test]
fn test_publish_order() {
let mut graph = DependencyGraph::new();
graph.add(WorkspaceDependency::new(
"crate-a".to_string(),
"crate-b".to_string(),
"^1.0".to_string(),
));
graph.add(WorkspaceDependency::new(
"crate-b".to_string(),
"crate-c".to_string(),
"^1.0".to_string(),
));
let order = graph.publish_order().unwrap();
assert_eq!(order, vec!["crate-c", "crate-b", "crate-a"]);
}
#[test]
fn test_cycle_detection() {
let mut graph = DependencyGraph::new();
graph.add(WorkspaceDependency::new(
"crate-a".to_string(),
"crate-b".to_string(),
"^1.0".to_string(),
));
graph.add(WorkspaceDependency::new(
"crate-b".to_string(),
"crate-c".to_string(),
"^1.0".to_string(),
));
graph.add(WorkspaceDependency::new(
"crate-c".to_string(),
"crate-a".to_string(),
"^1.0".to_string(),
));
assert!(graph.has_cycles());
assert!(graph.publish_order().is_err());
}
#[test]
fn test_no_cycles() {
let mut graph = DependencyGraph::new();
graph.add(WorkspaceDependency::new(
"crate-a".to_string(),
"crate-b".to_string(),
"^1.0".to_string(),
));
graph.add(WorkspaceDependency::new(
"crate-b".to_string(),
"crate-c".to_string(),
"^1.0".to_string(),
));
assert!(!graph.has_cycles());
}
}