ferron/util/
cache_control.rs1use std::time::Duration;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum Cachability {
5 NoCache,
6 Private,
7 Public,
8}
9
10#[derive(Debug, Clone, Default)]
11pub struct CacheControl {
12 pub no_store: bool,
13 pub no_cache: bool,
14 pub cachability: Option<Cachability>,
15 pub max_age: Option<Duration>,
16 pub s_max_age: Option<Duration>,
17}
18
19impl CacheControl {
20 pub fn from_value(value: &str) -> Option<Self> {
21 let mut cc = CacheControl::default();
22 let parts = value.split(',');
23
24 for part in parts {
25 let part = part.trim();
26 if part.is_empty() {
27 continue;
28 }
29
30 let mut kv = part.splitn(2, '=');
32 let key = kv.next()?.trim();
33 let value = kv.next().map(|v| v.trim());
34
35 if key.eq_ignore_ascii_case("no-store") {
36 cc.no_store = true;
37 } else if key.eq_ignore_ascii_case("no-cache") {
38 cc.no_cache = true;
39 cc.cachability = Some(Cachability::NoCache);
40 } else if key.eq_ignore_ascii_case("private") {
41 cc.cachability = Some(Cachability::Private);
42 } else if key.eq_ignore_ascii_case("public") {
43 cc.cachability = Some(Cachability::Public);
44 } else if key.eq_ignore_ascii_case("max-age") {
45 if let Some(secs) = value
46 .map(|v| v.trim_matches('"'))
47 .and_then(|v| v.parse::<u64>().ok())
48 {
49 cc.max_age = Some(Duration::from_secs(secs));
50 }
51 } else if key.eq_ignore_ascii_case("s-maxage") {
52 if let Some(secs) = value
53 .map(|v| v.trim_matches('"'))
54 .and_then(|v| v.parse::<u64>().ok())
55 {
56 cc.s_max_age = Some(Duration::from_secs(secs));
57 }
58 }
59 }
60
61 Some(cc)
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn test_parse_no_store() {
71 let cc = CacheControl::from_value("no-store").unwrap();
72 assert!(cc.no_store);
73 }
74
75 #[test]
76 fn test_parse_no_cache() {
77 let cc = CacheControl::from_value("no-cache").unwrap();
78 assert!(cc.no_cache);
79 assert_eq!(cc.cachability, Some(Cachability::NoCache));
80 }
81
82 #[test]
83 fn test_parse_public_max_age() {
84 let cc = CacheControl::from_value("public, max-age=3600").unwrap();
85 assert_eq!(cc.cachability, Some(Cachability::Public));
86 assert_eq!(cc.max_age, Some(Duration::from_secs(3600)));
87 }
88
89 #[test]
90 fn test_parse_s_maxage() {
91 let cc = CacheControl::from_value("s-maxage=600").unwrap();
92 assert_eq!(cc.s_max_age, Some(Duration::from_secs(600)));
93 }
94
95 #[test]
96 fn test_parse_complex() {
97 let cc = CacheControl::from_value("private, no-store, max-age=0").unwrap();
98 assert_eq!(cc.cachability, Some(Cachability::Private));
99 assert!(cc.no_store);
100 assert_eq!(cc.max_age, Some(Duration::from_secs(0)));
101 }
102
103 #[test]
104 fn test_parse_case_insensitive() {
105 let cc = CacheControl::from_value("PuBlIc, Max-Age=100").unwrap();
106 assert_eq!(cc.cachability, Some(Cachability::Public));
107 assert_eq!(cc.max_age, Some(Duration::from_secs(100)));
108 }
109
110 #[test]
111 fn test_parse_extra_whitespace() {
112 let cc = CacheControl::from_value(" public , max-age = 60 ").unwrap();
113 assert_eq!(cc.cachability, Some(Cachability::Public));
114 assert_eq!(cc.max_age, Some(Duration::from_secs(60)));
115 }
116}