http-types/src/cache/cache_control/cache_directive.rs

157 lines
5.8 KiB
Rust

use crate::headers::HeaderValue;
use crate::Status;
use std::time::Duration;
/// An HTTP `Cache-Control` directive.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CacheDirective {
/// The response body will not change over time.
Immutable,
/// The maximum amount of time a resource is considered fresh.
MaxAge(Duration),
/// Indicates the client will accept a stale response.
MaxStale(Option<Duration>),
/// A response that will still be fresh for at least the specified duration.
MinFresh(Duration),
/// Once a response is stale, a fresh response must be retrieved.
MustRevalidate,
/// The response may be cached, but must always be revalidated before being used.
NoCache,
/// The response may not be cached.
NoStore,
/// An intermediate cache or proxy should not edit the response body,
/// Content-Encoding, Content-Range, or Content-Type.
NoTransform,
/// Do not use the network for a response.
OnlyIfCached,
/// The response may be stored only by a browser's cache, even if the
/// response is normally non-cacheable.
Private,
/// Like must-revalidate, but only for shared caches (e.g., proxies).
ProxyRevalidate,
/// The response may be stored by any cache, even if the response is normally
/// non-cacheable.
Public,
/// Overrides max-age or the Expires header, but only for shared caches.
SMaxAge(Duration),
/// The client will accept a stale response if retrieving a fresh one fails.
StaleIfError(Duration),
/// Indicates the client will accept a stale response, while asynchronously
/// checking in the background for a fresh one.
StaleWhileRevalidate(Duration),
}
impl CacheDirective {
/// Check whether this directive is valid in an HTTP request.
pub fn valid_in_req(&self) -> bool {
use CacheDirective::*;
matches!(
self,
MaxAge(_) | MaxStale(_) | MinFresh(_) | NoCache | NoStore | NoTransform | OnlyIfCached
)
}
/// Check whether this directive is valid in an HTTP response.
pub fn valid_in_res(&self) -> bool {
use CacheDirective::*;
matches!(
self,
MustRevalidate
| NoCache
| NoStore
| NoTransform
| Public
| Private
| ProxyRevalidate
| MaxAge(_)
| SMaxAge(_)
| StaleIfError(_)
| StaleWhileRevalidate(_)
)
}
/// Create an instance from a string slice.
//
// This is a private method rather than a trait because we assume the
// input string is a single-value only. This is upheld by the calling
// function, but we cannot guarantee this to be true in the general
// sense.
pub(crate) fn from_str(s: &str) -> crate::Result<Option<Self>> {
use CacheDirective::*;
let s = s.trim();
// We're dealing with an empty string.
if s.is_empty() {
return Ok(None);
}
let s = s.to_lowercase();
let mut parts = s.split('=');
let next = parts.next().unwrap();
let mut get_dur = || -> crate::Result<Duration> {
let dur = parts.next().status(400)?;
let dur: u64 = dur.parse::<u64>().status(400)?;
Ok(Duration::new(dur, 0))
};
// This won't panic because each input string has at least one part.
let res = match next {
"immutable" => Some(Immutable),
"no-cache" => Some(NoCache),
"no-store" => Some(NoStore),
"no-transform" => Some(NoTransform),
"only-if-cached" => Some(OnlyIfCached),
"must-revalidate" => Some(MustRevalidate),
"public" => Some(Public),
"private" => Some(Private),
"proxy-revalidate" => Some(ProxyRevalidate),
"max-age" => Some(MaxAge(get_dur()?)),
"max-stale" => match parts.next() {
Some(secs) => {
let dur: u64 = secs.parse::<u64>().status(400)?;
Some(MaxStale(Some(Duration::new(dur, 0))))
}
None => Some(MaxStale(None)),
},
"min-fresh" => Some(MinFresh(get_dur()?)),
"s-maxage" => Some(SMaxAge(get_dur()?)),
"stale-if-error" => Some(StaleIfError(get_dur()?)),
"stale-while-revalidate" => Some(StaleWhileRevalidate(get_dur()?)),
_ => None,
};
Ok(res)
}
}
impl From<CacheDirective> for HeaderValue {
fn from(directive: CacheDirective) -> Self {
use CacheDirective::*;
let h = |s: String| unsafe { HeaderValue::from_bytes_unchecked(s.into_bytes()) };
match directive {
Immutable => h("immutable".to_string()),
MaxAge(dur) => h(format!("max-age={}", dur.as_secs())),
MaxStale(dur) => match dur {
Some(dur) => h(format!("max-stale={}", dur.as_secs())),
None => h("max-stale".to_string()),
},
MinFresh(dur) => h(format!("min-fresh={}", dur.as_secs())),
MustRevalidate => h("must-revalidate".to_string()),
NoCache => h("no-cache".to_string()),
NoStore => h("no-store".to_string()),
NoTransform => h("no-transform".to_string()),
OnlyIfCached => h("only-if-cached".to_string()),
Private => h("private".to_string()),
ProxyRevalidate => h("proxy-revalidate".to_string()),
Public => h("public".to_string()),
SMaxAge(dur) => h(format!("s-max-age={}", dur.as_secs())),
StaleIfError(dur) => h(format!("stale-if-error={}", dur.as_secs())),
StaleWhileRevalidate(dur) => h(format!("stale-while-revalidate={}", dur.as_secs())),
}
}
}