1use nargo_types::{Error, Result};
4use petgraph::{
5 algo::{is_cyclic_directed, toposort},
6 graph::{DiGraph, NodeIndex},
7 visit::EdgeRef,
8 Direction,
9};
10use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet};
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum PackageSource {
16 Registry {
18 registry: String,
20 },
21 Git {
23 url: String,
25 reference: Option<String>,
27 },
28 Path {
30 path: String,
32 },
33 Github {
35 repo: String,
37 reference: Option<String>,
39 },
40 Workspace,
42}
43
44impl Default for PackageSource {
45 fn default() -> Self {
46 PackageSource::Registry { registry: "https://registry.npmjs.org".to_string() }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct DependencyNode {
53 pub name: String,
55 pub version: String,
57 pub source: PackageSource,
59 pub is_dev: bool,
61 pub is_optional: bool,
63 pub features: Vec<String>,
65}
66
67impl DependencyNode {
68 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
70 Self { name: name.into(), version: version.into(), source: PackageSource::default(), is_dev: false, is_optional: false, features: Vec::new() }
71 }
72
73 pub fn with_source(mut self, source: PackageSource) -> Self {
75 self.source = source;
76 self
77 }
78
79 pub fn as_dev(mut self, is_dev: bool) -> Self {
81 self.is_dev = is_dev;
82 self
83 }
84
85 pub fn set_dev(mut self) -> Self {
87 self.is_dev = true;
88 self
89 }
90
91 pub fn as_optional(mut self) -> Self {
93 self.is_optional = true;
94 self
95 }
96
97 pub fn with_features(mut self, features: Vec<String>) -> Self {
99 self.features = features;
100 self
101 }
102
103 pub fn key(&self) -> String {
105 format!("{}@{}", self.name, self.version)
106 }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct DependencyEdge {
112 pub constraint: String,
114 pub is_peer: bool,
116}
117
118impl DependencyEdge {
119 pub fn new(constraint: impl Into<String>) -> Self {
121 Self { constraint: constraint.into(), is_peer: false }
122 }
123
124 pub fn as_peer(mut self) -> Self {
126 self.is_peer = true;
127 self
128 }
129}
130
131#[derive(Debug, Default, Clone)]
133pub struct DependencyGraph {
134 graph: DiGraph<DependencyNode, DependencyEdge>,
136 name_to_index: HashMap<String, NodeIndex>,
138 key_to_index: HashMap<String, NodeIndex>,
140}
141
142impl DependencyGraph {
143 pub fn new() -> Self {
145 Self { graph: DiGraph::new(), name_to_index: HashMap::new(), key_to_index: HashMap::new() }
146 }
147
148 pub fn add_node(&mut self, node: DependencyNode) -> NodeIndex {
150 let key = node.key();
151 let name = node.name.clone();
152 let idx = self.graph.add_node(node);
153 self.name_to_index.insert(name, idx);
154 self.key_to_index.insert(key, idx);
155 idx
156 }
157
158 pub fn add_edge(&mut self, from: NodeIndex, to: NodeIndex, edge: DependencyEdge) {
160 self.graph.add_edge(from, to, edge);
161 }
162
163 pub fn get_node_by_name(&self, name: &str) -> Option<&DependencyNode> {
165 self.name_to_index.get(name).map(|idx| &self.graph[*idx])
166 }
167
168 pub fn get_node_by_key(&self, key: &str) -> Option<&DependencyNode> {
170 self.key_to_index.get(key).map(|idx| &self.graph[*idx])
171 }
172
173 pub fn get_node(&self, idx: NodeIndex) -> Option<&DependencyNode> {
175 self.graph.node_weight(idx)
176 }
177
178 pub fn nodes(&self) -> impl Iterator<Item = &DependencyNode> {
180 self.graph.node_weights()
181 }
182
183 pub fn node_count(&self) -> usize {
185 self.graph.node_count()
186 }
187
188 pub fn edge_count(&self) -> usize {
190 self.graph.edge_count()
191 }
192
193 pub fn has_cycle(&self) -> bool {
195 is_cyclic_directed(&self.graph)
196 }
197
198 pub fn detect_cycle(&self) -> Option<Vec<String>> {
200 if !self.has_cycle() {
201 return None;
202 }
203
204 let mut visited = HashSet::new();
205 let mut path = Vec::new();
206 let mut result = None;
207
208 for node_idx in self.graph.node_indices() {
209 if result.is_some() {
210 break;
211 }
212 self.dfs_cycle(node_idx, &mut visited, &mut path, &mut result);
213 }
214
215 result
216 }
217
218 fn dfs_cycle(&self, node: NodeIndex, visited: &mut HashSet<NodeIndex>, path: &mut Vec<NodeIndex>, result: &mut Option<Vec<String>>) {
219 if result.is_some() {
220 return;
221 }
222
223 if path.contains(&node) {
224 let cycle_start = path.iter().position(|&n| n == node).unwrap();
225 let cycle: Vec<String> = path[cycle_start..].iter().chain(std::iter::once(&node)).map(|&idx| self.graph[idx].key()).collect();
226 *result = Some(cycle);
227 return;
228 }
229
230 if visited.contains(&node) {
231 return;
232 }
233
234 visited.insert(node);
235 path.push(node);
236
237 for neighbor in self.graph.neighbors_directed(node, Direction::Outgoing) {
238 self.dfs_cycle(neighbor, visited, path, result);
239 }
240
241 path.pop();
242 }
243
244 pub fn topological_sort(&self) -> Result<Vec<DependencyNode>> {
246 if self.has_cycle() {
247 let cycle = self.detect_cycle();
248 let msg = if let Some(cycle) = cycle { format!("Circular dependency detected: {}", cycle.join(" -> ")) } else { "Circular dependency detected".to_string() };
249 return Err(Error::external_error("resolver".to_string(), msg, nargo_types::Span::unknown()));
250 }
251
252 let sorted: Vec<NodeIndex> = toposort(&self.graph, None).map_err(|e| Error::external_error("resolver".to_string(), format!("Topological sort failed: {:?}", e), nargo_types::Span::unknown()))?;
253
254 Ok(sorted.into_iter().filter_map(|idx| self.graph.node_weight(idx).cloned()).collect())
255 }
256
257 pub fn dependencies_of(&self, node_idx: NodeIndex) -> Vec<(&DependencyNode, &DependencyEdge)> {
259 self.graph
260 .edges_directed(node_idx, Direction::Outgoing)
261 .filter_map(|edge| {
262 let target = edge.target();
263 self.graph.node_weight(target).map(|node| (node, edge.weight()))
264 })
265 .collect()
266 }
267
268 pub fn dependents_of(&self, node_idx: NodeIndex) -> Vec<(&DependencyNode, &DependencyEdge)> {
270 self.graph
271 .edges_directed(node_idx, Direction::Incoming)
272 .filter_map(|edge| {
273 let source = edge.source();
274 self.graph.node_weight(source).map(|node| (node, edge.weight()))
275 })
276 .collect()
277 }
278
279 pub fn node_index(&self, name: &str) -> Option<NodeIndex> {
281 self.name_to_index.get(name).copied()
282 }
283}