use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RlsEnforcement {
#[default]
Error,
Warn,
Off,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct CacheConfig {
pub enabled: bool,
pub max_entries: usize,
pub ttl_seconds: u64,
pub cache_list_queries: bool,
#[serde(default)]
pub rls_enforcement: RlsEnforcement,
#[serde(default)]
pub max_entry_bytes: Option<usize>,
#[serde(default)]
pub max_total_bytes: Option<usize>,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
enabled: false, max_entries: 10_000,
ttl_seconds: 86_400, cache_list_queries: true,
rls_enforcement: RlsEnforcement::Error,
max_entry_bytes: None,
max_total_bytes: None,
}
}
}
impl CacheConfig {
#[must_use]
pub const fn with_max_entries(max_entries: usize) -> Self {
Self {
enabled: false, max_entries,
ttl_seconds: 86_400,
cache_list_queries: true,
rls_enforcement: RlsEnforcement::Error,
max_entry_bytes: None,
max_total_bytes: None,
}
}
#[must_use]
pub const fn with_ttl(ttl_seconds: u64) -> Self {
Self {
enabled: false, max_entries: 10_000,
ttl_seconds,
cache_list_queries: true,
rls_enforcement: RlsEnforcement::Error,
max_entry_bytes: None,
max_total_bytes: None,
}
}
#[must_use]
pub const fn enabled() -> Self {
Self {
enabled: true,
max_entries: 10_000,
ttl_seconds: 86_400,
cache_list_queries: true,
rls_enforcement: RlsEnforcement::Error,
max_entry_bytes: None,
max_total_bytes: None,
}
}
#[must_use]
pub const fn disabled() -> Self {
Self {
enabled: false,
max_entries: 10_000,
ttl_seconds: 86_400,
cache_list_queries: true,
rls_enforcement: RlsEnforcement::Error,
max_entry_bytes: None,
max_total_bytes: None,
}
}
#[must_use]
pub const fn estimated_memory_bytes(&self) -> usize {
const AVG_ENTRY_SIZE_BYTES: usize = 10_000;
self.max_entries * AVG_ENTRY_SIZE_BYTES
}
}
impl From<bool> for CacheConfig {
fn from(enabled: bool) -> Self {
if enabled {
Self::enabled()
} else {
Self::disabled()
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_default_config() {
let config = CacheConfig::default();
assert!(!config.enabled); assert_eq!(config.max_entries, 10_000);
assert_eq!(config.ttl_seconds, 86_400);
assert!(config.cache_list_queries);
}
#[test]
fn test_with_max_entries() {
let config = CacheConfig::with_max_entries(50_000);
assert_eq!(config.max_entries, 50_000);
assert!(!config.enabled); assert_eq!(config.ttl_seconds, 86_400);
}
#[test]
fn test_with_ttl() {
let config = CacheConfig::with_ttl(3_600);
assert_eq!(config.ttl_seconds, 3_600);
assert!(!config.enabled); assert_eq!(config.max_entries, 10_000);
}
#[test]
fn test_enabled() {
let config = CacheConfig::enabled();
assert!(config.enabled);
assert_eq!(config.max_entries, 10_000);
assert_eq!(config.ttl_seconds, 86_400);
}
#[test]
fn test_disabled() {
let config = CacheConfig::disabled();
assert!(!config.enabled);
}
#[test]
fn test_estimated_memory() {
let config = CacheConfig::with_max_entries(10_000);
let estimated = config.estimated_memory_bytes();
assert_eq!(estimated, 100_000_000);
}
#[test]
fn test_from_bool_true() {
let config = CacheConfig::from(true);
assert!(config.enabled);
assert_eq!(config.max_entries, 10_000);
assert_eq!(config.ttl_seconds, 86_400);
}
#[test]
fn test_from_bool_false() {
let config = CacheConfig::from(false);
assert!(!config.enabled);
}
#[test]
fn test_serialization() {
let config = CacheConfig::default();
let json = serde_json::to_string(&config).unwrap();
let deserialized: CacheConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.enabled, deserialized.enabled);
assert_eq!(config.max_entries, deserialized.max_entries);
assert_eq!(config.ttl_seconds, deserialized.ttl_seconds);
}
}