use std::time::Duration;
use crate::client::{CacheKit, CacheKitBuilder, SharedBackend};
use crate::error::CachekitError;
#[cfg(not(any(target_arch = "wasm32", feature = "unsync")))]
fn wrap(b: impl crate::backend::Backend + 'static) -> SharedBackend {
std::sync::Arc::new(b)
}
#[cfg(any(target_arch = "wasm32", feature = "unsync"))]
fn wrap(b: impl crate::backend::Backend + 'static) -> SharedBackend {
std::rc::Rc::new(b)
}
#[cfg(feature = "redis")]
fn minimal_defaults(backend: SharedBackend) -> CacheKitBuilder {
let builder = CacheKitBuilder::default()
.backend(backend)
.default_ttl(Duration::from_secs(300))
.l1_capacity(1000);
#[cfg(all(feature = "l1", not(feature = "unsync"), not(target_arch = "wasm32")))]
let builder = builder.swr_enabled(false);
builder
}
#[cfg(all(feature = "redis", feature = "encryption"))]
fn secure_defaults(master_key_hex: &str, source: &str) -> Result<CacheKitBuilder, CachekitError> {
let master_key = crate::config::decode_master_key_hex(master_key_hex, source)?;
let builder = CacheKitBuilder::default()
.default_ttl(Duration::from_secs(600))
.l1_capacity(1000)
.encryption_from_bytes(&master_key, "default")?;
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
let builder = builder.reliability(crate::reliability::ReliabilityConfig::default());
Ok(builder)
}
#[cfg(all(feature = "redis", feature = "encryption"))]
fn master_key_hex_from_env() -> Result<zeroize::Zeroizing<String>, CachekitError> {
std::env::var("CACHEKIT_MASTER_KEY")
.ok()
.map(zeroize::Zeroizing::new)
.filter(|k| !k.is_empty())
.ok_or_else(|| {
CachekitError::Config(
"CACHEKIT_MASTER_KEY is unset or empty: set it to a 64-hex-char key or pass \
the key to CacheKit::secure(url, master_key_hex)"
.to_owned(),
)
})
}
#[cfg(all(feature = "redis", feature = "encryption"))]
async fn connect_secure(
redis_url: &str,
builder: CacheKitBuilder,
) -> Result<CacheKitBuilder, CachekitError> {
let backend = crate::backend::redis::RedisBackend::builder()
.url(redis_url)
.auto_reconnect()
.build()?;
drop(backend.connect().await?);
Ok(builder.backend(wrap(backend)))
}
impl CacheKit {
#[cfg(feature = "redis")]
pub async fn minimal(redis_url: &str) -> Result<CacheKitBuilder, CachekitError> {
let backend = crate::backend::redis::RedisBackend::builder()
.url(redis_url)
.build()?;
drop(backend.connect().await?);
Ok(minimal_defaults(wrap(backend)))
}
#[cfg(feature = "redis")]
pub async fn production(redis_url: &str) -> Result<CacheKitBuilder, CachekitError> {
let backend = crate::backend::redis::RedisBackend::builder()
.url(redis_url)
.auto_reconnect()
.build()?;
drop(backend.connect().await?);
let builder = CacheKitBuilder::default()
.backend(wrap(backend))
.default_ttl(Duration::from_secs(600))
.l1_capacity(1000);
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
let builder = builder.reliability(crate::reliability::ReliabilityConfig::default());
Ok(builder)
}
#[cfg(all(feature = "redis", feature = "encryption"))]
pub async fn secure(
redis_url: &str,
master_key_hex: &str,
) -> Result<CacheKitBuilder, CachekitError> {
connect_secure(
redis_url,
secure_defaults(master_key_hex, "master_key_hex")?,
)
.await
}
#[cfg(all(feature = "redis", feature = "encryption"))]
pub async fn secure_from_env(redis_url: &str) -> Result<CacheKitBuilder, CachekitError> {
let master_key_hex = master_key_hex_from_env()?;
connect_secure(
redis_url,
secure_defaults(&master_key_hex, "CACHEKIT_MASTER_KEY")?,
)
.await
}
#[cfg(all(feature = "cachekitio", not(target_arch = "wasm32")))]
pub fn io(api_key: &str) -> Result<CacheKitBuilder, CachekitError> {
let backend = crate::backend::cachekitio::CachekitIO::builder()
.api_key(api_key)
.build()?;
let builder = CacheKitBuilder::default()
.backend(wrap(backend))
.default_ttl(Duration::from_secs(3600))
.l1_capacity(1000);
#[cfg(all(feature = "reliability", not(target_arch = "wasm32")))]
let builder = builder.reliability(crate::reliability::ReliabilityConfig::default());
Ok(builder)
}
#[cfg(all(feature = "cachekitio", not(target_arch = "wasm32")))]
pub fn io_from_env() -> Result<CacheKitBuilder, CachekitError> {
let api_key = std::env::var("CACHEKIT_API_KEY")
.ok()
.map(zeroize::Zeroizing::new)
.filter(|k| !k.is_empty())
.ok_or_else(|| {
CachekitError::Config(
"CACHEKIT_API_KEY is unset or empty: set it or pass the key to CacheKit::io(api_key)"
.to_owned(),
)
})?;
Self::io(&api_key)
}
}
#[cfg(all(test, feature = "redis", feature = "l1"))]
#[allow(clippy::expect_used)] mod tests {
#[test]
fn minimal_enables_l1_without_swr() {
let backend = crate::backend::redis::RedisBackend::builder()
.url("redis://localhost:6379")
.build()
.expect("valid URL");
let cache = super::minimal_defaults(super::wrap(backend))
.build()
.expect("minimal defaults must build");
assert!(
cache.l1.is_some(),
"spec/intent-presets.md § L1 Posture: minimal MUST enable L1"
);
#[cfg(all(not(feature = "unsync"), not(target_arch = "wasm32")))]
assert!(
!cache.swr_enabled,
"spec/intent-presets.md § L1 Posture: minimal MUST NOT enable SWR"
);
}
}
#[cfg(all(test, feature = "redis", feature = "encryption"))]
#[allow(clippy::expect_used)] mod secure_tests {
const MASTER_KEY_HEX: &str = "6161616161616161616161616161616161616161616161616161616161616161";
const CACHE_KEY: &str =
"users:get_user:61598716255080080f6456eb065c2e51badfaa4320b0efe97469c29cffee8875";
const CIPHERTEXT_HEX: &str =
"0d0e0f1011121314151617183c29af318238925ee76d081934adce133c0b4a7c5eb5704102b04582dcbf278ffd"; const PLAINTEXT_HEX: &str = "82a36167651ea46e616d65a5616c696365";
fn assert_decrypts_default_tenant_vector(builder: crate::CacheKitBuilder) {
let layer = builder
.encryption
.expect("secure preset must configure encryption");
assert_eq!(layer.tenant_id(), "default");
let ciphertext = hex::decode(CIPHERTEXT_HEX).expect("vector hex");
let plaintext = layer
.decrypt(&ciphertext, CACHE_KEY)
.expect("default-tenant vector must decrypt");
assert_eq!(hex::encode(plaintext), PLAINTEXT_HEX);
}
#[test]
fn hex_path_decrypts_default_tenant_vector() {
assert_decrypts_default_tenant_vector(
super::secure_defaults(MASTER_KEY_HEX, "master_key_hex").expect("valid key"),
);
}
#[test]
#[serial_test::serial]
fn env_path_decrypts_default_tenant_vector() {
let saved = std::env::var_os("CACHEKIT_MASTER_KEY");
std::env::set_var("CACHEKIT_MASTER_KEY", MASTER_KEY_HEX);
let resolved = super::master_key_hex_from_env();
match saved {
Some(v) => std::env::set_var("CACHEKIT_MASTER_KEY", v),
None => std::env::remove_var("CACHEKIT_MASTER_KEY"),
}
let master_key_hex = resolved.expect("CACHEKIT_MASTER_KEY is set");
assert_decrypts_default_tenant_vector(
super::secure_defaults(&master_key_hex, "CACHEKIT_MASTER_KEY").expect("valid key"),
);
}
}