use sha2::{Digest, Sha256};
use std::path::PathBuf;
use url::Url;
#[derive(Debug, Clone)]
pub struct CacheKeyInfo {
pub url: String,
pub hash: String,
pub host: String,
pub filename: String,
pub cache_path: String,
}
pub fn compute_cache_key(url: &Url) -> CacheKeyInfo {
let url_str = url.as_str();
let mut hasher = Sha256::new();
hasher.update(url_str.as_bytes());
let hash_bytes = hasher.finalize();
let hash = hex::encode(&hash_bytes[..4]);
let host = url.host_str().unwrap_or("unknown").to_string();
let path = url.path();
let filename = path
.rsplit('/')
.next()
.filter(|s| !s.is_empty())
.unwrap_or("index")
.to_string();
let cache_path = format!(
"{}/{}/{}/{}-{}",
host,
&hash[0..2],
&hash[2..4],
hash,
filename
);
CacheKeyInfo {
url: url_str.to_string(),
hash,
host,
filename,
cache_path,
}
}
pub fn url_to_cache_path(url: &Url, cache_dir: &std::path::Path) -> PathBuf {
let key_info = compute_cache_key(url);
cache_dir.join(&key_info.cache_path)
}
pub fn meta_path(cache_path: &std::path::Path) -> PathBuf {
let mut meta = cache_path.as_os_str().to_owned();
meta.push(".meta");
PathBuf::from(meta)
}
pub fn lock_path(cache_path: &std::path::Path) -> PathBuf {
let mut lock = cache_path.as_os_str().to_owned();
lock.push(".lock");
PathBuf::from(lock)
}
pub fn compute_content_hash(content: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(content.as_bytes());
let hash_bytes = hasher.finalize();
hex::encode(&hash_bytes)
}
mod hex {
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
pub fn encode(bytes: &[u8]) -> String {
let mut result = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
result.push(HEX_CHARS[(byte >> 4) as usize] as char);
result.push(HEX_CHARS[(byte & 0x0f) as usize] as char);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_cache_key() {
let url = Url::parse("https://eure.dev/v0.1.0/schemas/eure-schema.schema.eure").unwrap();
let key = compute_cache_key(&url);
assert_eq!(key.host, "eure.dev");
assert_eq!(key.filename, "eure-schema.schema.eure");
assert_eq!(key.hash.len(), 8);
assert!(key.cache_path.starts_with("eure.dev/"));
assert!(key.cache_path.contains(&key.hash));
}
#[test]
fn test_compute_cache_key_root_path() {
let url = Url::parse("https://example.com/").unwrap();
let key = compute_cache_key(&url);
assert_eq!(key.host, "example.com");
assert_eq!(key.filename, "index");
}
#[test]
fn test_meta_path() {
let cache_path = PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure");
let meta = meta_path(&cache_path);
assert_eq!(
meta,
PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure.meta")
);
}
#[test]
fn test_lock_path() {
let cache_path = PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure");
let lock = lock_path(&cache_path);
assert_eq!(
lock,
PathBuf::from("/cache/eure.dev/a1/b2/a1b2c3d4-schema.eure.lock")
);
}
#[test]
fn test_compute_content_hash() {
let hash = compute_content_hash("");
assert_eq!(
hash,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
let hash = compute_content_hash("hello");
assert_eq!(
hash,
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
);
}
#[test]
fn test_url_to_cache_path() {
let url = Url::parse("https://eure.dev/schema.eure").unwrap();
let cache_dir = PathBuf::from("/home/user/.cache/eure/schemas");
let path = url_to_cache_path(&url, &cache_dir);
assert!(path.starts_with(&cache_dir));
assert!(path.to_string_lossy().contains("eure.dev"));
}
}