mecha10_cli/services/redis.rs
1#![allow(dead_code)]
2
3//! Redis service for managing Redis connections and operations
4//!
5//! This service provides a centralized, reusable interface for all Redis operations
6//! across the CLI. It handles connection management, health checks, and common
7//! Redis stream operations.
8
9use anyhow::{Context, Result};
10use redis::aio::MultiplexedConnection as RedisConnection;
11use std::time::Duration;
12
13/// Redis service for connection management and operations
14///
15/// # Examples
16///
17/// ```rust,ignore
18/// use mecha10_cli::services::RedisService;
19///
20/// // Create service with default URL
21/// let redis = RedisService::default();
22///
23/// // Create service with custom URL
24/// let redis = RedisService::new("redis://localhost:6380")?;
25///
26/// // Get a connection
27/// let mut conn = redis.get_connection().await?;
28/// ```
29#[derive(Debug)]
30pub struct RedisService {
31 client: redis::Client,
32 url: String,
33}
34
35impl RedisService {
36 /// Create a new Redis service with a custom URL
37 ///
38 /// # Arguments
39 ///
40 /// * `url` - Redis connection URL (e.g., "redis://localhost:6379")
41 ///
42 /// # Errors
43 ///
44 /// Returns an error if the Redis client cannot be created with the provided URL.
45 pub fn new(url: &str) -> Result<Self> {
46 let client =
47 redis::Client::open(url).with_context(|| format!("Failed to create Redis client for URL: {}", url))?;
48
49 Ok(Self {
50 client,
51 url: url.to_string(),
52 })
53 }
54
55 /// Create a new Redis service with default URL
56 ///
57 /// Reads URL from environment variables in order of precedence:
58 /// 1. `MECHA10_REDIS_URL`
59 /// 2. `REDIS_URL`
60 /// 3. Default: "redis://localhost:6379"
61 ///
62 /// # Errors
63 ///
64 /// Returns an error if the Redis client cannot be created.
65 pub fn from_env() -> Result<Self> {
66 let url = std::env::var("MECHA10_REDIS_URL")
67 .or_else(|_| std::env::var("REDIS_URL"))
68 .unwrap_or_else(|_| "redis://localhost:6379".to_string());
69
70 Self::new(&url)
71 }
72
73 /// Get the Redis URL being used
74 pub fn url(&self) -> &str {
75 &self.url
76 }
77
78 /// Get an async Redis connection
79 ///
80 /// This creates a new connection each time. For long-running operations,
81 /// consider caching the connection in your code.
82 ///
83 /// # Errors
84 ///
85 /// Returns an error if the connection cannot be established.
86 pub async fn get_connection(&self) -> Result<RedisConnection> {
87 self.client
88 .get_multiplexed_async_connection()
89 .await
90 .context("Failed to establish async Redis connection")
91 }
92
93 /// Get a multiplexed async Redis connection
94 ///
95 /// Multiplexed connections allow multiple concurrent operations on the same connection.
96 ///
97 /// # Errors
98 ///
99 /// Returns an error if the connection cannot be established.
100 pub async fn get_multiplexed_connection(&self) -> Result<redis::aio::MultiplexedConnection> {
101 self.client
102 .get_multiplexed_async_connection()
103 .await
104 .context("Failed to establish multiplexed Redis connection")
105 }
106
107 /// Check if Redis is healthy and reachable
108 ///
109 /// Attempts to connect and execute a PING command within the specified timeout.
110 ///
111 /// # Arguments
112 ///
113 /// * `timeout` - Maximum time to wait for health check
114 ///
115 /// # Returns
116 ///
117 /// Returns `true` if Redis is reachable and responds to PING, `false` otherwise.
118 pub async fn check_health(&self, timeout: Duration) -> bool {
119 match tokio::time::timeout(timeout, async {
120 let mut conn = self.get_multiplexed_connection().await?;
121 let _: String = redis::cmd("PING").query_async(&mut conn).await?;
122 Ok::<(), anyhow::Error>(())
123 })
124 .await
125 {
126 Ok(Ok(())) => true,
127 Ok(Err(_)) => false,
128 Err(_) => false, // Timeout
129 }
130 }
131
132 /// Check health with default timeout (2 seconds)
133 pub async fn is_healthy(&self) -> bool {
134 self.check_health(Duration::from_secs(2)).await
135 }
136}
137
138impl Default for RedisService {
139 /// Create a Redis service with environment-based configuration
140 ///
141 /// # Panics
142 ///
143 /// Panics if the Redis client cannot be created. For error handling,
144 /// use `RedisService::from_env()` instead.
145 fn default() -> Self {
146 Self::from_env().expect("Failed to create default Redis service")
147 }
148}