#[cfg(feature = "cache-memory")]
pub mod memory;
#[cfg(feature = "cache-redis")]
pub mod redis;
use std::sync::Arc;
use std::time::Duration;
const DEFAULT_MEMORY_CACHE_SIZE: usize = 1000;
const DEFAULT_CRATE_DOCS_TTL_SECS: u64 = 3600;
const DEFAULT_ITEM_DOCS_TTL_SECS: u64 = 1800;
const DEFAULT_SEARCH_RESULTS_TTL_SECS: u64 = 300;
#[async_trait::async_trait]
pub trait Cache: Send + Sync {
async fn get(&self, key: &str) -> Option<Arc<str>>;
async fn set(
&self,
key: String,
value: String,
ttl: Option<Duration>,
) -> crate::error::Result<()>;
async fn delete(&self, key: &str) -> crate::error::Result<()>;
async fn clear(&self) -> crate::error::Result<()>;
async fn exists(&self, key: &str) -> bool;
fn as_any(&self) -> &dyn std::any::Any;
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct CacheConfig {
pub cache_type: String,
pub memory_size: Option<usize>,
pub redis_url: Option<String>,
#[serde(default = "default_key_prefix")]
pub key_prefix: String,
pub default_ttl: Option<u64>,
#[serde(default = "default_crate_docs_ttl")]
pub crate_docs_ttl_secs: Option<u64>,
#[serde(default = "default_item_docs_ttl")]
pub item_docs_ttl_secs: Option<u64>,
#[serde(default = "default_search_results_ttl")]
pub search_results_ttl_secs: Option<u64>,
}
#[must_use]
pub fn default_crate_docs_ttl() -> Option<u64> {
Some(DEFAULT_CRATE_DOCS_TTL_SECS)
}
#[must_use]
pub fn default_item_docs_ttl() -> Option<u64> {
Some(DEFAULT_ITEM_DOCS_TTL_SECS)
}
#[must_use]
pub fn default_search_results_ttl() -> Option<u64> {
Some(DEFAULT_SEARCH_RESULTS_TTL_SECS)
}
#[must_use]
pub fn default_key_prefix() -> String {
String::new()
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
cache_type: "memory".to_string(),
memory_size: Some(DEFAULT_MEMORY_CACHE_SIZE),
redis_url: None,
key_prefix: String::new(),
default_ttl: Some(DEFAULT_CRATE_DOCS_TTL_SECS),
crate_docs_ttl_secs: default_crate_docs_ttl(),
item_docs_ttl_secs: default_item_docs_ttl(),
search_results_ttl_secs: default_search_results_ttl(),
}
}
}
pub fn create_cache(config: &CacheConfig) -> Result<Box<dyn Cache>, crate::error::Error> {
match config.cache_type.as_str() {
"memory" => {
#[cfg(feature = "cache-memory")]
{
let size = config.memory_size.unwrap_or(DEFAULT_MEMORY_CACHE_SIZE);
Ok(Box::new(memory::MemoryCache::new(size)))
}
#[cfg(not(feature = "cache-memory"))]
{
Err(crate::error::Error::config(
"cache_type",
"memory cache feature is not enabled",
))
}
}
"redis" => {
#[cfg(feature = "cache-redis")]
{
Err(crate::error::Error::config(
"cache_type",
"Redis cache requires async initialization. Use create_cache_async instead.",
))
}
#[cfg(not(feature = "cache-redis"))]
{
Err(crate::error::Error::config(
"cache_type",
"redis cache feature is not enabled",
))
}
}
_ => Err(crate::error::Error::config(
"cache_type",
format!("unsupported cache type: {}", config.cache_type),
)),
}
}
#[cfg(feature = "cache-redis")]
pub async fn create_cache_async(
config: &CacheConfig,
) -> Result<Box<dyn Cache>, crate::error::Error> {
match config.cache_type.as_str() {
"memory" => {
let size = config.memory_size.unwrap_or(DEFAULT_MEMORY_CACHE_SIZE);
Ok(Box::new(memory::MemoryCache::new(size)))
}
"redis" => {
let url = config
.redis_url
.as_ref()
.ok_or_else(|| crate::error::Error::config("redis_url", "redis_url is required"))?;
Ok(Box::new(
redis::RedisCache::new(url, config.key_prefix.clone()).await?,
))
}
_ => Err(crate::error::Error::config(
"cache_type",
format!("unsupported cache type: {}", config.cache_type),
)),
}
}