Skip to main content

sova_redis/
pool.rs

1use crate::error::RedisError;
2use crate::fake::FakeRedis;
3use crate::fake::FakeSubscriber;
4use redis::aio::ConnectionManager;
5use redis::Client;
6use sova_core::Request;
7use std::sync::{Arc, RwLock};
8
9#[derive(Default)]
10struct PoolInner {
11    conn: Option<ConnectionManager>,
12    url: Option<String>,
13    fake: Option<Arc<FakeRedis>>,
14}
15
16/// Shared Redis handle filled during `on_startup` (mirrors [`sova_db::DbPool`]).
17#[derive(Clone, Default)]
18pub struct RedisPool {
19    inner: Arc<RwLock<PoolInner>>,
20}
21
22impl RedisPool {
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    pub fn is_fake(&self) -> bool {
28        self.inner.read().unwrap().fake.is_some()
29    }
30
31    pub fn fake(&self) -> Option<Arc<FakeRedis>> {
32        self.inner.read().unwrap().fake.clone()
33    }
34
35    pub fn set_fake(&self, fake: FakeRedis) {
36        let mut g = self.inner.write().unwrap();
37        g.fake = Some(Arc::new(fake));
38        g.url = Some("fake://memory".into());
39    }
40
41    /// Remember `REDIS_URL` so Pub/Sub can open a dedicated connection.
42    pub fn set_url(&self, url: impl Into<String>) {
43        self.inner.write().unwrap().url = Some(url.into());
44    }
45
46    pub fn url(&self) -> Result<String, RedisError> {
47        self.inner
48            .read()
49            .unwrap()
50            .url
51            .clone()
52            .ok_or_else(|| RedisError::msg("redis url not set"))
53    }
54
55    pub fn set(&self, conn: ConnectionManager) {
56        self.inner.write().unwrap().conn = Some(conn);
57    }
58
59    pub fn get(&self) -> Result<ConnectionManager, RedisError> {
60        let g = self.inner.read().unwrap();
61        if g.fake.is_some() {
62            return Err(RedisError::msg("fake redis: use FakeRedis API"));
63        }
64        g.conn
65            .clone()
66            .ok_or_else(|| RedisError::msg("redis not connected"))
67    }
68
69    pub fn clear(&self) {
70        let mut g = self.inner.write().unwrap();
71        g.conn.take();
72    }
73
74    /// Connect and ping; used by the plugin and tests.
75    pub async fn connect(url: &str) -> Result<ConnectionManager, RedisError> {
76        let client = Client::open(url).map_err(RedisError::from)?;
77        let mut conn = ConnectionManager::new(client)
78            .await
79            .map_err(RedisError::from)?;
80        let _: String = redis::cmd("PING")
81            .query_async(&mut conn)
82            .await
83            .map_err(RedisError::from)?;
84        Ok(conn)
85    }
86
87    pub async fn subscribe_fake(
88        &self,
89        channel: impl AsRef<str>,
90    ) -> Result<FakeSubscriber, RedisError> {
91        let fake = self
92            .fake()
93            .ok_or_else(|| RedisError::msg("fake redis not configured"))?;
94        Ok(fake.subscribe(channel.as_ref()))
95    }
96}
97
98/// Convenient `req.redis()`.
99pub trait RedisExt {
100    fn redis(&self) -> &RedisPool;
101}
102
103impl RedisExt for Request {
104    fn redis(&self) -> &RedisPool {
105        self.get::<RedisPool>()
106            .expect("Redis plugin is not installed (missing req.redis())")
107    }
108}