mecha10_core/secrets/
redis.rs

1//! Redis Secret Backend
2
3use super::SecretBackend;
4use crate::{Mecha10Error, Result};
5
6/// Secret backend using Redis for storage
7///
8/// Stores secrets in Redis with optional TTL. Useful for temporary secrets
9/// and distributed systems.
10///
11/// # Example
12///
13/// ```rust
14/// use mecha10::secrets::{RedisBackend, SecretBackend};
15///
16/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
17/// let backend = RedisBackend::connect("redis://localhost:6379", "secrets:").await?;
18///
19/// // Store a secret
20/// backend.set("api_key", "secret_value").await?;
21///
22/// // Retrieve it
23/// let api_key = backend.get("api_key").await?;
24/// # Ok(())
25/// # }
26/// ```
27pub struct RedisBackend {
28    client: redis::Client,
29    prefix: String,
30}
31
32impl RedisBackend {
33    /// Connect to Redis
34    ///
35    /// # Arguments
36    ///
37    /// * `url` - Redis connection URL
38    /// * `prefix` - Key prefix for secrets (e.g., "secrets:")
39    pub async fn connect(url: &str, prefix: &str) -> Result<Self> {
40        let client = redis::Client::open(url)
41            .map_err(|e| Mecha10Error::Configuration(format!("Failed to connect to Redis: {}", e)))?;
42
43        Ok(Self {
44            client,
45            prefix: prefix.to_string(),
46        })
47    }
48
49    /// Connect using REDIS_URL environment variable
50    pub async fn from_env(prefix: &str) -> Result<Self> {
51        let url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://localhost:6379".to_string());
52        Self::connect(&url, prefix).await
53    }
54
55    fn make_key(&self, key: &str) -> String {
56        format!("{}{}", self.prefix, key)
57    }
58}
59
60#[async_trait::async_trait]
61impl SecretBackend for RedisBackend {
62    async fn get(&self, key: &str) -> Result<String> {
63        use redis::AsyncCommands;
64
65        let mut conn = self
66            .client
67            .get_multiplexed_async_connection()
68            .await
69            .map_err(|e| Mecha10Error::Other(format!("Failed to get Redis connection: {}", e)))?;
70
71        let redis_key = self.make_key(key);
72        let value: Option<String> = conn
73            .get(&redis_key)
74            .await
75            .map_err(|e| Mecha10Error::Other(format!("Failed to get secret from Redis: {}", e)))?;
76
77        value.ok_or_else(|| Mecha10Error::Configuration(format!("Secret '{}' not found in Redis", key)))
78    }
79
80    async fn set(&self, key: &str, value: &str) -> Result<()> {
81        use redis::AsyncCommands;
82
83        let mut conn = self
84            .client
85            .get_multiplexed_async_connection()
86            .await
87            .map_err(|e| Mecha10Error::Other(format!("Failed to get Redis connection: {}", e)))?;
88
89        let redis_key = self.make_key(key);
90        conn.set::<_, _, ()>(&redis_key, value)
91            .await
92            .map_err(|e| Mecha10Error::Other(format!("Failed to set secret in Redis: {}", e)))?;
93
94        Ok(())
95    }
96
97    async fn delete(&self, key: &str) -> Result<()> {
98        use redis::AsyncCommands;
99
100        let mut conn = self
101            .client
102            .get_multiplexed_async_connection()
103            .await
104            .map_err(|e| Mecha10Error::Other(format!("Failed to get Redis connection: {}", e)))?;
105
106        let redis_key = self.make_key(key);
107        conn.del::<_, ()>(&redis_key)
108            .await
109            .map_err(|e| Mecha10Error::Other(format!("Failed to delete secret from Redis: {}", e)))?;
110
111        Ok(())
112    }
113
114    async fn list(&self) -> Result<Vec<String>> {
115        use redis::AsyncCommands;
116
117        let mut conn = self
118            .client
119            .get_multiplexed_async_connection()
120            .await
121            .map_err(|e| Mecha10Error::Other(format!("Failed to get Redis connection: {}", e)))?;
122
123        let pattern = format!("{}*", self.prefix);
124        let keys: Vec<String> = conn
125            .keys(&pattern)
126            .await
127            .map_err(|e| Mecha10Error::Other(format!("Failed to list secrets from Redis: {}", e)))?;
128
129        // Strip prefix from keys
130        let prefix_len = self.prefix.len();
131        let keys = keys.into_iter().map(|k| k[prefix_len..].to_string()).collect();
132
133        Ok(keys)
134    }
135}