1use std::{
2 borrow::Cow,
3 fmt::{self, Display, Formatter},
4};
5
6const IMMUTABLE_MAX_AGE: u32 = 31536000;
8
9const WELL_KNOWN_HEADER_VALUES: [(CacheControl, &str); 4] = [
11 (CacheControl::new(), ""),
12 (CacheControl::no_cache(), "no-cache"),
13 (CacheControl::no_store(), "no-store"),
14 (CacheControl::immutable(), "public, max-age=31536000, immutable"),
15];
16
17#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
19pub enum Cacheability {
20 #[default]
22 Unspecified,
23 Public,
25 Private,
27 NoStore,
29}
30
31#[derive(Debug, Clone, Copy, Eq, PartialEq)]
46pub struct CacheControl {
47 pub cacheability: Cacheability,
49 pub max_age: Option<u32>,
51 pub s_max_age: Option<u32>,
53 pub no_cache: bool,
55 pub must_revalidate: bool,
57 pub proxy_revalidate: bool,
59 pub no_transform: bool,
61 pub immutable: bool,
63 pub stale_while_revalidate: Option<u32>,
65 pub stale_if_error: Option<u32>,
67}
68
69impl Default for CacheControl {
70 #[inline]
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76impl CacheControl {
77 #[inline]
79 pub const fn new() -> Self {
80 Self {
81 cacheability: Cacheability::Unspecified,
82 max_age: None,
83 s_max_age: None,
84 no_cache: false,
85 must_revalidate: false,
86 proxy_revalidate: false,
87 no_transform: false,
88 immutable: false,
89 stale_while_revalidate: None,
90 stale_if_error: None,
91 }
92 }
93
94 #[inline]
96 pub const fn public(max_age: u32) -> Self {
97 let mut cache_control = Self::new();
98
99 cache_control.cacheability = Cacheability::Public;
100 cache_control.max_age = Some(max_age);
101
102 cache_control
103 }
104
105 #[inline]
107 pub const fn private(max_age: u32) -> Self {
108 let mut cache_control = Self::new();
109
110 cache_control.cacheability = Cacheability::Private;
111 cache_control.max_age = Some(max_age);
112
113 cache_control
114 }
115
116 #[inline]
118 pub const fn no_cache() -> Self {
119 let mut cache_control = Self::new();
120
121 cache_control.no_cache = true;
122
123 cache_control
124 }
125
126 #[inline]
128 pub const fn no_store() -> Self {
129 let mut cache_control = Self::new();
130
131 cache_control.cacheability = Cacheability::NoStore;
132
133 cache_control
134 }
135
136 #[inline]
138 pub const fn immutable() -> Self {
139 let mut cache_control = Self::public(IMMUTABLE_MAX_AGE);
140
141 cache_control.immutable = true;
142
143 cache_control
144 }
145
146 #[inline]
148 pub fn to_header_value(&self) -> Cow<'static, str> {
149 for (cache_control, header_value) in WELL_KNOWN_HEADER_VALUES {
150 if *self == cache_control {
151 return Cow::Borrowed(header_value);
152 }
153 }
154
155 Cow::Owned(self.to_string())
156 }
157}
158
159impl Display for CacheControl {
160 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
161 let mut separator = "";
162
163 match self.cacheability {
164 Cacheability::Unspecified => (),
165 Cacheability::Public => write_directive(f, &mut separator, "public")?,
166 Cacheability::Private => write_directive(f, &mut separator, "private")?,
167 Cacheability::NoStore => write_directive(f, &mut separator, "no-store")?,
168 }
169
170 if self.no_cache {
171 write_directive(f, &mut separator, "no-cache")?;
172 }
173
174 if let Some(max_age) = self.max_age {
175 write_seconds_directive(f, &mut separator, "max-age", max_age)?;
176 }
177
178 if let Some(s_max_age) = self.s_max_age {
179 write_seconds_directive(f, &mut separator, "s-maxage", s_max_age)?;
180 }
181
182 if self.must_revalidate {
183 write_directive(f, &mut separator, "must-revalidate")?;
184 }
185
186 if self.proxy_revalidate {
187 write_directive(f, &mut separator, "proxy-revalidate")?;
188 }
189
190 if self.no_transform {
191 write_directive(f, &mut separator, "no-transform")?;
192 }
193
194 if self.immutable {
195 write_directive(f, &mut separator, "immutable")?;
196 }
197
198 if let Some(stale_while_revalidate) = self.stale_while_revalidate {
199 write_seconds_directive(
200 f,
201 &mut separator,
202 "stale-while-revalidate",
203 stale_while_revalidate,
204 )?;
205 }
206
207 if let Some(stale_if_error) = self.stale_if_error {
208 write_seconds_directive(f, &mut separator, "stale-if-error", stale_if_error)?;
209 }
210
211 Ok(())
212 }
213}
214
215#[inline]
216fn write_directive(
217 f: &mut Formatter<'_>,
218 separator: &mut &'static str,
219 directive: &str,
220) -> fmt::Result {
221 f.write_str(separator)?;
222 f.write_str(directive)?;
223
224 *separator = ", ";
225
226 Ok(())
227}
228
229#[inline]
230fn write_seconds_directive(
231 f: &mut Formatter<'_>,
232 separator: &mut &'static str,
233 directive: &str,
234 seconds: u32,
235) -> fmt::Result {
236 write_directive(f, separator, directive)?;
237
238 write!(f, "={seconds}")
239}