Skip to main content

brows3r_lib/cache/
invalidation.rs

1//! Mutation-triggered invalidation helpers.
2//!
3//! These pure helpers contain the invalidation logic for common S3 mutations.
4//! The actual call sites (S3 commands) wire them in during later tasks.
5//!
6//! # OCP
7//!
8//! Adding a new mutation type: add one more `pub fn on_*_mutation` that calls
9//! `store.invalidate(...)` for the affected `CacheKey` variants.  Nothing else
10//! changes.
11
12use crate::ids::{BucketId, ProfileId};
13
14use super::store::CacheHandle;
15use super::CacheKey;
16
17/// Invalidate `Objects` and `ObjectHead` keys for the affected scope when an
18/// object is created, updated, deleted, or moved within `bucket/prefix`.
19///
20/// Callers pass the exact `prefix` that was mutated.  If the mutation spans
21/// multiple prefixes (e.g. a recursive delete), callers are responsible for
22/// calling this helper once per distinct prefix.
23pub fn on_object_mutation(
24    profile: &ProfileId,
25    bucket: &BucketId,
26    prefix: &str,
27    object_key: Option<&crate::ids::ObjectKey>,
28    store: &CacheHandle,
29) {
30    // Invalidate the listing for this prefix.
31    store.invalidate(&CacheKey::Objects {
32        profile: profile.clone(),
33        bucket: bucket.clone(),
34        prefix: prefix.to_string(),
35    });
36
37    // If the caller supplies a specific object key, also invalidate the
38    // per-object head entry.
39    if let Some(key) = object_key {
40        store.invalidate(&CacheKey::ObjectHead {
41            profile: profile.clone(),
42            bucket: bucket.clone(),
43            key: key.clone(),
44        });
45        // Invalidate the inspector entry for this specific object.
46        store.invalidate(&CacheKey::Inspector {
47            profile: profile.clone(),
48            bucket: bucket.clone(),
49            key: Some(key.clone()),
50        });
51    }
52}
53
54/// Invalidate the `Buckets` listing for a profile when a bucket-level mutation
55/// occurs (e.g. bucket created or deleted).
56pub fn on_bucket_mutation(profile: &ProfileId, store: &CacheHandle) {
57    store.invalidate(&CacheKey::Buckets(profile.clone()));
58}
59
60// ---------------------------------------------------------------------------
61// Tests
62// ---------------------------------------------------------------------------
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::{
68        cache::{
69            store::{CacheStore, MockClock},
70            CacheConfig,
71        },
72        ids::{BucketId, ObjectKey, ProfileId},
73    };
74
75    fn make_store() -> CacheHandle {
76        CacheStore::new_with_clock(CacheConfig::default(), MockClock::new(1_000_000), None)
77    }
78
79    #[test]
80    fn on_object_mutation_removes_objects_and_head_entries() {
81        let store = make_store();
82        let pid = ProfileId::new("p");
83        let bid = BucketId::new("b");
84        let key = ObjectKey::new("folder/file.txt");
85        let validated = Some(0_i64);
86
87        // Write both an Objects listing and an ObjectHead entry.
88        let objects_key = CacheKey::Objects {
89            profile: pid.clone(),
90            bucket: bid.clone(),
91            prefix: "folder/".to_string(),
92        };
93        let head_key = CacheKey::ObjectHead {
94            profile: pid.clone(),
95            bucket: bid.clone(),
96            key: key.clone(),
97        };
98
99        store
100            .put(&objects_key, serde_json::json!(["file.txt"]), None)
101            .unwrap();
102        store
103            .put(&head_key, serde_json::json!({"size": 42}), None)
104            .unwrap();
105
106        // Invalidate after mutation.
107        on_object_mutation(&pid, &bid, "folder/", Some(&key), &store);
108
109        assert!(
110            store
111                .get::<serde_json::Value>(&objects_key, validated)
112                .unwrap()
113                .is_none(),
114            "Objects entry must be invalidated"
115        );
116        assert!(
117            store
118                .get::<serde_json::Value>(&head_key, validated)
119                .unwrap()
120                .is_none(),
121            "ObjectHead entry must be invalidated"
122        );
123    }
124
125    #[test]
126    fn on_bucket_mutation_removes_bucket_list_entry() {
127        let store = make_store();
128        let pid = ProfileId::new("p");
129        let validated = Some(0_i64);
130
131        let buckets_key = CacheKey::Buckets(pid.clone());
132        store
133            .put(&buckets_key, serde_json::json!(["b1", "b2"]), None)
134            .unwrap();
135
136        on_bucket_mutation(&pid, &store);
137
138        assert!(
139            store
140                .get::<serde_json::Value>(&buckets_key, validated)
141                .unwrap()
142                .is_none(),
143            "Buckets entry must be invalidated after bucket mutation"
144        );
145    }
146}