redis_universal_client/
lib.rs1use redis::{
2 Client, ErrorKind, RedisConnectionInfo, RedisError, RedisResult, cluster::ClusterClient,
3};
4
5#[derive(Clone)]
33pub enum UniversalClient {
34 Client(Client),
35 Cluster(ClusterClient),
36}
37
38impl UniversalClient {
39 pub async fn get_connection(&self) -> RedisResult<UniversalConnection> {
40 match self {
41 Self::Client(cli) => cli
42 .get_multiplexed_async_connection()
43 .await
44 .map(UniversalConnection::Client),
45 Self::Cluster(cli) => cli
46 .get_async_connection()
47 .await
48 .map(|c| UniversalConnection::Cluster(Box::new(c))),
49 }
50 }
51
52 pub fn open<T: redis::IntoConnectionInfo + Clone>(
59 addrs: Vec<T>,
60 ) -> RedisResult<UniversalClient> {
61 let mut addrs = addrs;
62
63 if addrs.is_empty() {
64 return Err(RedisError::from((
65 ErrorKind::InvalidClientConfig,
66 "No address specified",
67 )));
68 }
69
70 if addrs.len() == 1 {
71 Client::open(addrs.remove(0)).map(Self::Client)
72 } else {
73 ClusterClient::new(addrs).map(Self::Cluster)
74 }
75 }
76}
77
78pub struct UniversalBuilder<T> {
104 addrs: Vec<T>,
105 cluster: bool,
106 username: Option<String>,
107 password: Option<String>,
108}
109
110impl<T> UniversalBuilder<T> {
111 pub fn new(addrs: Vec<T>) -> UniversalBuilder<T> {
112 UniversalBuilder {
113 addrs,
114 cluster: false,
115 username: None,
116 password: None,
117 }
118 }
119
120 pub fn cluster(mut self, flag: bool) -> UniversalBuilder<T> {
121 self.cluster = flag;
122 self
123 }
124
125 pub fn username(mut self, username: impl Into<String>) -> UniversalBuilder<T> {
127 self.username = Some(username.into());
128 self
129 }
130
131 pub fn password(mut self, password: impl Into<String>) -> UniversalBuilder<T> {
133 self.password = Some(password.into());
134 self
135 }
136
137 pub fn build(self) -> RedisResult<UniversalClient>
138 where
139 T: redis::IntoConnectionInfo + Clone,
140 {
141 let UniversalBuilder {
142 mut addrs,
143 cluster,
144 username,
145 password,
146 } = self;
147
148 if addrs.is_empty() {
149 return Err(RedisError::from((
150 ErrorKind::InvalidClientConfig,
151 "No address specified",
152 )));
153 }
154
155 if cluster {
156 let mut builder = ClusterClient::builder(addrs);
157 if let Some(u) = username {
158 builder = builder.username(u);
159 }
160 if let Some(p) = password {
161 builder = builder.password(p);
162 }
163 builder.build().map(UniversalClient::Cluster)
164 } else if username.is_some() || password.is_some() {
165 let conn_info = addrs.remove(0).into_connection_info()?;
166 let orig = conn_info.redis_settings();
167 let mut redis_info = RedisConnectionInfo::default()
168 .set_db(orig.db())
169 .set_protocol(orig.protocol());
170 if let Some(u) = username {
171 redis_info = redis_info.set_username(u);
172 }
173 if let Some(p) = password {
174 redis_info = redis_info.set_password(p);
175 }
176 let conn_info = conn_info.set_redis_settings(redis_info);
177 Client::open(conn_info).map(UniversalClient::Client)
178 } else {
179 Client::open(addrs.remove(0)).map(UniversalClient::Client)
180 }
181 }
182}
183
184#[derive(Clone)]
192pub enum UniversalConnection {
193 Client(redis::aio::MultiplexedConnection),
194 Cluster(Box<redis::cluster_async::ClusterConnection>),
195}
196
197#[cfg(test)]
198impl UniversalClient {
199 fn is_client(&self) -> bool {
200 matches!(self, Self::Client(_))
201 }
202
203 fn is_cluster(&self) -> bool {
204 matches!(self, Self::Cluster(_))
205 }
206}
207
208impl redis::aio::ConnectionLike for UniversalConnection {
209 fn req_packed_command<'a>(
210 &'a mut self,
211 cmd: &'a redis::Cmd,
212 ) -> redis::RedisFuture<'a, redis::Value> {
213 match self {
214 Self::Client(conn) => conn.req_packed_command(cmd),
215 Self::Cluster(conn) => conn.req_packed_command(cmd),
216 }
217 }
218
219 fn req_packed_commands<'a>(
220 &'a mut self,
221 cmd: &'a redis::Pipeline,
222 offset: usize,
223 count: usize,
224 ) -> redis::RedisFuture<'a, Vec<redis::Value>> {
225 match self {
226 Self::Client(conn) => conn.req_packed_commands(cmd, offset, count),
227 Self::Cluster(conn) => conn.req_packed_commands(cmd, offset, count),
228 }
229 }
230
231 fn get_db(&self) -> i64 {
232 match self {
233 Self::Client(conn) => conn.get_db(),
234 Self::Cluster(conn) => conn.get_db(),
235 }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn open_empty_addrs_error() {
245 let result = UniversalClient::open(Vec::<String>::new());
246 assert!(result.is_err());
247 }
248
249 #[test]
250 fn open_single_addr_is_client() {
251 let result = UniversalClient::open(vec!["redis://127.0.0.1:6379"]);
252 assert!(result.unwrap().is_client());
253 }
254
255 #[test]
256 fn open_multiple_addrs_is_cluster() {
257 let result =
258 UniversalClient::open(vec!["redis://127.0.0.1:7000", "redis://127.0.0.1:7001"]);
259 assert!(result.unwrap().is_cluster());
260 }
261
262 #[test]
263 fn builder_empty_addrs_error() {
264 let result = UniversalBuilder::new(Vec::<String>::new()).build();
265 assert!(result.is_err());
266 }
267
268 #[test]
269 fn builder_cluster_true_forces_cluster() {
270 let result = UniversalBuilder::new(vec!["redis://127.0.0.1:6379".to_string()])
271 .cluster(true)
272 .build();
273 assert!(result.unwrap().is_cluster());
274 }
275
276 #[test]
277 fn builder_cluster_false_uses_first_addr() {
278 let result = UniversalBuilder::new(vec![
279 "redis://127.0.0.1:7000".to_string(),
280 "redis://127.0.0.1:7001".to_string(),
281 ])
282 .cluster(false)
283 .build();
284 assert!(result.unwrap().is_client());
285 }
286
287 #[test]
288 fn builder_with_password_is_client() {
289 let result = UniversalBuilder::new(vec!["redis://127.0.0.1:6379".to_string()])
290 .password("secret")
291 .build();
292 assert!(result.unwrap().is_client());
293 }
294
295 #[test]
296 fn builder_with_username_and_password_is_client() {
297 let result = UniversalBuilder::new(vec!["redis://127.0.0.1:6379".to_string()])
298 .username("alice")
299 .password("secret")
300 .build();
301 assert!(result.unwrap().is_client());
302 }
303
304 #[test]
305 fn builder_with_password_cluster_is_cluster() {
306 let result = UniversalBuilder::new(vec![
307 "redis://127.0.0.1:7000".to_string(),
308 "redis://127.0.0.1:7001".to_string(),
309 ])
310 .password("secret")
311 .cluster(true)
312 .build();
313 assert!(result.unwrap().is_cluster());
314 }
315}