1use core::time::Duration;
13
14#[derive(Eq, PartialEq, Debug)]
16pub enum Cachability {
17 Public,
19
20 Private,
22
23 NoCache,
25
26 OnlyIfCached,
28}
29
30#[derive(Eq, PartialEq, Debug, Default)]
32pub struct CacheControl {
33 pub cachability: Option<Cachability>,
34 pub max_age: Option<Duration>,
37 pub s_max_age: Option<Duration>,
40 pub max_stale: Option<Duration>,
43 pub min_fresh: Option<Duration>,
46 pub must_revalidate: bool,
49 pub proxy_revalidate: bool,
52 pub immutable: bool,
54 pub no_store: bool,
56 pub no_transform: bool,
59}
60
61impl CacheControl {
62 pub fn from_value(value: &str) -> Option<Self> {
72 let mut ret = Self::default();
73 for token in value.split(',') {
74 let (key, val) = {
75 let mut split = token.split('=').map(|s| s.trim());
76 (split.next().unwrap(), split.next())
77 };
78
79 match key {
80 "public" => ret.cachability = Some(Cachability::Public),
81 "private" => ret.cachability = Some(Cachability::Private),
82 "no-cache" => ret.cachability = Some(Cachability::NoCache),
83 "only-if-cached" => ret.cachability = Some(Cachability::OnlyIfCached),
84 "max-age" => match val.and_then(|v| v.parse().ok()) {
85 Some(secs) => ret.max_age = Some(Duration::from_secs(secs)),
86 None => return None,
87 },
88 "s-maxage" => match val.and_then(|v| v.parse().ok()) {
89 Some(secs) => ret.s_max_age = Some(Duration::from_secs(secs)),
90 None => return None,
91 },
92 "max-stale" => match val.and_then(|v| v.parse().ok()) {
93 Some(secs) => ret.max_stale = Some(Duration::from_secs(secs)),
94 None => return None,
95 },
96 "min-fresh" => match val.and_then(|v| v.parse().ok()) {
97 Some(secs) => ret.min_fresh = Some(Duration::from_secs(secs)),
98 None => return None,
99 },
100 "must-revalidate" => ret.must_revalidate = true,
101 "proxy-revalidate" => ret.proxy_revalidate = true,
102 "immutable" => ret.immutable = true,
103 "no-store" => ret.no_store = true,
104 "no-transform" => ret.no_transform = true,
105 _ => (),
106 };
107 }
108 Some(ret)
109 }
110
111 pub fn from_header(value: &str) -> Option<Self> {
113 let (name, value) = value.split_once(':')?;
114 if !name.trim().eq_ignore_ascii_case("Cache-Control") {
115 return None;
116 }
117 Self::from_value(value)
118 }
119}
120
121#[cfg(test)]
122mod test {
123 use super::*;
124
125 #[test]
126 fn test_from_value() {
127 assert_eq!(
128 CacheControl::from_value("").unwrap(),
129 CacheControl::default()
130 );
131 assert_eq!(
132 CacheControl::from_value("private")
133 .unwrap()
134 .cachability
135 .unwrap(),
136 Cachability::Private
137 );
138 assert_eq!(
139 CacheControl::from_value("max-age=60")
140 .unwrap()
141 .max_age
142 .unwrap(),
143 Duration::from_secs(60)
144 );
145 }
146
147 #[test]
148 fn test_from_value_multi() {
149 let test1 = &CacheControl::from_value("no-cache, no-store, must-revalidate").unwrap();
150 assert_eq!(test1.cachability, Some(Cachability::NoCache));
151 assert!(test1.no_store);
152 assert!(test1.must_revalidate);
153 assert_eq!(
154 *test1,
155 CacheControl {
156 cachability: Some(Cachability::NoCache),
157 max_age: None,
158 s_max_age: None,
159 max_stale: None,
160 min_fresh: None,
161 must_revalidate: true,
162 proxy_revalidate: false,
163 immutable: false,
164 no_store: true,
165 no_transform: false,
166 }
167 );
168 }
169
170 #[test]
171 fn test_from_header() {
172 assert_eq!(
173 CacheControl::from_header("Cache-Control: ").unwrap(),
174 CacheControl::default()
175 );
176 assert_eq!(
177 CacheControl::from_header("Cache-Control: private")
178 .unwrap()
179 .cachability
180 .unwrap(),
181 Cachability::Private
182 );
183 assert_eq!(
184 CacheControl::from_header("Cache-Control: max-age=60")
185 .unwrap()
186 .max_age
187 .unwrap(),
188 Duration::from_secs(60)
189 );
190 assert_eq!(CacheControl::from_header("foo"), None);
191 assert_eq!(CacheControl::from_header("bar: max-age=60"), None);
192 }
193
194 #[test]
195 fn test_from_header_multi() {
196 let test1 = &CacheControl::from_header("Cache-Control: public, max-age=600").unwrap();
197 assert_eq!(test1.cachability, Some(Cachability::Public));
198 assert_eq!(test1.max_age, Some(Duration::from_secs(600)));
199 assert_eq!(
200 *test1,
201 CacheControl {
202 cachability: Some(Cachability::Public),
203 max_age: Some(Duration::from_secs(600)),
204 s_max_age: None,
205 max_stale: None,
206 min_fresh: None,
207 must_revalidate: false,
208 proxy_revalidate: false,
209 immutable: false,
210 no_store: false,
211 no_transform: false,
212 }
213 );
214 }
215}