1pub mod cluster;
27pub mod sentinel;
28pub mod server;
29pub mod utils;
30
31use std::collections::VecDeque;
32use std::sync::{Arc, Mutex};
33
34use redis::{Cmd, ConnectionLike, ErrorKind, Pipeline, RedisError, RedisResult, Value};
35
36#[cfg(feature = "aio")]
37use futures::{future, FutureExt};
38
39#[cfg(feature = "aio")]
40use redis::{aio::ConnectionLike as AioConnectionLike, RedisFuture};
41
42pub trait IntoRedisValue {
46 fn into_redis_value(self) -> Value;
48}
49
50impl IntoRedisValue for String {
51 fn into_redis_value(self) -> Value {
52 Value::BulkString(self.as_bytes().to_vec())
53 }
54}
55
56impl IntoRedisValue for &str {
57 fn into_redis_value(self) -> Value {
58 Value::BulkString(self.as_bytes().to_vec())
59 }
60}
61
62#[cfg(feature = "bytes")]
63impl IntoRedisValue for bytes::Bytes {
64 fn into_redis_value(self) -> Value {
65 Value::BulkString(self.to_vec())
66 }
67}
68
69impl IntoRedisValue for Vec<u8> {
70 fn into_redis_value(self) -> Value {
71 Value::BulkString(self)
72 }
73}
74
75impl IntoRedisValue for Value {
76 fn into_redis_value(self) -> Value {
77 self
78 }
79}
80
81impl IntoRedisValue for i64 {
82 fn into_redis_value(self) -> Value {
83 Value::Int(self)
84 }
85}
86
87pub trait IntoRedisCmdBytes {
90 fn into_redis_cmd_bytes(self) -> Vec<u8>;
92}
93
94impl IntoRedisCmdBytes for Cmd {
95 fn into_redis_cmd_bytes(self) -> Vec<u8> {
96 self.get_packed_command()
97 }
98}
99
100impl IntoRedisCmdBytes for &Cmd {
101 fn into_redis_cmd_bytes(self) -> Vec<u8> {
102 self.get_packed_command()
103 }
104}
105
106impl IntoRedisCmdBytes for &mut Cmd {
107 fn into_redis_cmd_bytes(self) -> Vec<u8> {
108 self.get_packed_command()
109 }
110}
111
112impl IntoRedisCmdBytes for Pipeline {
113 fn into_redis_cmd_bytes(self) -> Vec<u8> {
114 self.get_packed_pipeline()
115 }
116}
117
118impl IntoRedisCmdBytes for &Pipeline {
119 fn into_redis_cmd_bytes(self) -> Vec<u8> {
120 self.get_packed_pipeline()
121 }
122}
123
124impl IntoRedisCmdBytes for &mut Pipeline {
125 fn into_redis_cmd_bytes(self) -> Vec<u8> {
126 self.get_packed_pipeline()
127 }
128}
129
130pub struct MockCmd {
132 cmd_bytes: Vec<u8>,
133 responses: Result<Vec<Value>, RedisError>,
134}
135
136impl MockCmd {
137 pub fn new<C, V>(cmd: C, response: Result<V, RedisError>) -> Self
140 where
141 C: IntoRedisCmdBytes,
142 V: IntoRedisValue,
143 {
144 MockCmd {
145 cmd_bytes: cmd.into_redis_cmd_bytes(),
146 responses: response.map(|r| vec![r.into_redis_value()]),
147 }
148 }
149
150 pub fn with_values<C, V>(cmd: C, responses: Result<Vec<V>, RedisError>) -> Self
153 where
154 C: IntoRedisCmdBytes,
155 V: IntoRedisValue,
156 {
157 MockCmd {
158 cmd_bytes: cmd.into_redis_cmd_bytes(),
159 responses: responses.map(|xs| xs.into_iter().map(|x| x.into_redis_value()).collect()),
160 }
161 }
162}
163
164#[derive(Clone)]
167pub struct MockRedisConnection {
168 commands: Arc<Mutex<VecDeque<MockCmd>>>,
169}
170
171impl MockRedisConnection {
172 pub fn new<I>(commands: I) -> Self
174 where
175 I: IntoIterator<Item = MockCmd>,
176 {
177 MockRedisConnection {
178 commands: Arc::new(Mutex::new(VecDeque::from_iter(commands))),
179 }
180 }
181}
182
183impl ConnectionLike for MockRedisConnection {
184 fn req_packed_command(&mut self, cmd: &[u8]) -> RedisResult<Value> {
185 let mut commands = self.commands.lock().unwrap();
186 let next_cmd = commands.pop_front().ok_or_else(|| {
187 RedisError::from((ErrorKind::Client, "TEST", "unexpected command".to_owned()))
188 })?;
189
190 if cmd != next_cmd.cmd_bytes {
191 return Err(RedisError::from((
192 ErrorKind::Client,
193 "TEST",
194 format!(
195 "unexpected command: expected={}, actual={}",
196 String::from_utf8(next_cmd.cmd_bytes)
197 .unwrap_or_else(|_| "decode error".to_owned()),
198 String::from_utf8(Vec::from(cmd)).unwrap_or_else(|_| "decode error".to_owned()),
199 ),
200 )));
201 }
202
203 next_cmd
204 .responses
205 .and_then(|values| match values.as_slice() {
206 [value] => Ok(value.clone()),
207 [] => Err(RedisError::from((
208 ErrorKind::Client,
209 "no value configured as response",
210 ))),
211 _ => Err(RedisError::from((
212 ErrorKind::Client,
213 "multiple values configured as response for command expecting a single value",
214 ))),
215 })
216 }
217
218 fn req_packed_commands(
219 &mut self,
220 cmd: &[u8],
221 _offset: usize,
222 _count: usize,
223 ) -> RedisResult<Vec<Value>> {
224 let mut commands = self.commands.lock().unwrap();
225 let next_cmd = commands.pop_front().ok_or_else(|| {
226 RedisError::from((ErrorKind::Client, "TEST", "unexpected command".to_owned()))
227 })?;
228
229 if cmd != next_cmd.cmd_bytes {
230 return Err(RedisError::from((
231 ErrorKind::Client,
232 "TEST",
233 format!(
234 "unexpected command: expected={}, actual={}",
235 String::from_utf8(next_cmd.cmd_bytes)
236 .unwrap_or_else(|_| "decode error".to_owned()),
237 String::from_utf8(Vec::from(cmd)).unwrap_or_else(|_| "decode error".to_owned()),
238 ),
239 )));
240 }
241
242 next_cmd.responses
243 }
244
245 fn get_db(&self) -> i64 {
246 0
247 }
248
249 fn check_connection(&mut self) -> bool {
250 true
251 }
252
253 fn is_open(&self) -> bool {
254 true
255 }
256}
257
258#[cfg(feature = "aio")]
259impl AioConnectionLike for MockRedisConnection {
260 fn req_packed_command<'a>(&'a mut self, cmd: &'a Cmd) -> RedisFuture<'a, Value> {
261 let packed_cmd = cmd.get_packed_command();
262 let response = <MockRedisConnection as ConnectionLike>::req_packed_command(
263 self,
264 packed_cmd.as_slice(),
265 );
266 future::ready(response).boxed()
267 }
268
269 fn req_packed_commands<'a>(
270 &'a mut self,
271 cmd: &'a Pipeline,
272 offset: usize,
273 count: usize,
274 ) -> RedisFuture<'a, Vec<Value>> {
275 let packed_cmd = cmd.get_packed_pipeline();
276 let response = <MockRedisConnection as ConnectionLike>::req_packed_commands(
277 self,
278 packed_cmd.as_slice(),
279 offset,
280 count,
281 );
282 future::ready(response).boxed()
283 }
284
285 fn get_db(&self) -> i64 {
286 0
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::{MockCmd, MockRedisConnection};
293 use redis::{cmd, pipe, ErrorKind, Value};
294
295 #[test]
296 fn sync_basic_test() {
297 let mut conn = MockRedisConnection::new(vec![
298 MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
299 MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
300 MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
301 MockCmd::new(cmd("GET").arg("bar"), Ok("foo")),
302 ]);
303
304 cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
305 assert_eq!(cmd("GET").arg("foo").query(&mut conn), Ok(42));
306
307 cmd("SET").arg("bar").arg("foo").exec(&mut conn).unwrap();
308 assert_eq!(
309 cmd("GET").arg("bar").query(&mut conn),
310 Ok(Value::BulkString(b"foo".as_ref().into()))
311 );
312 }
313
314 #[cfg(feature = "aio")]
315 #[tokio::test]
316 async fn async_basic_test() {
317 let mut conn = MockRedisConnection::new(vec![
318 MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
319 MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
320 MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
321 MockCmd::new(cmd("GET").arg("bar"), Ok("foo")),
322 ]);
323
324 cmd("SET")
325 .arg("foo")
326 .arg("42")
327 .exec_async(&mut conn)
328 .await
329 .unwrap();
330 let result: Result<usize, _> = cmd("GET").arg("foo").query_async(&mut conn).await;
331 assert_eq!(result, Ok(42));
332
333 cmd("SET")
334 .arg("bar")
335 .arg("foo")
336 .exec_async(&mut conn)
337 .await
338 .unwrap();
339 let result: Result<Vec<u8>, _> = cmd("GET").arg("bar").query_async(&mut conn).await;
340 assert_eq!(result.as_deref(), Ok(&b"foo"[..]));
341 }
342
343 #[test]
344 fn errors_for_unexpected_commands() {
345 let mut conn = MockRedisConnection::new(vec![
346 MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
347 MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
348 ]);
349
350 cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
351 assert_eq!(cmd("GET").arg("foo").query(&mut conn), Ok(42));
352
353 let err = cmd("SET")
354 .arg("bar")
355 .arg("foo")
356 .exec(&mut conn)
357 .unwrap_err();
358 assert_eq!(err.kind(), ErrorKind::Client);
359 assert_eq!(err.detail(), Some("unexpected command"));
360 }
361
362 #[test]
363 fn errors_for_mismatched_commands() {
364 let mut conn = MockRedisConnection::new(vec![
365 MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
366 MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
367 MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
368 ]);
369
370 cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
371 let err = cmd("SET")
372 .arg("bar")
373 .arg("foo")
374 .exec(&mut conn)
375 .unwrap_err();
376 assert_eq!(err.kind(), ErrorKind::Client);
377 assert!(err.detail().unwrap().contains("unexpected command"));
378 }
379
380 #[test]
381 fn pipeline_basic_test() {
382 let mut conn = MockRedisConnection::new(vec![MockCmd::with_values(
383 pipe().cmd("GET").arg("foo").cmd("GET").arg("bar"),
384 Ok(vec!["hello", "world"]),
385 )]);
386
387 let results: Vec<String> = pipe()
388 .cmd("GET")
389 .arg("foo")
390 .cmd("GET")
391 .arg("bar")
392 .query(&mut conn)
393 .expect("success");
394 assert_eq!(results, vec!["hello", "world"]);
395 }
396
397 #[test]
398 fn pipeline_atomic_test() {
399 let mut conn = MockRedisConnection::new(vec![MockCmd::with_values(
400 pipe().atomic().cmd("GET").arg("foo").cmd("GET").arg("bar"),
401 Ok(vec![Value::Array(
402 vec!["hello", "world"]
403 .into_iter()
404 .map(|x| Value::BulkString(x.as_bytes().into()))
405 .collect(),
406 )]),
407 )]);
408
409 let results: Vec<String> = pipe()
410 .atomic()
411 .cmd("GET")
412 .arg("foo")
413 .cmd("GET")
414 .arg("bar")
415 .query(&mut conn)
416 .expect("success");
417 assert_eq!(results, vec!["hello", "world"]);
418 }
419}