winbrew_database\journal/
key.rs

1use crate::core::Hasher;
2use crate::models::shared::hash::HashAlgorithm;
3
4pub fn package_journal_key(package_id: &str, version: &str) -> String {
5    let mut key = sanitize_package_key_component(package_id);
6    key.push('-');
7    key.push_str(&version_hash(version));
8    key
9}
10
11fn sanitize_package_key_component(value: &str) -> String {
12    let mut normalized = String::with_capacity(value.len());
13
14    for ch in value.trim().chars() {
15        if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.') {
16            normalized.push(ch);
17        } else {
18            normalized.push('_');
19        }
20    }
21
22    if normalized.is_empty() {
23        "package".to_string()
24    } else {
25        normalized
26    }
27}
28
29fn version_hash(version: &str) -> String {
30    let mut hasher = Hasher::new(HashAlgorithm::Sha256);
31    hasher.update(version.trim().as_bytes());
32
33    let digest = hasher.finalize();
34    let mut encoded = String::with_capacity(16);
35    const HEX: &[u8; 16] = b"0123456789abcdef";
36
37    for &byte in digest.iter().take(8) {
38        encoded.push(HEX[(byte >> 4) as usize] as char);
39        encoded.push(HEX[(byte & 0x0F) as usize] as char);
40    }
41
42    encoded
43}
44
45#[cfg(test)]
46mod tests {
47    use super::package_journal_key;
48
49    #[test]
50    fn package_journal_key_includes_sanitized_id_and_version_hash() {
51        let key = package_journal_key("winget/Contoso.App", "1.2.3");
52
53        assert_eq!(key, "winget_Contoso.App-c47f5b18b8a430e6");
54    }
55}