swiftide_integrations/redis/
node_cache.rs

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