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