rs-zero 0.2.7

Rust-first microservice framework inspired by go-zero engineering practices
Documentation
use crate::cache_redis::{RedisCacheConfig, RedisCacheError, RedisCacheResult};

/// Factory for Redis clients.
#[derive(Debug, Clone)]
pub struct RedisClientFactory {
    config: RedisCacheConfig,
}

impl RedisClientFactory {
    /// Creates a factory and validates the URL shape.
    pub fn new(config: RedisCacheConfig) -> RedisCacheResult<Self> {
        config.validate()?;
        Ok(Self { config })
    }

    /// Returns the configured Redis URL.
    pub fn url(&self) -> &str {
        &self.config.url
    }

    /// Creates a real single-node Redis client.
    pub fn create_client(&self) -> RedisCacheResult<redis::Client> {
        redis::Client::open(self.config.url.as_str())
            .map_err(|error| RedisCacheError::Connection(error.to_string()))
    }

    /// Creates a real Redis Cluster client from configured startup nodes.
    pub fn create_cluster_client(&self) -> RedisCacheResult<redis::cluster::ClusterClient> {
        let nodes = cluster_startup_nodes(&self.config.url)?;
        let mut builder = redis::cluster::ClusterClient::builder(nodes)
            .retries(self.config.cluster.max_redirects.max(1))
            .connection_timeout(self.config.connect_timeout)
            .response_timeout(self.config.command_timeout);
        if self.config.cluster.read_from_replicas {
            builder = builder.read_from_replicas();
        }
        builder
            .build()
            .map_err(|error| RedisCacheError::Connection(error.to_string()))
    }
}

/// Splits a comma-separated Redis Cluster startup node list.
pub fn cluster_startup_nodes(urls: &str) -> RedisCacheResult<Vec<String>> {
    let nodes = urls
        .split(',')
        .map(str::trim)
        .filter(|url| !url.is_empty())
        .map(str::to_string)
        .collect::<Vec<_>>();
    if nodes.is_empty() {
        return Err(RedisCacheError::InvalidUrl {
            url: urls.to_string(),
        });
    }
    Ok(nodes)
}

#[cfg(test)]
mod tests {
    use super::cluster_startup_nodes;

    #[test]
    fn cluster_startup_nodes_trim_empty_entries() {
        let nodes = cluster_startup_nodes(" redis://127.0.0.1:7000, ,redis://127.0.0.1:7001 ")
            .expect("nodes");

        assert_eq!(
            nodes,
            vec!["redis://127.0.0.1:7000", "redis://127.0.0.1:7001"]
        );
    }
}