winbrew_database\journal/
key.rs1use 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}