use std::{
borrow::Cow,
fmt::{self, Display, Formatter},
};
const IMMUTABLE_MAX_AGE: u32 = 31536000;
const WELL_KNOWN_HEADER_VALUES: [(CacheControl, &str); 4] = [
(CacheControl::new(), ""),
(CacheControl::no_cache(), "no-cache"),
(CacheControl::no_store(), "no-store"),
(CacheControl::immutable(), "public, max-age=31536000, immutable"),
];
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub enum Cacheability {
#[default]
Unspecified,
Public,
Private,
NoStore,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct CacheControl {
pub cacheability: Cacheability,
pub max_age: Option<u32>,
pub s_max_age: Option<u32>,
pub no_cache: bool,
pub must_revalidate: bool,
pub proxy_revalidate: bool,
pub no_transform: bool,
pub immutable: bool,
pub stale_while_revalidate: Option<u32>,
pub stale_if_error: Option<u32>,
}
impl Default for CacheControl {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl CacheControl {
#[inline]
pub const fn new() -> Self {
Self {
cacheability: Cacheability::Unspecified,
max_age: None,
s_max_age: None,
no_cache: false,
must_revalidate: false,
proxy_revalidate: false,
no_transform: false,
immutable: false,
stale_while_revalidate: None,
stale_if_error: None,
}
}
#[inline]
pub const fn public(max_age: u32) -> Self {
let mut cache_control = Self::new();
cache_control.cacheability = Cacheability::Public;
cache_control.max_age = Some(max_age);
cache_control
}
#[inline]
pub const fn private(max_age: u32) -> Self {
let mut cache_control = Self::new();
cache_control.cacheability = Cacheability::Private;
cache_control.max_age = Some(max_age);
cache_control
}
#[inline]
pub const fn no_cache() -> Self {
let mut cache_control = Self::new();
cache_control.no_cache = true;
cache_control
}
#[inline]
pub const fn no_store() -> Self {
let mut cache_control = Self::new();
cache_control.cacheability = Cacheability::NoStore;
cache_control
}
#[inline]
pub const fn immutable() -> Self {
let mut cache_control = Self::public(IMMUTABLE_MAX_AGE);
cache_control.immutable = true;
cache_control
}
#[inline]
pub fn to_header_value(&self) -> Cow<'static, str> {
for (cache_control, header_value) in WELL_KNOWN_HEADER_VALUES {
if *self == cache_control {
return Cow::Borrowed(header_value);
}
}
Cow::Owned(self.to_string())
}
}
impl Display for CacheControl {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut separator = "";
match self.cacheability {
Cacheability::Unspecified => (),
Cacheability::Public => write_directive(f, &mut separator, "public")?,
Cacheability::Private => write_directive(f, &mut separator, "private")?,
Cacheability::NoStore => write_directive(f, &mut separator, "no-store")?,
}
if self.no_cache {
write_directive(f, &mut separator, "no-cache")?;
}
if let Some(max_age) = self.max_age {
write_seconds_directive(f, &mut separator, "max-age", max_age)?;
}
if let Some(s_max_age) = self.s_max_age {
write_seconds_directive(f, &mut separator, "s-maxage", s_max_age)?;
}
if self.must_revalidate {
write_directive(f, &mut separator, "must-revalidate")?;
}
if self.proxy_revalidate {
write_directive(f, &mut separator, "proxy-revalidate")?;
}
if self.no_transform {
write_directive(f, &mut separator, "no-transform")?;
}
if self.immutable {
write_directive(f, &mut separator, "immutable")?;
}
if let Some(stale_while_revalidate) = self.stale_while_revalidate {
write_seconds_directive(
f,
&mut separator,
"stale-while-revalidate",
stale_while_revalidate,
)?;
}
if let Some(stale_if_error) = self.stale_if_error {
write_seconds_directive(f, &mut separator, "stale-if-error", stale_if_error)?;
}
Ok(())
}
}
#[inline]
fn write_directive(
f: &mut Formatter<'_>,
separator: &mut &'static str,
directive: &str,
) -> fmt::Result {
f.write_str(separator)?;
f.write_str(directive)?;
*separator = ", ";
Ok(())
}
#[inline]
fn write_seconds_directive(
f: &mut Formatter<'_>,
separator: &mut &'static str,
directive: &str,
seconds: u32,
) -> fmt::Result {
write_directive(f, separator, directive)?;
write!(f, "={seconds}")
}