use crate::config::AppConfig; use crate::error::{AppError, AppResult}; use futures_util::future::BoxFuture; use redis::cluster::ClusterClient; use redis::{Client, FromRedisValue}; #[derive(Clone)] enum RedisBackend { Single(redis::aio::ConnectionManager), Cluster(redis::cluster_async::ClusterConnection), } #[derive(Clone)] pub struct AppRedis { backend: RedisBackend, } impl AppRedis { pub async fn from_config(config: &AppConfig) -> AppResult { let backend = if config.redis_cluster_enabled()? { let nodes = config.redis_cluster_nodes()?; let cluster_client = ClusterClient::new(nodes.iter().map(|s| s.as_str()).collect::>())?; let conn = cluster_client.get_async_connection().await?; RedisBackend::Cluster(conn) } else { let url = config .redis_url()? .ok_or_else(|| AppError::Config("APP_REDIS_URL is not set".into()))?; let client = Client::open(url.as_str())?; let conn = client.get_connection_manager().await?; RedisBackend::Single(conn) }; Ok(Self { backend }) } pub fn get_connection(&self) -> RedisConnection { match &self.backend { RedisBackend::Single(cm) => RedisConnection::Single(cm.clone()), RedisBackend::Cluster(cc) => RedisConnection::Cluster(cc.clone()), } } } pub enum RedisConnection { Single(redis::aio::ConnectionManager), Cluster(redis::cluster_async::ClusterConnection), } impl redis::aio::ConnectionLike for RedisConnection { fn req_packed_command<'a>( &'a mut self, cmd: &'a redis::Cmd, ) -> BoxFuture<'a, redis::RedisResult> { match self { Self::Single(c) => Box::pin(c.req_packed_command(cmd)), Self::Cluster(c) => Box::pin(c.req_packed_command(cmd)), } } fn req_packed_commands<'a>( &'a mut self, cmd: &'a redis::Pipeline, offset: usize, count: usize, ) -> BoxFuture<'a, redis::RedisResult>> { match self { Self::Single(c) => Box::pin(c.req_packed_commands(cmd, offset, count)), Self::Cluster(c) => Box::pin(c.req_packed_commands(cmd, offset, count)), } } fn get_db(&self) -> i64 { match self { Self::Single(c) => c.get_db(), Self::Cluster(c) => c.get_db(), } } } impl RedisConnection { pub async fn query_async( &mut self, cmd: &mut redis::Cmd, ) -> redis::RedisResult { match self { Self::Single(c) => cmd.query_async(c).await, Self::Cluster(c) => cmd.query_async(c).await, } } pub async fn query_pipeline_async( &mut self, pipe: &mut redis::Pipeline, ) -> redis::RedisResult { match self { Self::Single(c) => pipe.query_async(c).await, Self::Cluster(c) => pipe.query_async(c).await, } } }