Skip to main content

coil_cache/
scope.rs

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}