use crate::error::Result;
use async_trait::async_trait;
use std::time::Duration;
#[async_trait]
pub trait Cache: Send + Sync {
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>;
async fn set(&self, key: &str, value: Vec<u8>, ttl: Duration) -> Result<()>;
async fn delete(&self, key: &str) -> Result<()>;
async fn clear(&self) -> Result<()>;
fn is_enabled(&self) -> bool {
true
}
fn name(&self) -> &str {
"cache"
}
}
pub struct CacheKey;
impl CacheKey {
pub fn from_parts(provider: &str, model: &str, content: &str) -> String {
let mut hasher = blake3::Hasher::new();
hasher.update(provider.as_bytes());
hasher.update(model.as_bytes());
hasher.update(content.as_bytes());
let hash = hasher.finalize();
format!("{}:{}:{}", provider, model, hash.to_hex())
}
pub fn with_namespace(namespace: &str, key: &str) -> String {
format!("{}:{}", namespace, key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_key_from_parts() {
let key1 = CacheKey::from_parts("openai", "gpt-4", "Hello");
let key2 = CacheKey::from_parts("openai", "gpt-4", "Hello");
let key3 = CacheKey::from_parts("openai", "gpt-4", "Goodbye");
assert_eq!(key1, key2);
assert_ne!(key1, key3);
assert!(key1.starts_with("openai:"));
assert!(key1.contains("gpt-4"));
}
#[test]
fn test_cache_key_with_namespace() {
let key = CacheKey::with_namespace("responses", "abc123");
assert_eq!(key, "responses:abc123");
}
#[test]
fn test_cache_key_deterministic() {
let key1 = CacheKey::from_parts("test", "model", "content");
let key2 = CacheKey::from_parts("test", "model", "content");
assert_eq!(key1, key2);
}
#[test]
fn test_cache_object_safety() {
fn _assert_object_safe(_: &dyn Cache) {}
}
#[test]
fn test_cache_key_blake3_deterministic() {
let key1 = CacheKey::from_parts("openai", "gpt-4", "Hello, world!");
let key2 = CacheKey::from_parts("openai", "gpt-4", "Hello, world!");
assert_eq!(key1, key2, "Blake3 hashing should be deterministic");
}
#[test]
fn test_cache_key_blake3_collision_resistance() {
let key1 = CacheKey::from_parts("openai", "gpt-4", "Hello");
let key2 = CacheKey::from_parts("openai", "gpt-4", "Hello!");
let key3 = CacheKey::from_parts("openai", "gpt-3.5", "Hello");
let key4 = CacheKey::from_parts("anthropic", "gpt-4", "Hello");
assert_ne!(
key1, key2,
"Different content should produce different hashes"
);
assert_ne!(
key1, key3,
"Different models should produce different hashes"
);
assert_ne!(
key1, key4,
"Different providers should produce different hashes"
);
}
#[test]
fn test_cache_key_blake3_format() {
let key = CacheKey::from_parts("openai", "gpt-4", "test");
let parts: Vec<&str> = key.split(':').collect();
assert_eq!(parts.len(), 3, "Key should have 3 parts");
assert_eq!(parts[0], "openai", "First part should be provider");
assert_eq!(parts[1], "gpt-4", "Second part should be model");
assert_eq!(
parts[2].len(),
64,
"Blake3 hash should be 64 hex characters"
);
assert!(
parts[2].chars().all(|c| c.is_ascii_hexdigit()),
"Hash should be valid hex"
);
}
}