swiftide_integrations/redis/
node_cache.rs

1use anyhow::Result;
2use async_trait::async_trait;
3
4use swiftide_core::indexing::{Node, NodeCache};
5
6use super::Redis;
7
8#[allow(dependency_on_unit_never_type_fallback)]
9#[async_trait]
10impl NodeCache for Redis {
11    /// Checks if a node is present in the cache.
12    ///
13    /// # Parameters
14    ///
15    /// * `node` - The node to be checked in the cache.
16    ///
17    /// # Returns
18    ///
19    /// `true` if the node is present in the cache, `false` otherwise.
20    ///
21    /// # Errors
22    ///
23    /// Logs an error and returns `false` if the cache check fails.
24    #[tracing::instrument(skip_all, name = "node_cache.redis.get", fields(hit))]
25    async fn get(&self, node: &Node) -> bool {
26        let cache_result = if let Some(mut cm) = self.lazy_connect().await {
27            let result = redis::cmd("EXISTS")
28                .arg(self.cache_key_for_node(node))
29                .query_async(&mut cm)
30                .await;
31
32            match result {
33                Ok(1) => true,
34                Ok(0) => false,
35                Err(e) => {
36                    tracing::error!("Failed to check node cache: {}", e);
37                    false
38                }
39                _ => {
40                    tracing::error!("Unexpected response from redis");
41                    false
42                }
43            }
44        } else {
45            false
46        };
47
48        tracing::Span::current().record("hit", cache_result);
49
50        cache_result
51    }
52
53    /// Sets a node in the cache.
54    ///
55    /// # Parameters
56    ///
57    /// * `node` - The node to be set in the cache.
58    ///
59    /// # Errors
60    ///
61    /// Logs an error if the node cannot be set in the cache.
62    #[tracing::instrument(skip_all, name = "node_cache.redis.get")]
63    async fn set(&self, node: &Node) {
64        if let Some(mut cm) = self.lazy_connect().await {
65            let result: Result<(), redis::RedisError> = redis::cmd("SET")
66                .arg(self.cache_key_for_node(node))
67                .arg(1)
68                .query_async(&mut cm)
69                .await;
70
71            if let Err(e) = result {
72                tracing::error!("Failed to set node cache: {}", e);
73            }
74        }
75    }
76
77    async fn clear(&self) -> Result<()> {
78        if self.cache_key_prefix.is_empty() {
79            return Err(anyhow::anyhow!(
80                "No cache key prefix set; not flushing cache"
81            ));
82        }
83
84        if let Some(mut cm) = self.lazy_connect().await {
85            redis::cmd("DEL")
86                .arg(format!("{}*", self.cache_key_prefix))
87                .query_async::<()>(&mut cm)
88                .await?;
89
90            Ok(())
91        } else {
92            anyhow::bail!("Failed to connect to Redis");
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    use testcontainers::runners::AsyncRunner;
102
103    /// Tests the `RedisNodeCache` implementation.
104    #[test_log::test(tokio::test)]
105    async fn test_redis_cache() {
106        let redis = testcontainers::GenericImage::new("redis", "7.2.4")
107            .with_exposed_port(6379.into())
108            .with_wait_for(testcontainers::core::WaitFor::message_on_stdout(
109                "Ready to accept connections",
110            ))
111            .start()
112            .await
113            .expect("Redis started");
114
115        let host = redis.get_host().await.unwrap();
116        let port = redis.get_host_port_ipv4(6379).await.unwrap();
117        let cache = Redis::try_from_url(format!("redis://{host}:{port}"), "test")
118            .expect("Could not build redis client");
119        cache.reset_cache().await;
120
121        let node = Node::new("chunk");
122
123        let before_cache = cache.get(&node).await;
124        assert!(!before_cache);
125
126        cache.set(&node).await;
127        let after_cache = cache.get(&node).await;
128        assert!(after_cache);
129    }
130}