use std::fmt::Write;
use axum::http::{HeaderMap, HeaderValue};
pub struct CacheContext {
pub is_get: bool,
pub has_auth: bool,
pub query_ttl: Option<u64>,
pub default_ttl: u64,
pub cdn_max_age: Option<u64>,
}
pub fn apply_cache_headers(headers: &mut HeaderMap, ctx: &CacheContext) {
if ctx.is_get {
let max_age = ctx.query_ttl.unwrap_or(ctx.default_ttl);
let visibility = if ctx.has_auth { "private" } else { "public" };
let mut value = format!("{visibility}, max-age={max_age}");
if !ctx.has_auth {
if let Some(s_maxage) = ctx.cdn_max_age {
write!(value, ", s-maxage={s_maxage}").expect("write to String");
}
}
if let Ok(val) = HeaderValue::from_str(&value) {
headers.insert("cache-control", val);
}
headers.insert("vary", HeaderValue::from_static("Authorization, Accept, Prefer"));
} else {
headers.insert("cache-control", HeaderValue::from_static("no-store"));
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
#[test]
fn get_public_default_ttl() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: true,
has_auth: false,
query_ttl: None,
default_ttl: 60,
cdn_max_age: None,
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "public, max-age=60");
assert_eq!(headers.get("vary").unwrap().to_str().unwrap(), "Authorization, Accept, Prefer");
}
#[test]
fn get_private_with_auth() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: true,
has_auth: true,
query_ttl: None,
default_ttl: 60,
cdn_max_age: None,
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "private, max-age=60");
}
#[test]
fn get_custom_ttl_from_query() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: true,
has_auth: false,
query_ttl: Some(120),
default_ttl: 60,
cdn_max_age: None,
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "public, max-age=120");
}
#[test]
fn mutation_no_store() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: false,
has_auth: false,
query_ttl: None,
default_ttl: 60,
cdn_max_age: None,
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "no-store");
assert!(headers.get("vary").is_none());
}
#[test]
fn mutation_no_store_with_auth() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: false,
has_auth: true,
query_ttl: None,
default_ttl: 60,
cdn_max_age: None,
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "no-store");
}
#[test]
fn zero_ttl_disables_caching() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: true,
has_auth: false,
query_ttl: Some(0),
default_ttl: 60,
cdn_max_age: None,
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "public, max-age=0");
}
#[test]
fn s_maxage_on_public_get() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: true,
has_auth: false,
query_ttl: None,
default_ttl: 60,
cdn_max_age: Some(300),
},
);
assert_eq!(
headers.get("cache-control").unwrap().to_str().unwrap(),
"public, max-age=60, s-maxage=300"
);
}
#[test]
fn no_s_maxage_on_private_get() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: true,
has_auth: true,
query_ttl: None,
default_ttl: 60,
cdn_max_age: Some(300),
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "private, max-age=60");
}
#[test]
fn no_s_maxage_when_none() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: true,
has_auth: false,
query_ttl: None,
default_ttl: 60,
cdn_max_age: None,
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "public, max-age=60");
}
#[test]
fn no_s_maxage_on_mutations() {
let mut headers = HeaderMap::new();
apply_cache_headers(
&mut headers,
&CacheContext {
is_get: false,
has_auth: false,
query_ttl: None,
default_ttl: 60,
cdn_max_age: Some(300),
},
);
assert_eq!(headers.get("cache-control").unwrap().to_str().unwrap(), "no-store");
}
}