Skip to main content

babelforce_manager_sdk/
http.rs

1//! Pagination helper for v2 list endpoints, which return
2//! `{ items: Vec<T>, pagination: Pagination { pages, current, … } }`.
3use crate::error::ManagerError;
4use crate::retry::{with_retry, RetryPolicy, Transient};
5
6/// One fetched page of a v2 list endpoint.
7#[derive(Debug, Clone)]
8pub struct Page<T> {
9    /// The items on this page.
10    pub items: Vec<T>,
11    /// The 1-based page this response is (the API's `pagination.current`).
12    pub page: i32,
13    /// Total number of pages.
14    pub pages: i32,
15    /// Total items across all pages, when the endpoint reports it.
16    pub total: Option<i32>,
17}
18
19/// Fetch exactly one page. `fetch()` performs the single page request and returns
20/// `(items, total_pages, current_page, total_items)`; retried like any idempotent read.
21/// The single-page sibling of [`collect_all`] — for callers that surface pagination
22/// instead of flattening it.
23pub(crate) async fn fetch_page<T, E, F, Fut, M>(
24    retry: &RetryPolicy,
25    map_err: M,
26    fetch: F,
27) -> Result<Page<T>, ManagerError>
28where
29    E: Transient,
30    M: Fn(E) -> ManagerError,
31    F: Fn() -> Fut,
32    Fut: std::future::Future<Output = Result<(Vec<T>, i32, i32, Option<i32>), E>>,
33{
34    let (items, pages, current, total) = with_retry(retry, true, fetch).await.map_err(map_err)?;
35    Ok(Page {
36        items,
37        page: current,
38        pages,
39        total,
40    })
41}
42
43pub(crate) async fn collect_all<T, E, F, Fut, M>(
44    retry: &RetryPolicy,
45    map_err: M,
46    mut fetch: F,
47) -> Result<Vec<T>, ManagerError>
48where
49    E: Transient,
50    M: Fn(E) -> ManagerError,
51    F: FnMut(i32) -> Fut,
52    Fut: std::future::Future<Output = Result<(Vec<T>, i32, i32), E>>,
53{
54    let mut out = Vec::new();
55    let mut page = 1;
56    loop {
57        let (items, pages, _current) = with_retry(retry, true, || fetch(page))
58            .await
59            .map_err(&map_err)?;
60        let empty = items.is_empty();
61        out.extend(items);
62        // Advance by a local monotonic counter rather than the server-echoed `current`: a server
63        // that misreports its cursor (e.g. always returns current=1) can't then spin us in an
64        // infinite, memory-exhausting loop. Stop on an empty page or once we reach the page count.
65        if empty || page >= pages {
66            return Ok(out);
67        }
68        page += 1;
69    }
70}