Skip to main content

mecha10_cli_core/services/
redis.rs

1#![allow(dead_code)]
2
3//! Redis service for managing Redis connections and operations
4//!
5//! Centralized, reusable interface for connection management, health checks, and streams.
6
7use anyhow::{Context, Result};
8use redis::aio::MultiplexedConnection as RedisConnection;
9use std::time::Duration;
10
11#[derive(Debug)]
12pub struct RedisService {
13    client: redis::Client,
14    url: String,
15}
16
17impl RedisService {
18    /// Create a new Redis service with a custom URL (e.g. "redis://localhost:6379")
19    pub fn new(url: &str) -> Result<Self> {
20        let client =
21            redis::Client::open(url).with_context(|| format!("Failed to create Redis client for URL: {}", url))?;
22
23        Ok(Self {
24            client,
25            url: url.to_string(),
26        })
27    }
28
29    /// Create a Redis service, reading the URL from `MECHA10_REDIS_URL`, then
30    /// `REDIS_URL`, defaulting to "redis://localhost:6379".
31    pub fn from_env() -> Result<Self> {
32        let url = std::env::var("MECHA10_REDIS_URL")
33            .or_else(|_| std::env::var("REDIS_URL"))
34            .unwrap_or_else(|_| "redis://localhost:6379".to_string());
35
36        Self::new(&url)
37    }
38
39    /// Get the Redis URL being used
40    pub fn url(&self) -> &str {
41        &self.url
42    }
43
44    /// Get a new async Redis connection. For long-running operations, consider
45    /// caching the connection instead of calling this repeatedly.
46    pub async fn get_connection(&self) -> Result<RedisConnection> {
47        self.client
48            .get_multiplexed_async_connection()
49            .await
50            .context("Failed to establish async Redis connection")
51    }
52
53    /// Get a multiplexed async Redis connection, allowing multiple concurrent
54    /// operations on the same connection.
55    pub async fn get_multiplexed_connection(&self) -> Result<redis::aio::MultiplexedConnection> {
56        self.client
57            .get_multiplexed_async_connection()
58            .await
59            .context("Failed to establish multiplexed Redis connection")
60    }
61
62    /// Connect and PING within `timeout`; `true` if Redis responds, `false` otherwise.
63    pub async fn check_health(&self, timeout: Duration) -> bool {
64        match tokio::time::timeout(timeout, async {
65            let mut conn = self.get_multiplexed_connection().await?;
66            let _: String = redis::cmd("PING").query_async(&mut conn).await?;
67            Ok::<(), anyhow::Error>(())
68        })
69        .await
70        {
71            Ok(Ok(())) => true,
72            Ok(Err(_)) => false,
73            Err(_) => false,
74        }
75    }
76
77    /// Check health with default timeout (2 seconds)
78    pub async fn is_healthy(&self) -> bool {
79        self.check_health(Duration::from_secs(2)).await
80    }
81}
82
83impl Default for RedisService {
84    /// Create a Redis service with environment-based configuration.
85    ///
86    /// Panics if the client cannot be created; use `RedisService::from_env()` for error handling.
87    fn default() -> Self {
88        Self::from_env().expect("Failed to create default Redis service")
89    }
90}