static TTL: std::sync::OnceLock<CacheTtl> = std::sync::OnceLock::new();
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CacheTtl {
FiveMinutes,
#[default]
OneHour,
}
impl CacheTtl {
pub fn wire_ttl(self) -> Option<&'static str> {
match self {
CacheTtl::FiveMinutes => None,
CacheTtl::OneHour => Some("1h"),
}
}
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_ascii_lowercase().as_str() {
"5m" | "5min" | "300" | "default" | "short" => Some(CacheTtl::FiveMinutes),
"1h" | "60m" | "3600" | "long" => Some(CacheTtl::OneHour),
_ => None,
}
}
}
pub fn init_from_config(ttl: Option<&str>) {
let resolved = match ttl {
None => CacheTtl::default(),
Some(raw) => CacheTtl::parse(raw).unwrap_or_else(|| {
tracing::warn!(
target: "dirge::provider",
value = %raw,
"unrecognized [prompt_cache] ttl; using the default (1h). Valid values: 5m, 1h",
);
CacheTtl::default()
}),
};
let _ = TTL.set(resolved);
}
pub fn ttl() -> CacheTtl {
if let Ok(raw) = std::env::var("DIRGE_PROMPT_CACHE_TTL")
&& let Some(parsed) = CacheTtl::parse(&raw)
{
return parsed;
}
TTL.get().copied().unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_the_two_wire_values_and_common_spellings() {
assert_eq!(CacheTtl::parse("5m"), Some(CacheTtl::FiveMinutes));
assert_eq!(CacheTtl::parse("1h"), Some(CacheTtl::OneHour));
assert_eq!(CacheTtl::parse(" 1H "), Some(CacheTtl::OneHour));
assert_eq!(CacheTtl::parse("60m"), Some(CacheTtl::OneHour));
assert_eq!(CacheTtl::parse("short"), Some(CacheTtl::FiveMinutes));
}
#[test]
fn rejects_values_the_api_would_reject() {
for raw in ["10m", "2h", "ephemeral", "", "true"] {
assert_eq!(CacheTtl::parse(raw), None, "{raw} must not parse");
}
}
#[test]
fn five_minutes_is_expressed_by_omitting_ttl() {
assert_eq!(CacheTtl::FiveMinutes.wire_ttl(), None);
assert_eq!(CacheTtl::OneHour.wire_ttl(), Some("1h"));
}
#[test]
fn default_is_one_hour() {
assert_eq!(CacheTtl::default(), CacheTtl::OneHour);
}
}