swiftide_integrations/redis/
node_cache.rs1use 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 #[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 #[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 #[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}