1use futures::stream::unfold;
83use futures::{Stream, StreamExt};
84use pin_project::pin_project;
85use std::future::Future;
86use std::pin::Pin;
87
88#[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
89#[allow(missing_docs)]
90pub mod internal {
91 use super::*;
96
97 pub trait PageableResponse {
100 type PageItem: Send;
101
102 fn items(self) -> Vec<Self::PageItem>;
105
106 fn next_page_token(&self) -> String;
108 }
109
110 pub fn new_paginator<T, E, F>(
113 seed_token: String,
114 execute: impl Fn(String) -> F + Clone + Send + 'static,
115 ) -> impl Paginator<T, E>
116 where
117 T: internal::PageableResponse,
118 F: Future<Output = Result<T, E>> + Send + 'static,
119 {
120 PaginatorImpl::new(seed_token, execute)
121 }
122}
123
124mod sealed {
125 pub trait Paginator {}
126}
127
128pub trait Paginator<PageType, Error>: Send + sealed::Paginator
146where
147 PageType: internal::PageableResponse,
148{
149 fn items(self) -> impl ItemPaginator<PageType, Error>;
151
152 fn next(&mut self) -> impl Future<Output = Option<Result<PageType, Error>>> + Send;
154
155 #[cfg(feature = "unstable-stream")]
156 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-stream")))]
157 fn into_stream(self) -> impl futures::Stream<Item = Result<PageType, Error>> + Unpin;
159}
160
161#[pin_project]
162struct PaginatorImpl<T, E> {
163 #[pin]
164 stream: Pin<Box<dyn Stream<Item = Result<T, E>> + Send>>,
165}
166
167type ControlFlow = std::ops::ControlFlow<(), String>;
168
169impl<T, E> PaginatorImpl<T, E>
170where
171 T: internal::PageableResponse,
172{
173 pub fn new<F>(
176 seed_token: String,
177 execute: impl Fn(String) -> F + Clone + Send + 'static,
178 ) -> Self
179 where
180 F: Future<Output = Result<T, E>> + Send + 'static,
181 {
182 let stream = unfold(ControlFlow::Continue(seed_token), move |state| {
183 let execute = execute.clone();
184 async move {
185 let token = match state {
186 ControlFlow::Continue(token) => token,
187 ControlFlow::Break(_) => return None,
188 };
189 match execute(token).await {
190 Ok(page_resp) => {
191 let tok = page_resp.next_page_token();
192 let next_state = if tok.is_empty() {
193 ControlFlow::Break(())
194 } else {
195 ControlFlow::Continue(tok)
196 };
197 Some((Ok(page_resp), next_state))
198 }
199 Err(e) => Some((Err(e), ControlFlow::Break(()))),
200 }
201 }
202 });
203 Self {
204 stream: Box::pin(stream),
205 }
206 }
207}
208
209impl<T, E> Paginator<T, E> for PaginatorImpl<T, E>
210where
211 T: internal::PageableResponse,
212{
213 fn items(self) -> impl ItemPaginator<T, E> {
215 ItemPaginatorImpl::new(self)
216 }
217
218 async fn next(&mut self) -> Option<Result<T, E>> {
220 self.stream.next().await
221 }
222
223 #[cfg(feature = "unstable-stream")]
224 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-stream")))]
225 fn into_stream(self) -> impl futures::Stream<Item = Result<T, E>> + Unpin {
227 Box::pin(unfold(self, |mut paginator| async move {
228 paginator.next().await.map(|item| (item, paginator))
229 }))
230 }
231}
232
233impl<T, E> sealed::Paginator for PaginatorImpl<T, E> where T: internal::PageableResponse {}
234
235impl<T, E> std::fmt::Debug for PaginatorImpl<T, E> {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 f.debug_struct("Paginator").finish()
238 }
239}
240
241pub trait ItemPaginator<PageType, Error>: Send + sealed::Paginator
263where
264 PageType: internal::PageableResponse,
265{
266 fn next(&mut self) -> impl Future<Output = Option<Result<PageType::PageItem, Error>>> + Send;
268
269 #[cfg(feature = "unstable-stream")]
270 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-stream")))]
271 fn into_stream(self) -> impl futures::Stream<Item = Result<PageType::PageItem, Error>> + Unpin;
273}
274
275#[pin_project]
276struct ItemPaginatorImpl<T, E>
277where
278 T: internal::PageableResponse,
279{
280 #[pin]
281 stream: PaginatorImpl<T, E>,
282 current_items: Option<std::vec::IntoIter<T::PageItem>>,
283}
284
285impl<T, E> ItemPaginatorImpl<T, E>
286where
287 T: internal::PageableResponse,
288{
289 fn new(paginator: PaginatorImpl<T, E>) -> Self {
291 Self {
292 stream: paginator,
293 current_items: None,
294 }
295 }
296}
297
298impl<T, E> ItemPaginator<T, E> for ItemPaginatorImpl<T, E>
299where
300 T: internal::PageableResponse,
301{
302 async fn next(&mut self) -> Option<Result<T::PageItem, E>> {
306 loop {
307 if let Some(ref mut iter) = self.current_items
308 && let Some(item) = iter.next()
309 {
310 return Some(Ok(item));
311 }
312
313 let next_page = self.stream.next().await;
314 match next_page {
315 Some(Ok(page)) => {
316 self.current_items = Some(page.items().into_iter());
317 }
318 Some(Err(e)) => {
319 return Some(Err(e));
320 }
321 None => return None,
322 }
323 }
324 }
325
326 #[cfg(feature = "unstable-stream")]
327 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-stream")))]
328 fn into_stream(self) -> impl Stream<Item = Result<T::PageItem, E>> + Unpin {
330 Box::pin(unfold(self, |mut paginator| async move {
331 paginator.next().await.map(|item| (item, paginator))
332 }))
333 }
334}
335
336impl<T, E> sealed::Paginator for ItemPaginatorImpl<T, E> where T: internal::PageableResponse {}
337
338#[cfg(test)]
339mod tests {
340 use super::internal::*;
341 use super::*;
342 use std::collections::VecDeque;
343 use std::sync::{Arc, Mutex};
344
345 #[derive(Clone, Default)]
346 struct TestRequest {
347 page_token: String,
348 }
349
350 struct TestResponse {
351 items: Vec<PageItem>,
352 next_page_token: String,
353 }
354
355 #[derive(Clone)]
356 struct PageItem {
357 name: String,
358 }
359
360 impl PageableResponse for TestResponse {
361 type PageItem = PageItem;
362
363 fn items(self) -> Vec<Self::PageItem> {
364 self.items
365 }
366
367 fn next_page_token(&self) -> String {
368 self.next_page_token.clone()
369 }
370 }
371
372 #[derive(Clone)]
373 struct Client {
374 inner: Arc<InnerClient>,
375 }
376
377 struct InnerClient {
378 data: Arc<Mutex<Vec<TestResponse>>>,
379 }
380
381 impl Client {
382 async fn execute(
383 data: Arc<Mutex<Vec<TestResponse>>>,
384 _: TestRequest,
385 ) -> Result<TestResponse, Box<dyn std::error::Error>> {
386 let mut responses = data.lock().unwrap();
388 let resp: TestResponse = responses.remove(0);
389 Ok(resp)
390 }
391
392 async fn list_rpc(
393 &self,
394 req: TestRequest,
395 ) -> Result<TestResponse, Box<dyn std::error::Error>> {
396 let inner = self.inner.clone();
397 Client::execute(inner.data.clone(), req).await
398 }
399
400 fn list_rpc_stream(
401 &self,
402 req: TestRequest,
403 ) -> impl Paginator<TestResponse, Box<dyn std::error::Error>> {
404 let client = self.clone();
405 let tok = req.page_token.clone();
406 let execute = move |token| {
407 let mut req = req.clone();
408 let client = client.clone();
409 req.page_token = token;
410 async move { client.list_rpc(req).await }
411 };
412 new_paginator(tok, execute)
413 }
414 }
415
416 #[tokio::test]
417 async fn test_pagination() {
418 let seed = "token1".to_string();
419 let mut responses = VecDeque::new();
420 responses.push_back(TestResponse {
421 items: vec![
422 PageItem {
423 name: "item1".to_string(),
424 },
425 PageItem {
426 name: "item2".to_string(),
427 },
428 ],
429 next_page_token: "token2".to_string(),
430 });
431 responses.push_back(TestResponse {
432 items: vec![PageItem {
433 name: "item3".to_string(),
434 }],
435 next_page_token: "".to_string(),
436 });
437 let mut expected_tokens = VecDeque::new();
438 expected_tokens.push_back("token1".to_string());
439 expected_tokens.push_back("token2".to_string());
440
441 let state = Arc::new(Mutex::new(responses));
442 let tokens = Arc::new(Mutex::new(expected_tokens));
443
444 let execute = move |token: String| {
445 let expected_token = tokens.clone().lock().unwrap().pop_front().unwrap();
446 assert_eq!(token, expected_token);
447 let resp: TestResponse = state.clone().lock().unwrap().pop_front().unwrap();
448 async move { Ok::<_, Box<dyn std::error::Error>>(resp) }
449 };
450
451 let mut resps = vec![];
452 let mut paginator = new_paginator(seed, execute);
453 while let Some(resp) = paginator.next().await {
454 if let Ok(resp) = resp {
455 resps.push(resp)
456 }
457 }
458 assert_eq!(resps.len(), 2);
459 assert_eq!(resps[0].items[0].name, "item1");
460 assert_eq!(resps[0].items[1].name, "item2");
461 }
462
463 #[tokio::test]
464 async fn test_paginator_as_client() {
465 let responses = vec![
466 TestResponse {
467 items: vec![
468 PageItem {
469 name: "item1".to_string(),
470 },
471 PageItem {
472 name: "item2".to_string(),
473 },
474 ],
475 next_page_token: "token1".to_string(),
476 },
477 TestResponse {
478 items: vec![PageItem {
479 name: "item3".to_string(),
480 }],
481 next_page_token: "".to_string(),
482 },
483 ];
484
485 let client = Client {
486 inner: Arc::new(InnerClient {
487 data: Arc::new(Mutex::new(responses)),
488 }),
489 };
490 let mut resps = vec![];
491 let mut paginator = client.list_rpc_stream(TestRequest::default());
492 while let Some(resp) = paginator.next().await {
493 if let Ok(resp) = resp {
494 resps.push(resp)
495 }
496 }
497 assert_eq!(resps.len(), 2);
498 assert_eq!(resps[0].items[0].name, "item1");
499 assert_eq!(resps[0].items[1].name, "item2");
500 assert_eq!(resps[1].items[0].name, "item3");
501 }
502
503 #[tokio::test]
504 async fn test_paginator_error() {
505 let execute = |_| async { Err::<TestResponse, Box<dyn std::error::Error>>("err".into()) };
506
507 let mut paginator = new_paginator(String::new(), execute);
508 let mut count = 0;
509 while let Some(resp) = paginator.next().await {
510 match resp {
511 Ok(_) => {
512 panic!("Should not succeed");
513 }
514 Err(e) => {
515 assert_eq!(e.to_string(), "err");
516 count += 1;
517 }
518 }
519 }
520 assert_eq!(count, 1);
521 }
522
523 #[cfg(feature = "unstable-stream")]
524 #[tokio::test]
525 async fn test_paginator_into_stream() {
526 let responses = vec![
527 TestResponse {
528 items: vec![
529 PageItem {
530 name: "item1".to_string(),
531 },
532 PageItem {
533 name: "item2".to_string(),
534 },
535 ],
536 next_page_token: "token1".to_string(),
537 },
538 TestResponse {
539 items: vec![PageItem {
540 name: "item3".to_string(),
541 }],
542 next_page_token: "".to_string(),
543 },
544 ];
545
546 let client = Client {
547 inner: Arc::new(InnerClient {
548 data: Arc::new(Mutex::new(responses)),
549 }),
550 };
551 let mut resps = vec![];
552 let mut stream = client.list_rpc_stream(TestRequest::default()).into_stream();
553 while let Some(resp) = stream.next().await {
554 if let Ok(resp) = resp {
555 resps.push(resp)
556 }
557 }
558 assert_eq!(resps.len(), 2);
559 assert_eq!(resps[0].items[0].name, "item1");
560 assert_eq!(resps[0].items[1].name, "item2");
561 assert_eq!(resps[1].items[0].name, "item3");
562 }
563
564 #[cfg(feature = "unstable-stream")]
565 #[tokio::test]
566 async fn test_item_paginator_into_stream() {
567 let responses = vec![
568 TestResponse {
569 items: vec![
570 PageItem {
571 name: "item1".to_string(),
572 },
573 PageItem {
574 name: "item2".to_string(),
575 },
576 ],
577 next_page_token: "token1".to_string(),
578 },
579 TestResponse {
580 items: vec![PageItem {
581 name: "item3".to_string(),
582 }],
583 next_page_token: "".to_string(),
584 },
585 ];
586
587 let client = Client {
588 inner: Arc::new(InnerClient {
589 data: Arc::new(Mutex::new(responses)),
590 }),
591 };
592 let mut items = vec![];
593 let mut stream = client
594 .list_rpc_stream(TestRequest::default())
595 .items()
596 .into_stream();
597 while let Some(item) = stream.next().await {
598 if let Ok(item) = item {
599 items.push(item)
600 }
601 }
602 assert_eq!(items.len(), 3);
603 assert_eq!(items[0].name, "item1");
604 assert_eq!(items[1].name, "item2");
605 assert_eq!(items[2].name, "item3");
606 }
607}