1use sha2::{Digest, Sha256};
4use std::path::PathBuf;
5use url::Url;
6
7#[derive(Debug, Clone)]
9pub struct CacheKeyInfo {
10 pub url: String,
12 pub hash: String,
14 pub host: String,
16 pub filename: String,
18 pub cache_path: String,
20}
21
22pub fn compute_cache_key(url: &Url) -> CacheKeyInfo {
31 let url_str = url.as_str();
32
33 let mut hasher = Sha256::new();
35 hasher.update(url_str.as_bytes());
36 let hash_bytes = hasher.finalize();
37 let hash = hex::encode(&hash_bytes[..4]); let host = url.host_str().unwrap_or("unknown").to_string();
41
42 let path = url.path();
44 let filename = path
45 .rsplit('/')
46 .next()
47 .filter(|s| !s.is_empty())
48 .unwrap_or("index")
49 .to_string();
50
51 let cache_path = format!(
53 "{}/{}/{}/{}-{}",
54 host,
55 &hash[0..2],
56 &hash[2..4],
57 hash,
58 filename
59 );
60
61 CacheKeyInfo {
62 url: url_str.to_string(),
63 hash,
64 host,
65 filename,
66 cache_path,
67 }
68}
69
70pub fn url_to_cache_path(url: &Url, cache_dir: &std::path::Path) -> PathBuf {
72 let key_info = compute_cache_key(url);
73 cache_dir.join(&key_info.cache_path)
74}
75
76pub fn meta_path(cache_path: &std::path::Path) -> PathBuf {
78 let mut meta = cache_path.as_os_str().to_owned();
79 meta.push(".meta");
80 PathBuf::from(meta)
81}
82
83pub fn lock_path(cache_path: &std::path::Path) -> PathBuf {
85 let mut lock = cache_path.as_os_str().to_owned();
86 lock.push(".lock");
87 PathBuf::from(lock)
88}
89
90pub fn compute_content_hash(content: &str) -> String {
92 let mut hasher = Sha256::new();
93 hasher.update(content.as_bytes());
94 let hash_bytes = hasher.finalize();
95 hex::encode(&hash_bytes)
96}
97
98mod hex {
100 const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
101
102 pub fn encode(bytes: &[u8]) -> String {
103 let mut result = String::with_capacity(bytes.len() * 2);
104 for &byte in bytes {
105 result.push(HEX_CHARS[(byte >> 4) as usize] as char);
106 result.push(HEX_CHARS[(byte & 0x0f) as usize] as char);
107 }
108 result
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_compute_cache_key() {
118 let url = Url::parse("https://eure.dev/v0.1.0/schemas/eure-schema.schema.eure").unwrap();
119 let key = compute_cache_key(&url);
120
121 assert_eq!(key.host, "eure.dev");
122 assert_eq!(key.filename, "eure-schema.schema.eure");
123 assert_eq!(key.hash.len(), 8);
124 assert!(key.cache_path.starts_with("eure.dev/"));
125 assert!(key.cache_path.contains(&key.hash));
126 }
127
128 #[test]
129 fn test_compute_cache_key_root_path() {
130 let url = Url::parse("https://example.com/").unwrap();
131 let key = compute_cache_key(&url);
132
133 assert_eq!(key.host, "example.com");
134 assert_eq!(key.filename, "index");
135 }
136
137 #[test]
138 fn test_meta_path() {
139 let cache_path = PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure");
140 let meta = meta_path(&cache_path);
141 assert_eq!(
142 meta,
143 PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure.meta")
144 );
145 }
146
147 #[test]
148 fn test_lock_path() {
149 let cache_path = PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure");
150 let lock = lock_path(&cache_path);
151 assert_eq!(
152 lock,
153 PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure.lock")
154 );
155 }
156
157 #[test]
158 fn test_compute_content_hash() {
159 let hash = compute_content_hash("");
161 assert_eq!(
162 hash,
163 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
164 );
165
166 let hash = compute_content_hash("hello");
168 assert_eq!(
169 hash,
170 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
171 );
172 }
173
174 #[test]
175 fn test_url_to_cache_path() {
176 let url = Url::parse("https://eure.dev/schema.eure").unwrap();
177 let cache_dir = PathBuf::from("/home/user/.cache/eure/schemas");
178 let path = url_to_cache_path(&url, &cache_dir);
179
180 assert!(path.starts_with(&cache_dir));
181 assert!(path.to_string_lossy().contains("eure.dev"));
182 }
183}