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!(
77            self,
78            Self::Direct | Self::Optional | Self::Peer | Self::Replaces
79        )
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::{ConstraintSpec, Version};
87
88    #[test]
89    fn kind_string_tokens_are_stable() {
90        for k in [
91            DependencyKind::Direct,
92            DependencyKind::Build,
93            DependencyKind::Dev,
94            DependencyKind::Optional,
95            DependencyKind::Peer,
96            DependencyKind::Replaces,
97        ] {
98            assert!(!k.as_str().is_empty());
99        }
100    }
101
102    #[test]
103    fn is_runtime_classifies_correctly() {
104        assert!(DependencyKind::Direct.is_runtime());
105        assert!(DependencyKind::Optional.is_runtime());
106        assert!(DependencyKind::Peer.is_runtime());
107        assert!(DependencyKind::Replaces.is_runtime());
108        assert!(!DependencyKind::Build.is_runtime());
109        assert!(!DependencyKind::Dev.is_runtime());
110    }
111
112    #[test]
113    fn round_trip_through_serde() {
114        let d = Dependency {
115            name: "serde".into(),
116            constraint: VersionConstraint::from_spec(ConstraintSpec::Caret(Version::new(1, 0, 0))),
117            kind: DependencyKind::Direct,
118            features_enabled: vec!["derive".into()],
119            default_features: true,
120            target_predicate: None,
121            source_override: None,
122        };
123        let j = serde_json::to_string(&d).unwrap();
124        let parsed: Dependency = serde_json::from_str(&j).unwrap();
125        assert_eq!(d, parsed);
126    }
127}