Skip to main content

gen_types/
source.rs

1//! Typed [`PackageSource`] — where a package's bits come from.
2
3use crate::Registry;
4use serde::{Deserialize, Serialize};
5
6/// One typed source variant per resolution kind every adapter
7/// surfaces.
8#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(tag = "kind", rename_all = "kebab-case")]
10pub enum PackageSource {
11    /// Fetched from an upstream registry (most common case).
12    Registry {
13        registry: Registry,
14        /// Canonical name in the registry's namespace (may differ
15        /// from `Package.name` for renames / aliases).
16        registry_name: String,
17        /// SHA-256 / BLAKE3 / etc. of the registry tarball — the
18        /// content-addressed integrity check the lockfile pins.
19        integrity_hash: Option<String>,
20    },
21    /// Fetched from a git repository.
22    Git {
23        url: String,
24        /// Branch / tag / commit-rev — adapters normalise to commit-
25        /// rev during resolution so the lockfile is reproducible.
26        rev: String,
27        /// Subdirectory inside the git tree, if the package isn't at
28        /// the repo root.
29        #[serde(default)]
30        subdir: Option<String>,
31    },
32    /// Local path (workspace-relative or absolute).
33    Path { path: String },
34    /// Local development override (`[patch."<registry>"]` in Cargo;
35    /// `npm install file:../foo`; etc.). Differs from `Path` in that
36    /// it's an override of a registry source, not the canonical
37    /// source.
38    Local { path: String, overrides: String },
39}
40
41impl PackageSource {
42    /// Stable string identifier for cache keys + diagnostics.
43    #[must_use]
44    pub const fn kind_str(&self) -> &'static str {
45        match self {
46            Self::Registry { .. } => "registry",
47            Self::Git { .. } => "git",
48            Self::Path { .. } => "path",
49            Self::Local { .. } => "local",
50        }
51    }
52
53    /// Is this source content-addressed (registry with integrity hash
54    /// OR git with a commit-rev)? Cache-hit-first only makes sense
55    /// for content-addressed sources — path + local sources rebuild
56    /// every time.
57    #[must_use]
58    pub fn is_content_addressed(&self) -> bool {
59        match self {
60            Self::Registry { integrity_hash, .. } => integrity_hash.is_some(),
61            Self::Git { .. } => true,
62            Self::Path { .. } | Self::Local { .. } => false,
63        }
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn registry_with_hash_is_content_addressed() {
73        let s = PackageSource::Registry {
74            registry: Registry::CratesIo,
75            registry_name: "serde".into(),
76            integrity_hash: Some("sha256:abc".into()),
77        };
78        assert!(s.is_content_addressed());
79    }
80
81    #[test]
82    fn registry_without_hash_is_not_content_addressed() {
83        let s = PackageSource::Registry {
84            registry: Registry::CratesIo,
85            registry_name: "serde".into(),
86            integrity_hash: None,
87        };
88        assert!(!s.is_content_addressed());
89    }
90
91    #[test]
92    fn git_is_always_content_addressed() {
93        let s = PackageSource::Git {
94            url: "https://github.com/x/y".into(),
95            rev: "abc123".into(),
96            subdir: None,
97        };
98        assert!(s.is_content_addressed());
99    }
100
101    #[test]
102    fn path_and_local_are_not_content_addressed() {
103        assert!(!PackageSource::Path { path: "../foo".into() }.is_content_addressed());
104        assert!(
105            !PackageSource::Local {
106                path: "../foo".into(),
107                overrides: "crates-io.serde".into(),
108            }
109            .is_content_addressed()
110        );
111    }
112
113    #[test]
114    fn round_trip_through_serde() {
115        let s = PackageSource::Git {
116            url: "https://github.com/x/y".into(),
117            rev: "abc123".into(),
118            subdir: Some("crates/foo".into()),
119        };
120        let j = serde_json::to_string(&s).unwrap();
121        let parsed: PackageSource = serde_json::from_str(&j).unwrap();
122        assert_eq!(s, parsed);
123    }
124}