Skip to main content

rig_redis/
lib.rs

1//! Redis support utilities for Rig integrations.
2//!
3//! The crate intentionally starts with client-agnostic configuration and key
4//! helpers. That keeps the public surface useful for Redis-backed Rig code
5//! without forcing a specific async runtime or Redis client choice.
6
7use std::fmt;
8
9const DEFAULT_NAMESPACE: &str = "rig";
10const DEFAULT_INDEX: &str = "rig:index";
11
12/// Redis connection and namespace settings.
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct RedisConfig {
15    url: String,
16    namespace: String,
17    index: String,
18}
19
20impl RedisConfig {
21    /// Creates a new Redis configuration using the default namespace and index.
22    pub fn new(url: impl Into<String>) -> Self {
23        Self {
24            url: url.into(),
25            namespace: DEFAULT_NAMESPACE.to_string(),
26            index: DEFAULT_INDEX.to_string(),
27        }
28    }
29
30    /// Returns the Redis connection URL.
31    pub fn url(&self) -> &str {
32        &self.url
33    }
34
35    /// Returns the namespace prefix used for generated Redis keys.
36    pub fn namespace(&self) -> &str {
37        &self.namespace
38    }
39
40    /// Returns the Redis index name.
41    pub fn index(&self) -> &str {
42        &self.index
43    }
44
45    /// Sets the namespace prefix used for generated Redis keys.
46    pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
47        self.namespace = namespace.into();
48        self
49    }
50
51    /// Sets the Redis index name.
52    pub fn with_index(mut self, index: impl Into<String>) -> Self {
53        self.index = index.into();
54        self
55    }
56}
57
58/// A Redis key with a namespace prefix.
59#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
60pub struct RedisKey(String);
61
62impl RedisKey {
63    /// Creates a namespaced Redis key.
64    pub fn new(namespace: impl AsRef<str>, key: impl AsRef<str>) -> Self {
65        let namespace = namespace.as_ref().trim_matches(':');
66        let key = key.as_ref().trim_matches(':');
67
68        if namespace.is_empty() {
69            Self(key.to_string())
70        } else if key.is_empty() {
71            Self(namespace.to_string())
72        } else {
73            Self(format!("{namespace}:{key}"))
74        }
75    }
76
77    /// Returns the key as a string slice.
78    pub fn as_str(&self) -> &str {
79        &self.0
80    }
81
82    /// Consumes the key and returns the underlying string.
83    pub fn into_string(self) -> String {
84        self.0
85    }
86}
87
88impl fmt::Display for RedisKey {
89    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90        self.0.fmt(formatter)
91    }
92}
93
94impl AsRef<str> for RedisKey {
95    fn as_ref(&self) -> &str {
96        self.as_str()
97    }
98}
99
100impl From<RedisKey> for String {
101    fn from(key: RedisKey) -> Self {
102        key.into_string()
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn config_uses_defaults() {
112        let config = RedisConfig::new("redis://localhost:6379");
113
114        assert_eq!(config.url(), "redis://localhost:6379");
115        assert_eq!(config.namespace(), "rig");
116        assert_eq!(config.index(), "rig:index");
117    }
118
119    #[test]
120    fn config_can_be_customized() {
121        let config = RedisConfig::new("redis://localhost:6379")
122            .with_namespace("agents")
123            .with_index("agent-memory");
124
125        assert_eq!(config.namespace(), "agents");
126        assert_eq!(config.index(), "agent-memory");
127    }
128
129    #[test]
130    fn key_joins_namespace_and_key() {
131        let key = RedisKey::new("rig", "documents:1");
132
133        assert_eq!(key.as_str(), "rig:documents:1");
134        assert_eq!(key.to_string(), "rig:documents:1");
135    }
136
137    #[test]
138    fn key_trims_extra_separators() {
139        let key = RedisKey::new("rig:", ":documents:1:");
140
141        assert_eq!(key.as_str(), "rig:documents:1");
142    }
143
144    #[test]
145    fn key_allows_empty_namespace() {
146        let key = RedisKey::new("", "documents:1");
147
148        assert_eq!(key.as_str(), "documents:1");
149    }
150}