1use std::fmt;
8
9const DEFAULT_NAMESPACE: &str = "rig";
10const DEFAULT_INDEX: &str = "rig:index";
11
12#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct RedisConfig {
15 url: String,
16 namespace: String,
17 index: String,
18}
19
20impl RedisConfig {
21 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 pub fn url(&self) -> &str {
32 &self.url
33 }
34
35 pub fn namespace(&self) -> &str {
37 &self.namespace
38 }
39
40 pub fn index(&self) -> &str {
42 &self.index
43 }
44
45 pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
47 self.namespace = namespace.into();
48 self
49 }
50
51 pub fn with_index(mut self, index: impl Into<String>) -> Self {
53 self.index = index.into();
54 self
55 }
56}
57
58#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
60pub struct RedisKey(String);
61
62impl RedisKey {
63 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 pub fn as_str(&self) -> &str {
79 &self.0
80 }
81
82 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}