mecha10-cli-core 0.8.2

Mecha10 CLI core foundation — shared types, services, and utilities
Documentation
#![allow(dead_code)]

//! Redis service for managing Redis connections and operations
//!
//! Centralized, reusable interface for connection management, health checks, and streams.

use anyhow::{Context, Result};
use redis::aio::MultiplexedConnection as RedisConnection;
use std::time::Duration;

#[derive(Debug)]
pub struct RedisService {
    client: redis::Client,
    url: String,
}

impl RedisService {
    /// Create a new Redis service with a custom URL (e.g. "redis://localhost:6379")
    pub fn new(url: &str) -> Result<Self> {
        let client =
            redis::Client::open(url).with_context(|| format!("Failed to create Redis client for URL: {}", url))?;

        Ok(Self {
            client,
            url: url.to_string(),
        })
    }

    /// Create a Redis service, reading the URL from `MECHA10_REDIS_URL`, then
    /// `REDIS_URL`, defaulting to "redis://localhost:6379".
    pub fn from_env() -> Result<Self> {
        let url = std::env::var("MECHA10_REDIS_URL")
            .or_else(|_| std::env::var("REDIS_URL"))
            .unwrap_or_else(|_| "redis://localhost:6379".to_string());

        Self::new(&url)
    }

    /// Get the Redis URL being used
    pub fn url(&self) -> &str {
        &self.url
    }

    /// Get a new async Redis connection. For long-running operations, consider
    /// caching the connection instead of calling this repeatedly.
    pub async fn get_connection(&self) -> Result<RedisConnection> {
        self.client
            .get_multiplexed_async_connection()
            .await
            .context("Failed to establish async Redis connection")
    }

    /// Get a multiplexed async Redis connection, allowing multiple concurrent
    /// operations on the same connection.
    pub async fn get_multiplexed_connection(&self) -> Result<redis::aio::MultiplexedConnection> {
        self.client
            .get_multiplexed_async_connection()
            .await
            .context("Failed to establish multiplexed Redis connection")
    }

    /// Connect and PING within `timeout`; `true` if Redis responds, `false` otherwise.
    pub async fn check_health(&self, timeout: Duration) -> bool {
        match tokio::time::timeout(timeout, async {
            let mut conn = self.get_multiplexed_connection().await?;
            let _: String = redis::cmd("PING").query_async(&mut conn).await?;
            Ok::<(), anyhow::Error>(())
        })
        .await
        {
            Ok(Ok(())) => true,
            Ok(Err(_)) => false,
            Err(_) => false,
        }
    }

    /// Check health with default timeout (2 seconds)
    pub async fn is_healthy(&self) -> bool {
        self.check_health(Duration::from_secs(2)).await
    }
}

impl Default for RedisService {
    /// Create a Redis service with environment-based configuration.
    ///
    /// Panics if the client cannot be created; use `RedisService::from_env()` for error handling.
    fn default() -> Self {
        Self::from_env().expect("Failed to create default Redis service")
    }
}