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