Skip to main content

camel_component_redis/
config.rs

1use camel_api::CamelError;
2use camel_endpoint::parse_uri;
3use std::str::FromStr;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum RedisCommand {
7    // String operations
8    Set,
9    Get,
10    Getset,
11    Setnx,
12    Setex,
13    Mget,
14    Mset,
15    Incr,
16    Incrby,
17    Decr,
18    Decrby,
19    Append,
20    Strlen,
21
22    // Key operations
23    Exists,
24    Del,
25    Expire,
26    Expireat,
27    Pexpire,
28    Pexpireat,
29    Ttl,
30    Keys,
31    Rename,
32    Renamenx,
33    Type,
34    Persist,
35    Move,
36    Sort,
37
38    // List operations
39    Lpush,
40    Rpush,
41    Lpushx,
42    Rpushx,
43    Lpop,
44    Rpop,
45    Blpop,
46    Brpop,
47    Llen,
48    Lrange,
49    Lindex,
50    Linsert,
51    Lset,
52    Lrem,
53    Ltrim,
54    Rpoplpush,
55
56    // Hash operations
57    Hset,
58    Hget,
59    Hsetnx,
60    Hmset,
61    Hmget,
62    Hdel,
63    Hexists,
64    Hlen,
65    Hkeys,
66    Hvals,
67    Hgetall,
68    Hincrby,
69
70    // Set operations
71    Sadd,
72    Srem,
73    Smembers,
74    Scard,
75    Sismember,
76    Spop,
77    Smove,
78    Sinter,
79    Sunion,
80    Sdiff,
81    Sinterstore,
82    Sunionstore,
83    Sdiffstore,
84    Srandmember,
85
86    // Sorted set operations
87    Zadd,
88    Zrem,
89    Zrange,
90    Zrevrange,
91    Zrank,
92    Zrevrank,
93    Zscore,
94    Zcard,
95    Zincrby,
96    Zcount,
97    Zrangebyscore,
98    Zrevrangebyscore,
99    Zremrangebyrank,
100    Zremrangebyscore,
101    Zunionstore,
102    Zinterstore,
103
104    // Pub/Sub operations
105    Publish,
106    Subscribe,
107    Psubscribe,
108
109    // Other operations
110    Ping,
111    Echo,
112}
113
114impl FromStr for RedisCommand {
115    type Err = CamelError;
116
117    fn from_str(s: &str) -> Result<Self, Self::Err> {
118        match s.to_uppercase().as_str() {
119            // String operations
120            "SET" => Ok(RedisCommand::Set),
121            "GET" => Ok(RedisCommand::Get),
122            "GETSET" => Ok(RedisCommand::Getset),
123            "SETNX" => Ok(RedisCommand::Setnx),
124            "SETEX" => Ok(RedisCommand::Setex),
125            "MGET" => Ok(RedisCommand::Mget),
126            "MSET" => Ok(RedisCommand::Mset),
127            "INCR" => Ok(RedisCommand::Incr),
128            "INCRBY" => Ok(RedisCommand::Incrby),
129            "DECR" => Ok(RedisCommand::Decr),
130            "DECRBY" => Ok(RedisCommand::Decrby),
131            "APPEND" => Ok(RedisCommand::Append),
132            "STRLEN" => Ok(RedisCommand::Strlen),
133
134            // Key operations
135            "EXISTS" => Ok(RedisCommand::Exists),
136            "DEL" => Ok(RedisCommand::Del),
137            "EXPIRE" => Ok(RedisCommand::Expire),
138            "EXPIREAT" => Ok(RedisCommand::Expireat),
139            "PEXPIRE" => Ok(RedisCommand::Pexpire),
140            "PEXPIREAT" => Ok(RedisCommand::Pexpireat),
141            "TTL" => Ok(RedisCommand::Ttl),
142            "KEYS" => Ok(RedisCommand::Keys),
143            "RENAME" => Ok(RedisCommand::Rename),
144            "RENAMENX" => Ok(RedisCommand::Renamenx),
145            "TYPE" => Ok(RedisCommand::Type),
146            "PERSIST" => Ok(RedisCommand::Persist),
147            "MOVE" => Ok(RedisCommand::Move),
148            "SORT" => Ok(RedisCommand::Sort),
149
150            // List operations
151            "LPUSH" => Ok(RedisCommand::Lpush),
152            "RPUSH" => Ok(RedisCommand::Rpush),
153            "LPUSHX" => Ok(RedisCommand::Lpushx),
154            "RPUSHX" => Ok(RedisCommand::Rpushx),
155            "LPOP" => Ok(RedisCommand::Lpop),
156            "RPOP" => Ok(RedisCommand::Rpop),
157            "BLPOP" => Ok(RedisCommand::Blpop),
158            "BRPOP" => Ok(RedisCommand::Brpop),
159            "LLEN" => Ok(RedisCommand::Llen),
160            "LRANGE" => Ok(RedisCommand::Lrange),
161            "LINDEX" => Ok(RedisCommand::Lindex),
162            "LINSERT" => Ok(RedisCommand::Linsert),
163            "LSET" => Ok(RedisCommand::Lset),
164            "LREM" => Ok(RedisCommand::Lrem),
165            "LTRIM" => Ok(RedisCommand::Ltrim),
166            "RPOPLPUSH" => Ok(RedisCommand::Rpoplpush),
167
168            // Hash operations
169            "HSET" => Ok(RedisCommand::Hset),
170            "HGET" => Ok(RedisCommand::Hget),
171            "HSETNX" => Ok(RedisCommand::Hsetnx),
172            "HMSET" => Ok(RedisCommand::Hmset),
173            "HMGET" => Ok(RedisCommand::Hmget),
174            "HDEL" => Ok(RedisCommand::Hdel),
175            "HEXISTS" => Ok(RedisCommand::Hexists),
176            "HLEN" => Ok(RedisCommand::Hlen),
177            "HKEYS" => Ok(RedisCommand::Hkeys),
178            "HVALS" => Ok(RedisCommand::Hvals),
179            "HGETALL" => Ok(RedisCommand::Hgetall),
180            "HINCRBY" => Ok(RedisCommand::Hincrby),
181
182            // Set operations
183            "SADD" => Ok(RedisCommand::Sadd),
184            "SREM" => Ok(RedisCommand::Srem),
185            "SMEMBERS" => Ok(RedisCommand::Smembers),
186            "SCARD" => Ok(RedisCommand::Scard),
187            "SISMEMBER" => Ok(RedisCommand::Sismember),
188            "SPOP" => Ok(RedisCommand::Spop),
189            "SMOVE" => Ok(RedisCommand::Smove),
190            "SINTER" => Ok(RedisCommand::Sinter),
191            "SUNION" => Ok(RedisCommand::Sunion),
192            "SDIFF" => Ok(RedisCommand::Sdiff),
193            "SINTERSTORE" => Ok(RedisCommand::Sinterstore),
194            "SUNIONSTORE" => Ok(RedisCommand::Sunionstore),
195            "SDIFFSTORE" => Ok(RedisCommand::Sdiffstore),
196            "SRANDMEMBER" => Ok(RedisCommand::Srandmember),
197
198            // Sorted set operations
199            "ZADD" => Ok(RedisCommand::Zadd),
200            "ZREM" => Ok(RedisCommand::Zrem),
201            "ZRANGE" => Ok(RedisCommand::Zrange),
202            "ZREVRANGE" => Ok(RedisCommand::Zrevrange),
203            "ZRANK" => Ok(RedisCommand::Zrank),
204            "ZREVRANK" => Ok(RedisCommand::Zrevrank),
205            "ZSCORE" => Ok(RedisCommand::Zscore),
206            "ZCARD" => Ok(RedisCommand::Zcard),
207            "ZINCRBY" => Ok(RedisCommand::Zincrby),
208            "ZCOUNT" => Ok(RedisCommand::Zcount),
209            "ZRANGEBYSCORE" => Ok(RedisCommand::Zrangebyscore),
210            "ZREVRANGEBYSCORE" => Ok(RedisCommand::Zrevrangebyscore),
211            "ZREMRANGEBYRANK" => Ok(RedisCommand::Zremrangebyrank),
212            "ZREMRANGEBYSCORE" => Ok(RedisCommand::Zremrangebyscore),
213            "ZUNIONSTORE" => Ok(RedisCommand::Zunionstore),
214            "ZINTERSTORE" => Ok(RedisCommand::Zinterstore),
215
216            // Pub/Sub operations
217            "PUBLISH" => Ok(RedisCommand::Publish),
218            "SUBSCRIBE" => Ok(RedisCommand::Subscribe),
219            "PSUBSCRIBE" => Ok(RedisCommand::Psubscribe),
220
221            // Other operations
222            "PING" => Ok(RedisCommand::Ping),
223            "ECHO" => Ok(RedisCommand::Echo),
224
225            _ => Err(CamelError::InvalidUri(format!(
226                "Unknown Redis command: {}",
227                s
228            ))),
229        }
230    }
231}
232
233#[derive(Debug, Clone)]
234pub struct RedisConfig {
235    pub host: String,
236    pub port: u16,
237    pub command: RedisCommand,
238    pub channels: Vec<String>,
239    pub key: Option<String>,
240    pub timeout: u64,
241    pub password: Option<String>,
242    pub db: u8,
243}
244
245impl RedisConfig {
246    pub fn from_uri(uri: &str) -> Result<Self, CamelError> {
247        let parts = parse_uri(uri)?;
248
249        if parts.scheme != "redis" {
250            return Err(CamelError::InvalidUri(format!(
251                "expected scheme 'redis', got '{}'",
252                parts.scheme
253            )));
254        }
255
256        // Parse host and port from path (format: //host:port or //host)
257        let (host, port) = if parts.path.starts_with("//") {
258            let path = &parts.path[2..]; // Remove leading //
259            let (host_part, port_part) = match path.split_once(':') {
260                Some((h, p)) => (h, p),
261                None => (path, "6379"),
262            };
263            let port = port_part.parse().unwrap_or(6379);
264            (host_part.to_string(), port)
265        } else {
266            ("localhost".to_string(), 6379)
267        };
268
269        // Parse command (default to SET)
270        let command = parts
271            .params
272            .get("command")
273            .map(|s| RedisCommand::from_str(s))
274            .transpose()?
275            .unwrap_or(RedisCommand::Set);
276
277        // Parse channels (comma-separated)
278        let channels = parts
279            .params
280            .get("channels")
281            .map(|s| s.split(',').map(String::from).collect())
282            .unwrap_or_default();
283
284        // Parse key
285        let key = parts.params.get("key").cloned();
286
287        // Parse timeout (default to 1 second)
288        let timeout = parts
289            .params
290            .get("timeout")
291            .and_then(|s| s.parse().ok())
292            .unwrap_or(1);
293
294        // Parse password
295        let password = parts.params.get("password").cloned();
296
297        // Parse db (default to 0)
298        let db = parts
299            .params
300            .get("db")
301            .and_then(|s| s.parse().ok())
302            .unwrap_or(0);
303
304        Ok(Self {
305            host,
306            port,
307            command,
308            channels,
309            key,
310            timeout,
311            password,
312            db,
313        })
314    }
315
316    pub fn redis_url(&self) -> String {
317        if let Some(password) = &self.password {
318            format!(
319                "redis://:{}@{}:{}/{}",
320                password, self.host, self.port, self.db
321            )
322        } else {
323            format!("redis://{}:{}/{}", self.host, self.port, self.db)
324        }
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn test_config_defaults() {
334        let c = RedisConfig::from_uri("redis://localhost:6379").unwrap();
335        assert_eq!(c.host, "localhost");
336        assert_eq!(c.port, 6379);
337        assert_eq!(c.command, RedisCommand::Set);
338        assert!(c.channels.is_empty());
339        assert!(c.key.is_none());
340        assert_eq!(c.timeout, 1);
341        assert!(c.password.is_none());
342        assert_eq!(c.db, 0);
343    }
344
345    #[test]
346    fn test_config_wrong_scheme() {
347        assert!(RedisConfig::from_uri("http://localhost:6379").is_err());
348    }
349
350    #[test]
351    fn test_config_command() {
352        let c = RedisConfig::from_uri("redis://localhost:6379?command=GET").unwrap();
353        assert_eq!(c.command, RedisCommand::Get);
354    }
355
356    #[test]
357    fn test_config_subscribe() {
358        let c = RedisConfig::from_uri("redis://localhost:6379?command=SUBSCRIBE&channels=foo,bar")
359            .unwrap();
360        assert_eq!(c.command, RedisCommand::Subscribe);
361        assert_eq!(c.channels, vec!["foo".to_string(), "bar".to_string()]);
362    }
363
364    #[test]
365    fn test_config_blpop() {
366        let c = RedisConfig::from_uri("redis://localhost:6379?command=BLPOP&key=jobs&timeout=5")
367            .unwrap();
368        assert_eq!(c.command, RedisCommand::Blpop);
369        assert_eq!(c.key, Some("jobs".to_string()));
370        assert_eq!(c.timeout, 5);
371    }
372
373    #[test]
374    fn test_config_auth_db() {
375        let c = RedisConfig::from_uri("redis://localhost:6379?password=secret&db=2").unwrap();
376        assert_eq!(c.password, Some("secret".to_string()));
377        assert_eq!(c.db, 2);
378    }
379
380    #[test]
381    fn test_redis_url() {
382        let c = RedisConfig::from_uri("redis://localhost:6379?password=secret&db=2").unwrap();
383        assert_eq!(c.redis_url(), "redis://:secret@localhost:6379/2");
384    }
385
386    #[test]
387    fn test_redis_url_no_auth() {
388        let c = RedisConfig::from_uri("redis://localhost:6379").unwrap();
389        assert_eq!(c.redis_url(), "redis://localhost:6379/0");
390    }
391
392    #[test]
393    fn test_command_from_str() {
394        assert!(RedisCommand::from_str("SET").is_ok());
395        assert_eq!(RedisCommand::from_str("SET").unwrap(), RedisCommand::Set);
396        assert!(RedisCommand::from_str("get").is_ok());
397        assert_eq!(RedisCommand::from_str("get").unwrap(), RedisCommand::Get);
398        assert!(RedisCommand::from_str("UNKNOWN").is_err());
399    }
400}