1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3use std::time::Duration;
4
5use crate::types::validate_token;
6use crate::{CacheModelError, VariationKey};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum CacheVisibility {
11 Public,
12 Private,
13 NoStore,
14}
15
16impl fmt::Display for CacheVisibility {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 Self::Public => f.write_str("public"),
20 Self::Private => f.write_str("private"),
21 Self::NoStore => f.write_str("no_store"),
22 }
23 }
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct CacheScope {
28 visibility: CacheVisibility,
29 tenant: Option<String>,
30 site: Option<String>,
31 locale: Option<String>,
32 user: Option<String>,
33 session: Option<String>,
34 custom: BTreeMap<String, String>,
35}
36
37impl CacheScope {
38 pub fn public() -> Self {
39 Self::new(CacheVisibility::Public)
40 }
41
42 pub fn private() -> Self {
43 Self::new(CacheVisibility::Private)
44 }
45
46 pub fn no_store() -> Self {
47 Self::new(CacheVisibility::NoStore)
48 }
49
50 pub fn new(visibility: CacheVisibility) -> Self {
51 Self {
52 visibility,
53 tenant: None,
54 site: None,
55 locale: None,
56 user: None,
57 session: None,
58 custom: BTreeMap::new(),
59 }
60 }
61
62 pub fn visibility(&self) -> CacheVisibility {
63 self.visibility
64 }
65
66 pub fn tenant(&self) -> Option<&str> {
67 self.tenant.as_deref()
68 }
69
70 pub fn site(&self) -> Option<&str> {
71 self.site.as_deref()
72 }
73
74 pub fn locale(&self) -> Option<&str> {
75 self.locale.as_deref()
76 }
77
78 pub fn user(&self) -> Option<&str> {
79 self.user.as_deref()
80 }
81
82 pub fn session(&self) -> Option<&str> {
83 self.session.as_deref()
84 }
85
86 pub fn custom(&self) -> &BTreeMap<String, String> {
87 &self.custom
88 }
89
90 pub fn with_tenant(mut self, tenant: impl Into<String>) -> Result<Self, CacheModelError> {
91 self.tenant = Some(validate_token("tenant", tenant.into())?);
92 Ok(self)
93 }
94
95 pub fn with_site(mut self, site: impl Into<String>) -> Result<Self, CacheModelError> {
96 self.site = Some(validate_token("site", site.into())?);
97 Ok(self)
98 }
99
100 pub fn with_locale(mut self, locale: impl Into<String>) -> Result<Self, CacheModelError> {
101 self.locale = Some(validate_token("locale", locale.into())?);
102 Ok(self)
103 }
104
105 pub fn with_user(mut self, user: impl Into<String>) -> Result<Self, CacheModelError> {
106 if self.visibility == CacheVisibility::Public {
107 return Err(CacheModelError::PublicScopeCannotVaryByUser);
108 }
109
110 self.user = Some(validate_token("user", user.into())?);
111 Ok(self)
112 }
113
114 pub fn with_session(mut self, session: impl Into<String>) -> Result<Self, CacheModelError> {
115 if self.visibility == CacheVisibility::Public {
116 return Err(CacheModelError::PublicScopeCannotVaryBySession);
117 }
118
119 self.session = Some(validate_token("session", session.into())?);
120 Ok(self)
121 }
122
123 pub fn with_custom_variation(
124 mut self,
125 name: impl Into<String>,
126 value: impl Into<String>,
127 ) -> Result<Self, CacheModelError> {
128 let name = validate_token("variation_name", name.into())?;
129 let value = validate_token("variation_value", value.into())?;
130 self.custom.insert(name, value);
131 Ok(self)
132 }
133
134 pub fn is_cacheable(&self) -> bool {
135 self.visibility != CacheVisibility::NoStore
136 }
137
138 pub fn is_edge_cacheable(&self) -> bool {
139 self.visibility == CacheVisibility::Public
140 }
141
142 pub fn variation_key(&self) -> Option<VariationKey> {
143 self.variation_key_internal(false)
144 }
145
146 pub fn cache_partition_key(&self) -> Option<VariationKey> {
147 self.variation_key_internal(true)
148 }
149
150 fn variation_key_internal(&self, include_visibility: bool) -> Option<VariationKey> {
151 if self.visibility == CacheVisibility::NoStore {
152 return None;
153 }
154
155 let mut parts = Vec::new();
156 if include_visibility {
157 parts.push(format!("visibility={}", self.visibility));
158 }
159
160 if let Some(tenant) = &self.tenant {
161 parts.push(format!("tenant={tenant}"));
162 }
163
164 if let Some(site) = &self.site {
165 parts.push(format!("site={site}"));
166 }
167
168 if let Some(locale) = &self.locale {
169 parts.push(format!("locale={locale}"));
170 }
171
172 if let Some(user) = &self.user {
173 parts.push(format!("user={user}"));
174 }
175
176 if let Some(session) = &self.session {
177 parts.push(format!("session={session}"));
178 }
179
180 for (name, value) in &self.custom {
181 parts.push(format!("x:{name}={value}"));
182 }
183
184 if parts.is_empty() {
185 None
186 } else {
187 Some(VariationKey::new(parts.join("|")))
188 }
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
193pub struct InvalidationSet {
194 tags: BTreeSet<crate::InvalidationTag>,
195}
196
197impl InvalidationSet {
198 pub fn new() -> Self {
199 Self::default()
200 }
201
202 pub fn from_tags(tags: impl IntoIterator<Item = crate::InvalidationTag>) -> Self {
203 let mut set = Self::new();
204 for tag in tags {
205 set.insert(tag);
206 }
207 set
208 }
209
210 pub fn insert(&mut self, tag: crate::InvalidationTag) {
211 self.tags.insert(tag);
212 }
213
214 pub fn len(&self) -> usize {
215 self.tags.len()
216 }
217
218 pub fn is_empty(&self) -> bool {
219 self.tags.is_empty()
220 }
221
222 pub fn iter(&self) -> impl Iterator<Item = &crate::InvalidationTag> {
223 self.tags.iter()
224 }
225
226 pub fn header_value(&self) -> Option<String> {
227 if self.tags.is_empty() {
228 None
229 } else {
230 Some(
231 self.tags
232 .iter()
233 .map(crate::InvalidationTag::as_str)
234 .collect::<Vec<_>>()
235 .join(" "),
236 )
237 }
238 }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
242pub struct FreshnessPolicy {
243 ttl: Duration,
244 stale_while_revalidate: Option<Duration>,
245}
246
247impl FreshnessPolicy {
248 pub fn new(
249 ttl: Duration,
250 stale_while_revalidate: Option<Duration>,
251 ) -> Result<Self, CacheModelError> {
252 if ttl.is_zero() {
253 return Err(CacheModelError::ZeroDuration { field: "ttl" });
254 }
255
256 if stale_while_revalidate.is_some_and(|value| value.is_zero()) {
257 return Err(CacheModelError::ZeroDuration {
258 field: "stale_while_revalidate",
259 });
260 }
261
262 Ok(Self {
263 ttl,
264 stale_while_revalidate,
265 })
266 }
267
268 pub fn ttl(&self) -> Duration {
269 self.ttl
270 }
271
272 pub fn stale_while_revalidate(&self) -> Option<Duration> {
273 self.stale_while_revalidate
274 }
275
276 pub fn ttl_seconds(&self) -> u64 {
277 self.ttl.as_secs()
278 }
279
280 pub fn stale_while_revalidate_seconds(&self) -> Option<u64> {
281 self.stale_while_revalidate.map(|value| value.as_secs())
282 }
283}
284
285#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
286pub struct ResponseValidators {
287 pub etag: Option<crate::EntityTag>,
288 pub last_modified_unix_seconds: Option<u64>,
289}