uv_distribution_types/
hash.rs

1use uv_pypi_types::{HashAlgorithm, HashDigest};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum HashPolicy<'a> {
5    /// No hash policy is specified.
6    None,
7    /// Hashes should be generated (specifically, a SHA-256 hash), but not validated.
8    Generate(HashGeneration),
9    /// Hashes should be validated against a pre-defined list of hashes. If necessary, hashes should
10    /// be generated so as to ensure that the archive is valid.
11    Validate(&'a [HashDigest]),
12}
13
14impl HashPolicy<'_> {
15    /// Returns `true` if the hash policy is `None`.
16    pub fn is_none(&self) -> bool {
17        matches!(self, Self::None)
18    }
19
20    /// Returns `true` if the hash policy is `Validate`.
21    pub fn is_validate(&self) -> bool {
22        matches!(self, Self::Validate(_))
23    }
24
25    /// Returns `true` if the hash policy indicates that hashes should be generated.
26    pub fn is_generate(&self, dist: &crate::BuiltDist) -> bool {
27        match self {
28            Self::Generate(HashGeneration::Url) => dist.file().is_none(),
29            Self::Generate(HashGeneration::All) => {
30                dist.file().is_none_or(|file| file.hashes.is_empty())
31            }
32            Self::Validate(_) => false,
33            Self::None => false,
34        }
35    }
36
37    /// Return the algorithms used in the hash policy.
38    pub fn algorithms(&self) -> Vec<HashAlgorithm> {
39        match self {
40            Self::None => vec![],
41            Self::Generate(_) => vec![HashAlgorithm::Sha256],
42            Self::Validate(hashes) => {
43                let mut algorithms = hashes.iter().map(HashDigest::algorithm).collect::<Vec<_>>();
44                algorithms.sort();
45                algorithms.dedup();
46                algorithms
47            }
48        }
49    }
50
51    /// Return the digests used in the hash policy.
52    pub fn digests(&self) -> &[HashDigest] {
53        match self {
54            Self::None => &[],
55            Self::Generate(_) => &[],
56            Self::Validate(hashes) => hashes,
57        }
58    }
59}
60
61/// The context in which hashes should be generated.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum HashGeneration {
64    /// Generate hashes for direct URL distributions.
65    Url,
66    /// Generate hashes for direct URL distributions, along with any distributions that are hosted
67    /// on a registry that does _not_ provide hashes.
68    All,
69}
70
71pub trait Hashed {
72    /// Return the [`HashDigest`]s for the archive.
73    fn hashes(&self) -> &[HashDigest];
74
75    /// Returns `true` if the archive satisfies the given hash policy.
76    fn satisfies(&self, hashes: HashPolicy) -> bool {
77        match hashes {
78            HashPolicy::None => true,
79            HashPolicy::Generate(_) => self
80                .hashes()
81                .iter()
82                .any(|hash| hash.algorithm == HashAlgorithm::Sha256),
83            HashPolicy::Validate(hashes) => self.hashes().iter().any(|hash| hashes.contains(hash)),
84        }
85    }
86
87    /// Returns `true` if the archive includes a hash for at least one of the given algorithms.
88    fn has_digests(&self, hashes: HashPolicy) -> bool {
89        match hashes {
90            HashPolicy::None => true,
91            HashPolicy::Generate(_) => self
92                .hashes()
93                .iter()
94                .any(|hash| hash.algorithm == HashAlgorithm::Sha256),
95            HashPolicy::Validate(hashes) => hashes
96                .iter()
97                .map(HashDigest::algorithm)
98                .any(|algorithm| self.hashes().iter().any(|hash| hash.algorithm == algorithm)),
99        }
100    }
101}