Skip to main content

trillium_caching_headers/
caching_conn_ext.rs

1use crate::CacheControlHeader;
2use etag::EntityTag;
3use std::{str::FromStr, time::SystemTime};
4use trillium::{Conn, HeaderName, KnownHeaderName};
5
6/// Provides an extension trait for both [`trillium::Headers`] and
7/// also [`trillium::Conn`] for setting and getting various parsed
8/// caching headers.
9pub trait CachingHeadersExt: Sized {
10    /// returns an [`EntityTag`] if these headers contain an `Etag` header.
11    fn etag(&self) -> Option<EntityTag>;
12
13    /// sets an etag header from an EntityTag.
14    fn set_etag(&mut self, entity_tag: &EntityTag);
15
16    /// returns a parsed timestamp if these headers contain a `Last-Modified` header.
17    fn last_modified(&self) -> Option<SystemTime>;
18
19    /// sets a formatted `Last-Modified` header from a timestamp.
20    fn set_last_modified(&mut self, system_time: SystemTime);
21
22    /// Returns a parsed [`CacheControlHeader`] if these headers include a `Cache-Control`
23    /// header. Note that if this is called on a [`Conn`], it returns the *request's*
24    /// [`Cache-Control`](CacheControlHeader) header.
25    fn cache_control(&self) -> Option<CacheControlHeader>;
26
27    /// sets a `Cache-Control` header on these headers. Note that this
28    /// is valid in both request and response contexts, and specific
29    /// directives have different meanings.
30    fn set_cache_control(&mut self, cache_control: impl Into<CacheControlHeader>);
31
32    /// Returns a parsed [`CacheControlHeader`] if these headers include a `CDN-Cache-Control`
33    /// header (RFC 9213). Unlike [`cache_control`](Self::cache_control), `CDN-Cache-Control`
34    /// is response-only by spec, so when called on a [`Conn`] this reads the *response* headers.
35    fn cdn_cache_control(&self) -> Option<CacheControlHeader>;
36
37    /// Sets a `CDN-Cache-Control` (RFC 9213) header on these headers. Targeted at CDN /
38    /// shared-intermediary caches, ignored by other caches.
39    fn set_cdn_cache_control(&mut self, cache_control: impl Into<CacheControlHeader>);
40
41    /// returns a parsed `If-Modified-Since` header if one exists
42    fn if_modified_since(&self) -> Option<SystemTime>;
43
44    /// returns a parsed [`EntityTag`] header if there is an `If-None-Match` header.
45    fn if_none_match(&self) -> Option<EntityTag>;
46
47    /// sets the Vary header to a collection of `Into<HeaderName>`
48    fn set_vary<I, N>(&mut self, vary: I)
49    where
50        I: IntoIterator<Item = N>,
51        N: Into<HeaderName<'static>>;
52
53    /// chainable method to set cache control and return self. primarily useful on Conn
54    fn with_cache_control(mut self, cache_control: impl Into<CacheControlHeader>) -> Self {
55        self.set_cache_control(cache_control);
56        self
57    }
58
59    /// chainable method to set last modified and return self. primarily useful on Conn
60    fn with_last_modified(mut self, system_time: SystemTime) -> Self {
61        self.set_last_modified(system_time);
62        self
63    }
64
65    /// chainable method to set etag and return self. primarily useful on Conn
66    fn with_etag(mut self, entity_tag: &EntityTag) -> Self {
67        self.set_etag(entity_tag);
68        self
69    }
70
71    /// chainable method to set vary and return self. primarily useful on Conn
72    fn with_vary<I, N>(mut self, vary: I) -> Self
73    where
74        I: IntoIterator<Item = N>,
75        N: Into<HeaderName<'static>>,
76    {
77        self.set_vary(vary);
78        self
79    }
80
81    /// chainable method to set CDN-Cache-Control and return self. primarily useful on Conn
82    fn with_cdn_cache_control(mut self, cache_control: impl Into<CacheControlHeader>) -> Self {
83        self.set_cdn_cache_control(cache_control);
84        self
85    }
86}
87
88impl CachingHeadersExt for Conn {
89    fn etag(&self) -> Option<EntityTag> {
90        self.response_headers().etag()
91    }
92
93    fn set_etag(&mut self, entity_tag: &EntityTag) {
94        self.response_headers_mut().set_etag(entity_tag)
95    }
96
97    fn last_modified(&self) -> Option<SystemTime> {
98        self.response_headers().last_modified()
99    }
100
101    fn set_last_modified(&mut self, system_time: SystemTime) {
102        self.response_headers_mut().set_last_modified(system_time)
103    }
104
105    fn cache_control(&self) -> Option<CacheControlHeader> {
106        self.request_headers().cache_control()
107    }
108
109    fn set_cache_control(&mut self, cache_control: impl Into<CacheControlHeader>) {
110        self.response_headers_mut().set_cache_control(cache_control)
111    }
112
113    // CDN-Cache-Control is response-only per RFC 9213; both getter and setter
114    // act on response_headers (no request-side asymmetry).
115    fn cdn_cache_control(&self) -> Option<CacheControlHeader> {
116        self.response_headers().cdn_cache_control()
117    }
118
119    fn set_cdn_cache_control(&mut self, cache_control: impl Into<CacheControlHeader>) {
120        self.response_headers_mut()
121            .set_cdn_cache_control(cache_control)
122    }
123
124    fn if_modified_since(&self) -> Option<SystemTime> {
125        self.request_headers().if_modified_since()
126    }
127
128    fn if_none_match(&self) -> Option<EntityTag> {
129        self.request_headers().if_none_match()
130    }
131
132    fn set_vary<I, N>(&mut self, vary: I)
133    where
134        I: IntoIterator<Item = N>,
135        N: Into<HeaderName<'static>>,
136    {
137        self.response_headers_mut().set_vary(vary)
138    }
139}
140
141impl CachingHeadersExt for trillium::Headers {
142    fn etag(&self) -> Option<EntityTag> {
143        self.get_str(KnownHeaderName::Etag)
144            .and_then(|etag| etag.parse().ok())
145    }
146
147    fn set_etag(&mut self, entity_tag: &EntityTag) {
148        let string = entity_tag.to_string();
149        self.insert(KnownHeaderName::Etag, string);
150    }
151
152    fn last_modified(&self) -> Option<SystemTime> {
153        self.get_str(KnownHeaderName::LastModified)
154            .and_then(|x| httpdate::parse_http_date(x).ok())
155    }
156
157    fn set_last_modified(&mut self, system_time: SystemTime) {
158        self.insert(
159            KnownHeaderName::LastModified,
160            httpdate::fmt_http_date(system_time),
161        );
162    }
163
164    fn cache_control(&self) -> Option<CacheControlHeader> {
165        self.get_str(KnownHeaderName::CacheControl)
166            .map(CacheControlHeader::parse)
167    }
168
169    fn set_cache_control(&mut self, cache_control: impl Into<CacheControlHeader>) {
170        self.insert(
171            KnownHeaderName::CacheControl,
172            cache_control.into().to_string(),
173        );
174    }
175
176    fn cdn_cache_control(&self) -> Option<CacheControlHeader> {
177        self.get_str(KnownHeaderName::CdnCacheControl)
178            .map(CacheControlHeader::parse)
179    }
180
181    fn set_cdn_cache_control(&mut self, cache_control: impl Into<CacheControlHeader>) {
182        self.insert(
183            KnownHeaderName::CdnCacheControl,
184            cache_control.into().to_string(),
185        );
186    }
187
188    fn if_modified_since(&self) -> Option<SystemTime> {
189        self.get_str(KnownHeaderName::IfModifiedSince)
190            .and_then(|h| httpdate::parse_http_date(h).ok())
191    }
192
193    fn if_none_match(&self) -> Option<EntityTag> {
194        self.get_str(KnownHeaderName::IfNoneMatch)
195            .and_then(|etag| EntityTag::from_str(etag).ok())
196    }
197
198    fn set_vary<I, N>(&mut self, vary: I)
199    where
200        I: IntoIterator<Item = N>,
201        N: Into<HeaderName<'static>>,
202    {
203        self.insert(
204            KnownHeaderName::Vary,
205            vary.into_iter()
206                .map(|n| n.into().to_string())
207                .collect::<Vec<_>>()
208                .join(","),
209        );
210    }
211}