Skip to main content

stow_types/
crate_info.rs

1//! Crate coordinates ([`CrateId`]) and the canonical [`FeatureSet`] used in
2//! artifact identity hashing.
3
4use std::collections::BTreeSet;
5use std::fmt;
6
7use serde::{Deserialize, Serialize};
8
9/// Identifies a specific crate version from crates.io.
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct CrateId {
12    /// crates.io package name.
13    pub name: String,
14    /// Published package version.
15    pub version: semver::Version,
16}
17
18impl fmt::Display for CrateId {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(f, "{}@{}", self.name, self.version)
21    }
22}
23
24/// Sorted, deduplicated feature set.
25///
26/// `BTreeSet` ensures deterministic iteration order, which is critical for
27/// producing identical hashes across platforms and invocations.
28#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
29pub struct FeatureSet(pub BTreeSet<String>);
30
31impl FeatureSet {
32    /// Create an empty — already canonical — feature set.
33    #[must_use]
34    pub const fn new() -> Self {
35        Self(BTreeSet::new())
36    }
37
38    /// Whether the set contains no features.
39    #[must_use]
40    pub fn is_empty(&self) -> bool {
41        self.0.is_empty()
42    }
43
44    /// Compute a short hash of the feature set for use in OCI tags.
45    /// Returns first 8 hex chars of BLAKE3 hash over sorted features.
46    #[must_use]
47    pub fn short_hash(&self) -> String {
48        let mut hasher = blake3::Hasher::new();
49        for f in &self.0 {
50            hasher.update(f.as_bytes());
51        }
52        hex::encode(&hasher.finalize().as_bytes()[..4])
53    }
54}
55
56impl Default for FeatureSet {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl FromIterator<String> for FeatureSet {
63    fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> Self {
64        Self(iter.into_iter().collect())
65    }
66}