Skip to main content

gen_types/
dependency.rs

1//! Typed [`Dependency`] — one edge in the package graph.
2
3use crate::{TargetPredicate, VersionConstraint};
4use serde::{Deserialize, Serialize};
5
6/// One dependency edge from a parent package to a child.
7#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
8pub struct Dependency {
9    pub name: String,
10    pub constraint: VersionConstraint,
11    pub kind: DependencyKind,
12    /// Which of the dependency's features the parent enables.
13    #[serde(default)]
14    pub features_enabled: Vec<String>,
15    /// Whether to pull the dependency's `default` feature set.
16    #[serde(default = "default_true")]
17    pub default_features: bool,
18    /// Conditional-activation predicate (cfg / engine / platform).
19    #[serde(default)]
20    pub target_predicate: Option<TargetPredicate>,
21    /// `[patch."<registry>"]` / `npm overrides` — a per-edge source
22    /// override. Most edges have None; resolver consults this when
23    /// non-None.
24    #[serde(default)]
25    pub source_override: Option<crate::PackageSource>,
26}
27
28fn default_true() -> bool {
29    true
30}
31
32/// Typed kind covering every per-language dep classification.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "kebab-case")]
35pub enum DependencyKind {
36    /// Standard runtime dependency.
37    Direct,
38    /// Build-time only (cargo `[build-dependencies]`, npm absent —
39    /// approximated as devDeps used during build script).
40    Build,
41    /// Test / dev only (cargo `[dev-dependencies]`, npm
42    /// `devDependencies`, gem `:development`, pip `[dev]` extras).
43    Dev,
44    /// Optional — pulled in by feature activation (cargo
45    /// `optional = true`, npm `optionalDependencies`).
46    Optional,
47    /// Peer dependency (npm `peerDependencies`) — declared by the
48    /// consumer, satisfied by the parent. Cargo has no direct
49    /// analog; adapters can use this for similar "consumer
50    /// provides" relationships.
51    Peer,
52    /// Replaces / substitutes another package (cargo `[replace]`,
53    /// npm `overrides`, Bundler `gemspec` source overrides).
54    Replaces,
55}
56
57impl DependencyKind {
58    /// Stable string token for diagnostics + cache keys.
59    #[must_use]
60    pub const fn as_str(self) -> &'static str {
61        match self {
62            Self::Direct => "direct",
63            Self::Build => "build",
64            Self::Dev => "dev",
65            Self::Optional => "optional",
66            Self::Peer => "peer",
67            Self::Replaces => "replaces",
68        }
69    }
70
71    /// Is this dependency kind needed at runtime?
72    /// (used by the resolver to filter dev/build deps for
73    /// release-build cache hits).
74    #[must_use]
75    pub const fn is_runtime(self) -> bool {
76        matches!(self, Self::Direct | Self::Optional | Self::Peer | Self::Replaces)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::{ConstraintSpec, Version};
84
85    #[test]
86    fn kind_string_tokens_are_stable() {
87        for k in [
88            DependencyKind::Direct,
89            DependencyKind::Build,
90            DependencyKind::Dev,
91            DependencyKind::Optional,
92            DependencyKind::Peer,
93            DependencyKind::Replaces,
94        ] {
95            assert!(!k.as_str().is_empty());
96        }
97    }
98
99    #[test]
100    fn is_runtime_classifies_correctly() {
101        assert!(DependencyKind::Direct.is_runtime());
102        assert!(DependencyKind::Optional.is_runtime());
103        assert!(DependencyKind::Peer.is_runtime());
104        assert!(DependencyKind::Replaces.is_runtime());
105        assert!(!DependencyKind::Build.is_runtime());
106        assert!(!DependencyKind::Dev.is_runtime());
107    }
108
109    #[test]
110    fn round_trip_through_serde() {
111        let d = Dependency {
112            name: "serde".into(),
113            constraint: VersionConstraint::from_spec(ConstraintSpec::Caret(Version::new(1, 0, 0))),
114            kind: DependencyKind::Direct,
115            features_enabled: vec!["derive".into()],
116            default_features: true,
117            target_predicate: None,
118            source_override: None,
119        };
120        let j = serde_json::to_string(&d).unwrap();
121        let parsed: Dependency = serde_json::from_str(&j).unwrap();
122        assert_eq!(d, parsed);
123    }
124}