use std::time::Duration;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct CacheLimitsConfig {
#[serde(default = "default_max_entries")]
pub max_entries: u64,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize"
)]
#[schemars(with = "Option<String>")]
pub time_to_live: Option<Duration>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "humantime_serde::deserialize",
serialize_with = "humantime_serde::serialize"
)]
#[schemars(with = "Option<String>")]
pub time_to_idle: Option<Duration>,
}
impl Default for CacheLimitsConfig {
fn default() -> Self {
Self {
max_entries: default_max_entries(),
time_to_live: None,
time_to_idle: None,
}
}
}
impl CacheLimitsConfig {
pub fn with_max_entries(mut self, max_entries: u64) -> Self {
self.max_entries = max_entries;
self
}
pub fn with_time_to_live(mut self, time_to_live: Option<Duration>) -> Self {
self.time_to_live = time_to_live;
self
}
pub fn with_time_to_idle(mut self, time_to_idle: Option<Duration>) -> Self {
self.time_to_idle = time_to_idle;
self
}
}
fn default_max_entries() -> u64 {
1000
}
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct CacheConfig {
#[serde(default)]
pub router: RouterCacheConfig,
#[serde(default)]
pub supergraph: SupergraphCacheConfig,
}
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct RouterCacheConfig {
#[serde(default)]
pub parsing: CacheLimitsConfig,
}
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct SupergraphCacheConfig {
#[serde(default)]
pub validation: CacheLimitsConfig,
#[serde(default)]
pub normalization: CacheLimitsConfig,
#[serde(default)]
pub query_plans: CacheLimitsConfig,
}
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct SupergraphCacheOverrides {
pub validation: CacheOverride,
pub normalization: CacheOverride,
pub query_plans: CacheOverride,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub(crate) enum CacheSetting<T> {
#[default]
Inherit,
Override(T),
}
impl<T: Copy> CacheSetting<T> {
fn resolve(&self, inherited: T) -> T {
match self {
Self::Inherit => inherited,
Self::Override(value) => *value,
}
}
}
#[derive(Debug, Default, Clone)]
pub struct CacheOverride {
max_entries: CacheSetting<u64>,
time_to_live: CacheSetting<Option<Duration>>,
time_to_idle: CacheSetting<Option<Duration>>,
}
impl CacheOverride {
pub fn set_max_entries(&mut self, max_entries: u64) -> &mut Self {
self.max_entries = CacheSetting::Override(max_entries);
self
}
pub fn set_time_to_live(&mut self, time_to_live: Option<Duration>) -> &mut Self {
self.time_to_live = CacheSetting::Override(time_to_live);
self
}
pub fn set_time_to_idle(&mut self, time_to_idle: Option<Duration>) -> &mut Self {
self.time_to_idle = CacheSetting::Override(time_to_idle);
self
}
pub fn disable(&mut self) -> &mut Self {
self.set_max_entries(0)
}
pub fn inherit(&mut self) -> &mut Self {
*self = Self::default();
self
}
pub(crate) fn resolve(&self, inherited: &CacheLimitsConfig) -> CacheLimitsConfig {
CacheLimitsConfig {
max_entries: self.max_entries.resolve(inherited.max_entries),
time_to_live: self.time_to_live.resolve(inherited.time_to_live),
time_to_idle: self.time_to_idle.resolve(inherited.time_to_idle),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_default_every_cache_when_config_is_empty() {
let from_empty: CacheConfig = serde_json::from_str("{}").unwrap();
assert_eq!(from_empty, CacheConfig::default());
assert_eq!(from_empty.router.parsing.max_entries, 1000);
assert_eq!(from_empty.router.parsing.time_to_live, None);
assert_eq!(from_empty.router.parsing.time_to_idle, None);
assert_eq!(from_empty.supergraph.query_plans.max_entries, 1000);
assert_eq!(from_empty.supergraph.query_plans.time_to_live, None);
assert_eq!(from_empty.supergraph.query_plans.time_to_idle, None);
}
#[test]
fn should_keep_defaults_for_unset_siblings() {
let config: CacheConfig =
serde_json::from_str(r#"{"supergraph": {"query_plans": {"max_entries": 5}}}"#).unwrap();
assert_eq!(config.supergraph.query_plans.max_entries, 5);
assert_eq!(config.supergraph.query_plans.time_to_live, None);
assert_eq!(config.supergraph.query_plans.time_to_idle, None);
assert_eq!(config.supergraph.validation.max_entries, 1000);
assert_eq!(config.router.parsing.max_entries, 1000);
}
#[test]
fn should_parse_ttl_and_tti() {
let config: CacheConfig = serde_json::from_str(
r#"{"supergraph": {"query_plans": {"max_entries": 5, "time_to_live": "30m", "time_to_idle": "5m"}}}"#,
)
.unwrap();
assert_eq!(config.supergraph.query_plans.max_entries, 5);
assert_eq!(
config.supergraph.query_plans.time_to_live,
Some(Duration::from_secs(30 * 60))
);
assert_eq!(
config.supergraph.query_plans.time_to_idle,
Some(Duration::from_secs(5 * 60))
);
assert_eq!(config.supergraph.validation.time_to_live, None);
assert_eq!(config.supergraph.validation.time_to_idle, None);
}
#[test]
fn should_resolve_each_override_dimension_independently() {
let inherited = CacheLimitsConfig::default()
.with_max_entries(9)
.with_time_to_live(Some(Duration::from_secs(1800)))
.with_time_to_idle(Some(Duration::from_secs(300)));
assert_eq!(CacheOverride::default().resolve(&inherited), inherited);
let mut ttl_only = CacheOverride::default();
ttl_only.set_time_to_live(Some(Duration::from_secs(60)));
let resolved = ttl_only.resolve(&inherited);
assert_eq!(resolved.max_entries, 9);
assert_eq!(resolved.time_to_live, Some(Duration::from_secs(60)));
assert_eq!(resolved.time_to_idle, Some(Duration::from_secs(300)));
let mut disabled = CacheOverride::default();
disabled.set_time_to_live(None).set_time_to_idle(None);
let resolved = disabled.resolve(&inherited);
assert_eq!(resolved.max_entries, 9);
assert_eq!(resolved.time_to_live, None);
assert_eq!(resolved.time_to_idle, None);
}
}