Skip to main content

cc_core/config/
redis.rs

1use std::collections::HashMap;
2
3use serde::Deserialize;
4
5use super::Validate;
6
7// ──────────────────────────────────────────────
8// Redis 配置
9// ──────────────────────────────────────────────
10
11/// 单个 Redis 连接的配置。
12#[derive(Debug, Clone, Default, Deserialize)]
13pub struct RedisConfig {
14    /// 形如 `redis://[:password@]host:port[/db]` 的连接串
15    pub url: String,
16}
17
18impl Validate for RedisConfig {
19    fn validate(&self) -> anyhow::Result<()> {
20        if self.url.is_empty() {
21            anyhow::bail!("Redis url 不能为空");
22        }
23        if !self.url.starts_with("redis://") && !self.url.starts_with("rediss://") {
24            anyhow::bail!(
25                "Redis url 格式无效: `{}`,需以 `redis://` 或 `rediss://` 开头",
26                self.url
27            );
28        }
29        Ok(())
30    }
31}
32
33// ──────────────────────────────────────────────
34// Redis 子构建器
35// ──────────────────────────────────────────────
36
37/// Redis 单连接构建器。
38pub struct RedisConfigBuilder(pub(crate) RedisConfig);
39
40impl RedisConfigBuilder {
41    pub fn url(mut self, v: impl Into<String>) -> Self {
42        self.0.url = v.into();
43        self
44    }
45}
46
47// ──────────────────────────────────────────────
48// 环境变量解析
49// ──────────────────────────────────────────────
50
51pub(crate) fn collect_env_redis(
52    prefix: &str,
53    existing: &HashMap<String, RedisConfig>,
54) -> anyhow::Result<HashMap<String, RedisConfig>> {
55    let mut result = HashMap::new();
56    let pfx_upper = prefix.to_uppercase();
57
58    for (key, val) in std::env::vars() {
59        let upper = key.to_uppercase();
60        let rest = match upper.strip_prefix(&format!("{pfx_upper}_REDIS_")) {
61            Some(r) => r,
62            None => continue,
63        };
64        let (name, field) = match rest.rsplit_once('_') {
65            Some((n, f)) => (n.to_lowercase(), f),
66            None => continue,
67        };
68
69        if field == "URL" {
70            let entry = result
71                .entry(name.clone())
72                .or_insert_with(|| existing.get(&name).cloned().unwrap_or_default());
73            entry.url = val;
74        }
75    }
76    Ok(result)
77}
78
79// ──────────────────────────────────────────────
80// Tests
81// ──────────────────────────────────────────────
82
83#[cfg(test)]
84mod tests {
85    use crate::ConfigBuilder;
86
87    #[test]
88    fn validation_rejects_bad_redis_url() {
89        let result = ConfigBuilder::new()
90            .with_redis("default", |r| r.url("http://wrong"))
91            .build();
92        assert!(result.is_err());
93        assert!(result.unwrap_err().to_string().contains("redis://"));
94    }
95}