Skip to main content

grex_core/pack/validate/
depends_on.rs

1//! `depends_on` resolver over an assembled [`PackGraph`].
2//!
3//! The walker records `depends_on` as edges only when the target already
4//! exists in the graph; unresolved entries are surfaced here as
5//! [`PackValidationError::DependsOnUnsatisfied`].
6//!
7//! Resolution rule (mirrors the walker's `looks_like_url`):
8//!
9//! * URL-shaped entry (scheme prefix or `.git` suffix) → match against
10//!   each node's `source_url`.
11//! * Otherwise → match against each node's `name`.
12
13use super::{GraphValidator, PackValidationError};
14use crate::tree::PackGraph;
15
16/// Verify every `depends_on` entry resolves to some node in the graph.
17pub struct DependsOnValidator;
18
19impl GraphValidator for DependsOnValidator {
20    fn name(&self) -> &'static str {
21        "depends_on_unsatisfied"
22    }
23
24    fn check(&self, graph: &PackGraph) -> Vec<PackValidationError> {
25        let mut errs = Vec::new();
26        for node in graph.nodes() {
27            for dep in &node.manifest.depends_on {
28                if !is_resolvable(graph, dep) {
29                    errs.push(PackValidationError::DependsOnUnsatisfied {
30                        pack: node.name.clone(),
31                        required: dep.clone(),
32                    });
33                }
34            }
35        }
36        errs
37    }
38}
39
40fn is_resolvable(graph: &PackGraph, dep: &str) -> bool {
41    if looks_like_url(dep) {
42        graph.nodes().iter().any(|n| n.source_url.as_deref() == Some(dep))
43    } else {
44        graph.find_by_name(dep).is_some()
45    }
46}
47
48/// Literal URL/path heuristic. Duplicates the walker's rule to keep both
49/// modules decoupled — neither imports from the other's internals.
50fn looks_like_url(s: &str) -> bool {
51    s.starts_with("http://")
52        || s.starts_with("https://")
53        || s.starts_with("ssh://")
54        || s.starts_with("git@")
55        || s.ends_with(".git")
56}