trillium_caching_headers/
cache_control.rs1use std::{
2 fmt::{Display, Write},
3 ops::{Deref, DerefMut},
4 str::FromStr,
5 time::Duration,
6};
7use trillium::{async_trait, Conn, Handler, HeaderValues, KnownHeaderName};
8use CacheControlDirective::*;
9#[derive(Debug, Clone, Eq, PartialEq)]
15#[non_exhaustive]
16pub enum CacheControlDirective {
17 Immutable,
19
20 MaxAge(Duration),
22
23 MaxFresh(Duration),
25
26 MaxStale(Option<Duration>),
28
29 MustRevalidate,
31
32 NoCache,
34
35 NoStore,
37
38 NoTransform,
40
41 OnlyIfCached,
43
44 Private,
46
47 ProxyRevalidate,
49
50 Public,
52
53 SMaxage(Duration),
55
56 StaleIfError(Duration),
58
59 StaleWhileRevalidate(Duration),
61
62 UnknownDirective(String),
64}
65
66#[async_trait]
67impl Handler for CacheControlDirective {
68 async fn run(&self, conn: Conn) -> Conn {
69 conn.with_response_header(KnownHeaderName::CacheControl, self.clone())
70 }
71}
72
73#[async_trait]
74impl Handler for CacheControlHeader {
75 async fn run(&self, conn: Conn) -> Conn {
76 conn.with_response_header(KnownHeaderName::CacheControl, self.clone())
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct CacheControlHeader(Vec<CacheControlDirective>);
87
88pub fn cache_control(into: impl Into<CacheControlHeader>) -> CacheControlHeader {
90 into.into()
91}
92
93impl<T> From<T> for CacheControlHeader
94where
95 T: IntoIterator<Item = CacheControlDirective>,
96{
97 fn from(directives: T) -> Self {
98 directives.into_iter().collect()
99 }
100}
101
102impl From<CacheControlDirective> for CacheControlHeader {
103 fn from(directive: CacheControlDirective) -> Self {
104 Self(vec![directive])
105 }
106}
107
108impl FromIterator<CacheControlDirective> for CacheControlHeader {
109 fn from_iter<T: IntoIterator<Item = CacheControlDirective>>(iter: T) -> Self {
110 Self(iter.into_iter().collect())
111 }
112}
113
114impl From<CacheControlDirective> for HeaderValues {
115 fn from(ccd: CacheControlDirective) -> HeaderValues {
116 let header: CacheControlHeader = ccd.into();
117 header.into()
118 }
119}
120
121impl From<CacheControlHeader> for HeaderValues {
122 fn from(cch: CacheControlHeader) -> Self {
123 cch.to_string().into()
124 }
125}
126
127impl CacheControlHeader {
128 pub fn new(into: impl Into<Self>) -> Self {
130 into.into()
131 }
132
133 pub fn is_immutable(&self) -> bool {
135 self.contains(&Immutable)
136 }
137
138 pub fn max_age(&self) -> Option<Duration> {
140 self.iter().find_map(|d| match d {
141 MaxAge(d) => Some(*d),
142 _ => None,
143 })
144 }
145
146 pub fn max_fresh(&self) -> Option<Duration> {
148 self.iter().find_map(|d| match d {
149 MaxFresh(d) => Some(*d),
150 _ => None,
151 })
152 }
153
154 pub fn max_stale(&self) -> Option<Option<Duration>> {
160 self.iter().find_map(|d| match d {
161 MaxStale(d) => Some(*d),
162 _ => None,
163 })
164 }
165
166 pub fn must_revalidate(&self) -> bool {
168 self.contains(&MustRevalidate)
169 }
170
171 pub fn is_no_cache(&self) -> bool {
173 self.contains(&NoCache)
174 }
175
176 pub fn is_no_store(&self) -> bool {
178 self.contains(&NoStore)
179 }
180
181 pub fn is_no_transform(&self) -> bool {
184 self.contains(&NoTransform)
185 }
186
187 pub fn is_only_if_cached(&self) -> bool {
190 self.contains(&OnlyIfCached)
191 }
192
193 pub fn is_private(&self) -> bool {
195 self.contains(&Private)
196 }
197
198 pub fn is_proxy_revalidate(&self) -> bool {
201 self.contains(&ProxyRevalidate)
202 }
203
204 pub fn is_public(&self) -> bool {
206 self.contains(&Public)
207 }
208
209 pub fn s_maxage(&self) -> Option<Duration> {
212 self.iter().find_map(|h| match h {
213 SMaxage(d) => Some(*d),
214 _ => None,
215 })
216 }
217
218 pub fn stale_if_error(&self) -> Option<Duration> {
221 self.iter().find_map(|h| match h {
222 StaleIfError(d) => Some(*d),
223 _ => None,
224 })
225 }
226
227 pub fn stale_while_revalidate(&self) -> Option<Duration> {
230 self.iter().find_map(|h| match h {
231 StaleWhileRevalidate(d) => Some(*d),
232 _ => None,
233 })
234 }
235}
236
237impl Deref for CacheControlHeader {
238 type Target = [CacheControlDirective];
239
240 fn deref(&self) -> &Self::Target {
241 self.0.as_slice()
242 }
243}
244
245impl DerefMut for CacheControlHeader {
246 fn deref_mut(&mut self) -> &mut Self::Target {
247 self.0.as_mut_slice()
248 }
249}
250
251#[derive(Debug, Clone, Copy)]
252pub struct CacheControlParseError;
253impl std::error::Error for CacheControlParseError {}
254impl Display for CacheControlParseError {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 f.write_str("cache control parse error")
257 }
258}
259
260impl Display for CacheControlHeader {
261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 let mut first = true;
263 for directive in &self.0 {
264 if first {
265 first = false;
266 } else {
267 f.write_char(',')?;
268 }
269
270 match directive {
271 Immutable => write!(f, "immutable"),
272 MaxAge(d) => write!(f, "max-age={}", d.as_secs()),
273 MaxFresh(d) => write!(f, "max-fresh={}", d.as_secs()),
274 MaxStale(Some(d)) => write!(f, "max-stale={}", d.as_secs()),
275 MaxStale(None) => write!(f, "max-stale"),
276 MustRevalidate => write!(f, "must-revalidate"),
277 NoCache => write!(f, "no-cache"),
278 NoStore => write!(f, "no-store"),
279 NoTransform => write!(f, "no-transform"),
280 OnlyIfCached => write!(f, "only-if-cached"),
281 Private => write!(f, "private"),
282 ProxyRevalidate => write!(f, "proxy-revalidate"),
283 Public => write!(f, "public"),
284 SMaxage(d) => write!(f, "s-maxage={}", d.as_secs()),
285 StaleIfError(d) => write!(f, "stale-if-error={}", d.as_secs()),
286 StaleWhileRevalidate(d) => write!(f, "stale-while-revalidate={}", d.as_secs()),
287 UnknownDirective(directive) => write!(f, "{directive}"),
288 }?;
289 }
290
291 Ok(())
292 }
293}
294
295impl FromStr for CacheControlHeader {
296 type Err = CacheControlParseError;
297
298 fn from_str(s: &str) -> Result<Self, Self::Err> {
299 s.to_ascii_lowercase()
300 .split(',')
301 .map(str::trim)
302 .map(|directive| match directive {
303 "immutable" => Ok(Immutable),
304 "must-revalidate" => Ok(MustRevalidate),
305 "no-cache" => Ok(NoCache),
306 "no-store" => Ok(NoStore),
307 "no-transform" => Ok(NoTransform),
308 "only-if-cached" => Ok(OnlyIfCached),
309 "private" => Ok(Private),
310 "proxy-revalidate" => Ok(ProxyRevalidate),
311 "public" => Ok(Public),
312 "max-stale" => Ok(MaxStale(None)),
313 other => match other.split_once('=') {
314 Some((directive, number)) => {
315 let seconds = number.parse().map_err(|_| CacheControlParseError)?;
316 let seconds = Duration::from_secs(seconds);
317 match directive {
318 "max-age" => Ok(MaxAge(seconds)),
319 "max-fresh" => Ok(MaxFresh(seconds)),
320 "max-stale" => Ok(MaxStale(Some(seconds))),
321 "s-maxage" => Ok(SMaxage(seconds)),
322 "stale-if-error" => Ok(StaleIfError(seconds)),
323 "stale-while-revalidate" => Ok(StaleWhileRevalidate(seconds)),
324 _ => Ok(UnknownDirective(String::from(other))),
325 }
326 }
327
328 None => Ok(UnknownDirective(String::from(other))),
329 },
330 })
331 .collect::<Result<Vec<_>, _>>()
332 .map(Self)
333 }
334}
335#[cfg(test)]
336mod test {
337 use super::*;
338 #[test]
339 fn parse() {
340 assert_eq!(
341 CacheControlHeader(vec![NoStore]),
342 "no-store".parse().unwrap()
343 );
344
345 let long = "private,no-cache,no-store,max-age=0,must-revalidate,pre-check=0,post-check=0"
346 .parse()
347 .unwrap();
348
349 assert_eq!(
350 CacheControlHeader::from([
351 Private,
352 NoCache,
353 NoStore,
354 MaxAge(Duration::ZERO),
355 MustRevalidate,
356 UnknownDirective("pre-check=0".to_string()),
357 UnknownDirective("post-check=0".to_string())
358 ]),
359 long
360 );
361
362 assert_eq!(
363 long.to_string(),
364 "private,no-cache,no-store,max-age=0,must-revalidate,pre-check=0,post-check=0"
365 );
366
367 assert_eq!(
368 CacheControlHeader::from([Public, MaxAge(Duration::from_secs(604800)), Immutable]),
369 "public, max-age=604800, immutable".parse().unwrap()
370 );
371 }
372}