1use crate::ValkeyResult;
25use crate::cmd::Cmd;
26use crate::commands::core::CommandDispatch;
27use crate::value::FromValkeyValue;
28
29#[cfg(feature = "sync")]
30use crate::commands::core::SyncCommandDispatch;
31
32#[derive(Debug)]
35struct PageSpec {
36 prefix: Vec<Vec<u8>>,
37 suffix: Vec<Vec<u8>>,
38}
39
40impl PageSpec {
41 fn to_cmd(&self, cursor: u64) -> Cmd {
42 let mut cmd = Cmd::new();
43 for a in &self.prefix {
44 cmd.arg(&a[..]);
45 }
46 cmd.arg(cursor);
47 for a in &self.suffix {
48 cmd.arg(&a[..]);
49 }
50 cmd
51 }
52}
53
54pub struct ScanIter<'a, C: ?Sized, RV> {
66 con: &'a C,
67 spec: PageSpec,
68 cursor: u64,
69 batch: std::vec::IntoIter<RV>,
70}
71
72impl<'a, C: CommandDispatch, RV: FromValkeyValue> ScanIter<'a, C, RV> {
73 pub(crate) async fn new(
76 con: &'a C,
77 prefix: Vec<Vec<u8>>,
78 suffix: Vec<Vec<u8>>,
79 ) -> ValkeyResult<ScanIter<'a, C, RV>> {
80 let spec = PageSpec { prefix, suffix };
81
82 let (cursor, batch): (u64, Vec<RV>) = FromValkeyValue::from_owned_valkey_value(
84 con.glide_dispatch_command(spec.to_cmd(0)).await?,
85 )?;
86 Ok(ScanIter {
87 con,
88 spec,
89 cursor,
90 batch: batch.into_iter(),
91 })
92 }
93
94 pub async fn next_item(&mut self) -> Option<ValkeyResult<RV>> {
98 loop {
101 if let Some(v) = self.batch.next() {
102 return Some(Ok(v));
103 }
104 if self.cursor == 0 {
105 return None;
106 }
107 match self.fetch_page().await {
108 Ok(()) => {}
109 Err(e) => {
110 self.cursor = 0;
112 return Some(Err(e));
113 }
114 }
115 }
116 }
117
118 async fn fetch_page(&mut self) -> ValkeyResult<()> {
120 let reply = self
121 .con
122 .glide_dispatch_command(self.spec.to_cmd(self.cursor))
123 .await?;
124 let (cursor, batch): (u64, Vec<RV>) = FromValkeyValue::from_owned_valkey_value(reply)?;
125 self.cursor = cursor;
126 self.batch = batch.into_iter();
127 Ok(())
128 }
129}
130
131#[cfg(feature = "sync")]
143pub struct SyncScanIter<'a, C: ?Sized, RV> {
144 con: &'a C,
145 spec: PageSpec,
146 cursor: u64,
147 batch: std::vec::IntoIter<RV>,
148}
149
150#[cfg(feature = "sync")]
151impl<'a, C: SyncCommandDispatch, RV: FromValkeyValue> SyncScanIter<'a, C, RV> {
152 pub(crate) fn new(
155 con: &'a C,
156 prefix: Vec<Vec<u8>>,
157 suffix: Vec<Vec<u8>>,
158 ) -> ValkeyResult<SyncScanIter<'a, C, RV>> {
159 let spec = PageSpec { prefix, suffix };
160
161 let (cursor, batch): (u64, Vec<RV>) =
163 FromValkeyValue::from_owned_valkey_value(con.glide_dispatch_command(spec.to_cmd(0))?)?;
164 Ok(SyncScanIter {
165 con,
166 spec,
167 cursor,
168 batch: batch.into_iter(),
169 })
170 }
171
172 fn fetch_page(&mut self) -> ValkeyResult<()> {
174 let reply = self
175 .con
176 .glide_dispatch_command(self.spec.to_cmd(self.cursor))?;
177 let (cursor, batch): (u64, Vec<RV>) = FromValkeyValue::from_owned_valkey_value(reply)?;
178 self.cursor = cursor;
179 self.batch = batch.into_iter();
180 Ok(())
181 }
182}
183
184#[cfg(feature = "sync")]
185impl<C: SyncCommandDispatch, RV: FromValkeyValue> Iterator for SyncScanIter<'_, C, RV> {
186 type Item = ValkeyResult<RV>;
187
188 fn next(&mut self) -> Option<ValkeyResult<RV>> {
192 loop {
195 if let Some(v) = self.batch.next() {
196 return Some(Ok(v));
197 }
198 if self.cursor == 0 {
199 return None;
200 }
201 match self.fetch_page() {
202 Ok(()) => {}
203 Err(e) => {
204 self.cursor = 0;
206 return Some(Err(e));
207 }
208 }
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::error::GlideError;
217 use crate::value::ValkeyValue;
218 use crate::{ValkeyFuture, ValkeyResult};
219 use bytes::Bytes;
220 use std::collections::VecDeque;
221 use std::sync::Mutex;
222
223 struct MockConnection {
225 replies: Mutex<VecDeque<ValkeyResult<ValkeyValue>>>,
226 }
227
228 impl MockConnection {
229 fn new(replies: Vec<ValkeyResult<ValkeyValue>>) -> Self {
230 MockConnection {
231 replies: Mutex::new(replies.into()),
232 }
233 }
234
235 fn pop(&self) -> ValkeyResult<ValkeyValue> {
238 self.replies
239 .lock()
240 .unwrap()
241 .pop_front()
242 .expect("scan iterator requested more pages than the test queued")
243 }
244
245 fn remaining(&self) -> usize {
247 self.replies.lock().unwrap().len()
248 }
249 }
250
251 #[sealed::sealed]
252 impl crate::commands::core::CommandDispatch for MockConnection {
253 fn glide_dispatch_command<'a>(&'a self, _cmd: Cmd) -> ValkeyFuture<'a, ValkeyValue> {
254 Box::pin(async move { self.pop() })
255 }
256 }
257
258 #[cfg(feature = "sync")]
259 #[sealed::sealed]
260 impl crate::commands::core::SyncCommandDispatch for MockConnection {
261 fn glide_dispatch_command(&self, _cmd: Cmd) -> ValkeyResult<ValkeyValue> {
262 self.pop()
263 }
264 }
265
266 fn page(cursor: &str, items: &[&str]) -> ValkeyValue {
268 ValkeyValue::Array(vec![
269 ValkeyValue::BulkString(Bytes::copy_from_slice(cursor.as_bytes())),
270 ValkeyValue::Array(
271 items
272 .iter()
273 .map(|s| ValkeyValue::BulkString(Bytes::copy_from_slice(s.as_bytes())))
274 .collect(),
275 ),
276 ])
277 }
278
279 fn error() -> GlideError {
281 GlideError::Request("simulated scan failure".into())
282 }
283
284 async fn scan_iter(con: &MockConnection) -> ValkeyResult<ScanIter<'_, MockConnection, String>> {
288 ScanIter::new(con, vec![b"SCAN".to_vec()], Vec::new()).await
289 }
290
291 async fn get_items(iter: &mut ScanIter<'_, MockConnection, String>) -> Vec<String> {
293 let mut items = Vec::new();
294 while let Some(item) = iter.next_item().await {
295 items.push(item.expect("unexpected error during scan"));
296 }
297 items
298 }
299
300 #[tokio::test]
301 async fn async_success_empty_page() {
302 let con = MockConnection::new(vec![Ok(page("4", &[])), Ok(page("0", &[]))]);
303 let mut iter = scan_iter(&con).await.unwrap();
304 assert_eq!(get_items(&mut iter).await, Vec::<String>::new());
305 assert_eq!(con.remaining(), 0);
306 }
307
308 #[tokio::test]
309 async fn async_success_one_page() {
310 let con = MockConnection::new(vec![Ok(page("0", &["a", "b"]))]);
311 let mut iter = scan_iter(&con).await.unwrap();
312 assert_eq!(get_items(&mut iter).await, ["a", "b"]);
313 assert_eq!(con.remaining(), 0);
314 }
315
316 #[tokio::test]
317 async fn async_success_multi_page() {
318 let con = MockConnection::new(vec![Ok(page("6", &["a", "b"])), Ok(page("0", &["c"]))]);
319 let mut iter = scan_iter(&con).await.unwrap();
320 assert_eq!(get_items(&mut iter).await, ["a", "b", "c"]);
321 assert_eq!(con.remaining(), 0);
322 }
323
324 #[tokio::test]
325 async fn async_error_new() {
326 let con = MockConnection::new(vec![Err(error())]);
327 assert!(scan_iter(&con).await.is_err());
328 }
329
330 #[tokio::test]
331 async fn async_error_scan() {
332 let con = MockConnection::new(vec![Ok(page("5", &["a"])), Err(error())]);
333 let mut iter = scan_iter(&con).await.unwrap();
334 assert_eq!(iter.next_item().await.unwrap().unwrap(), "a");
335 assert!(iter.next_item().await.expect("expected an error").is_err());
336 assert!(iter.next_item().await.is_none());
337 }
338
339 #[cfg(feature = "sync")]
343 fn sync_scan_iter(
344 con: &MockConnection,
345 ) -> ValkeyResult<SyncScanIter<'_, MockConnection, String>> {
346 SyncScanIter::new(con, vec![b"SCAN".to_vec()], Vec::new())
347 }
348
349 #[cfg(feature = "sync")]
351 fn sync_get_items(iter: SyncScanIter<'_, MockConnection, String>) -> Vec<String> {
352 iter.collect::<ValkeyResult<Vec<String>>>()
353 .expect("unexpected error during scan")
354 }
355
356 #[cfg(feature = "sync")]
357 #[test]
358 fn sync_success_empty_page() {
359 let con = MockConnection::new(vec![Ok(page("4", &[])), Ok(page("0", &[]))]);
360 let items = sync_get_items(sync_scan_iter(&con).unwrap());
361 assert_eq!(items, Vec::<String>::new());
362 assert_eq!(con.remaining(), 0);
363 }
364
365 #[cfg(feature = "sync")]
366 #[test]
367 fn sync_success_one_page() {
368 let con = MockConnection::new(vec![Ok(page("0", &["a", "b"]))]);
369 let items = sync_get_items(sync_scan_iter(&con).unwrap());
370 assert_eq!(items, ["a", "b"]);
371 assert_eq!(con.remaining(), 0);
372 }
373
374 #[cfg(feature = "sync")]
375 #[test]
376 fn sync_success_multi_page() {
377 let con = MockConnection::new(vec![Ok(page("6", &["a", "b"])), Ok(page("0", &["c"]))]);
378 let items = sync_get_items(sync_scan_iter(&con).unwrap());
379 assert_eq!(items, ["a", "b", "c"]);
380 assert_eq!(con.remaining(), 0);
381 }
382
383 #[cfg(feature = "sync")]
384 #[test]
385 fn sync_error_new() {
386 let con = MockConnection::new(vec![Err(error())]);
387 assert!(sync_scan_iter(&con).is_err());
388 }
389
390 #[cfg(feature = "sync")]
391 #[test]
392 fn sync_error_scan() {
393 let con = MockConnection::new(vec![Ok(page("5", &["a"])), Err(error())]);
394 let mut iter = sync_scan_iter(&con).unwrap();
395 assert_eq!(iter.next().unwrap().unwrap(), "a");
396 assert!(iter.next().expect("expected an error").is_err());
397 assert!(iter.next().is_none());
398 }
399}