cloud-sdk 0.44.0

no_std-first provider-neutral cloud SDK foundations.
Documentation
use super::super::{
    NumberedPageMetadata, NumberedPagination, PageNumber, PaginationBudget, PaginationError,
    PaginationLimits, SnapshotId, SnapshotPolicy,
};
use crate::rate_limit::RateLimit;

fn page(value: u64) -> PageNumber {
    PageNumber::new(value).unwrap_or_else(|_| unreachable!())
}

fn limits(requests: u32, items: u64) -> PaginationLimits {
    PaginationLimits::new(requests, items, 128).unwrap_or_else(|_| unreachable!())
}

fn traversal(requests: u32, items: u64) -> NumberedPagination {
    NumberedPagination::new(
        page(1),
        25,
        PaginationBudget::new(limits(requests, items), SnapshotPolicy::Forbidden),
    )
    .unwrap_or_else(|_| unreachable!())
}

fn metadata(current: u64, next: Option<u64>) -> NumberedPageMetadata {
    NumberedPageMetadata::new(
        page(current),
        25,
        current.checked_sub(1).filter(|value| *value != 0).map(page),
        next.map(page),
        Some(page(next.unwrap_or(current))),
        None,
    )
    .unwrap_or_else(|_| unreachable!())
}

#[test]
fn walks_numbered_pages_with_hard_request_item_and_rate_limit_state() {
    let mut traversal = traversal(3, 30);
    let rate_limit = RateLimit::new(3600, 3599, 42).ok();
    let first = traversal.observe(metadata(1, Some(2)), 25, rate_limit, None);
    assert_eq!(first.map(|value| value.rate_limit()), Ok(rate_limit));
    assert_eq!(traversal.next_page(), Ok(page(2)));

    let last = traversal.observe(metadata(2, None), 3, None, None);
    assert!(last.is_ok_and(|value| {
        value.is_terminal() && value.progress().requests() == 2 && value.progress().items() == 28
    }));
    assert_eq!(traversal.next_page(), Err(PaginationError::Complete));
}

#[test]
fn request_and_item_budgets_fail_before_state_advances() {
    let mut request_limited = traversal(1, 100);
    assert_eq!(
        request_limited.observe(metadata(1, Some(2)), 25, None, None),
        Err(PaginationError::RequestBudgetExceeded)
    );
    assert_eq!(request_limited.next_page(), Ok(page(1)));
    assert_eq!(request_limited.progress().requests(), 0);

    let mut item_limited = traversal(2, 24);
    assert_eq!(
        item_limited.observe(metadata(1, Some(2)), 25, None, None),
        Err(PaginationError::ItemBudgetExceeded)
    );
    assert_eq!(item_limited.next_page(), Ok(page(1)));
    assert_eq!(item_limited.progress().items(), 0);
}

#[test]
fn snapshot_omission_presence_and_drift_are_explicit_and_transactional() {
    let snapshot = SnapshotId::new(b"snapshot-7").ok();
    let mut required = PaginationBudget::new(limits(3, 100), SnapshotPolicy::Required);
    assert_eq!(
        required.admit(1, true, None),
        Err(PaginationError::SnapshotRequired)
    );
    assert_eq!(required.progress().requests(), 0);
    assert!(required.admit(1, true, snapshot).is_ok());
    assert_eq!(
        required.admit(1, false, SnapshotId::new(b"snapshot-8").ok()),
        Err(PaginationError::SnapshotChanged)
    );
    assert_eq!(required.progress().requests(), 1);

    let mut forbidden = PaginationBudget::new(limits(2, 10), SnapshotPolicy::Forbidden);
    assert_eq!(
        forbidden.admit(1, false, snapshot),
        Err(PaginationError::SnapshotForbidden)
    );

    let mut optional = PaginationBudget::new(limits(3, 10), SnapshotPolicy::Optional);
    assert!(optional.admit(1, true, None).is_ok());
    assert_eq!(
        optional.admit(1, false, snapshot),
        Err(PaginationError::SnapshotChanged)
    );
}

#[test]
fn snapshot_identity_uses_exact_bounded_bytes_without_hash_collisions() {
    use super::super::MAX_SNAPSHOT_ID_BYTES;

    assert_eq!(SnapshotId::new(b""), Err(PaginationError::SnapshotIdEmpty));
    let exact = [b'x'; MAX_SNAPSHOT_ID_BYTES];
    assert!(SnapshotId::new(&exact).is_ok());
    let oversized = [b'x'; MAX_SNAPSHOT_ID_BYTES + 1];
    assert_eq!(
        SnapshotId::new(&oversized),
        Err(PaginationError::SnapshotIdTooLong)
    );

    let first = b"same-prefix-snapshot-a";
    let changed = b"same-prefix-snapshot-b";
    let mut budget = PaginationBudget::new(limits(3, 10), SnapshotPolicy::Required);
    assert!(budget.admit(1, true, SnapshotId::new(first).ok()).is_ok());
    assert_eq!(
        budget.admit(1, false, SnapshotId::new(changed).ok()),
        Err(PaginationError::SnapshotChanged)
    );
    assert_eq!(budget.progress().requests(), 1);

    let mut transactional = PaginationBudget::new(limits(1, 10), SnapshotPolicy::Required);
    assert_eq!(
        transactional.admit(1, true, SnapshotId::new(first).ok()),
        Err(PaginationError::RequestBudgetExceeded)
    );
    assert!(
        transactional
            .admit(1, false, SnapshotId::new(changed).ok())
            .is_ok()
    );
}

#[test]
fn validates_navigation_counts_and_locked_metadata() {
    assert_eq!(PageNumber::new(0), Err(PaginationError::PageZero));
    assert_eq!(
        NumberedPageMetadata::new(page(2), 25, None, Some(page(2)), None, None),
        Err(PaginationError::InvalidNextPage)
    );
    assert_eq!(
        NumberedPageMetadata::new(page(2), 25, Some(page(2)), None, None, None),
        Err(PaginationError::InvalidPreviousPage)
    );
    assert_eq!(
        NumberedPageMetadata::new(page(1), 25, None, None, Some(page(4)), Some(100)),
        Err(PaginationError::InvalidLastPage)
    );

    let mut traversal = traversal(4, 200);
    let first =
        NumberedPageMetadata::new(page(1), 25, None, Some(page(2)), Some(page(4)), Some(100))
            .unwrap_or_else(|_| unreachable!());
    assert!(traversal.observe(first, 25, None, None).is_ok());

    let changed = NumberedPageMetadata::new(
        page(2),
        25,
        Some(page(1)),
        Some(page(3)),
        Some(page(4)),
        Some(101),
    )
    .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        traversal.observe(changed, 25, None, None),
        Err(PaginationError::TraversalChanged)
    );
    assert_eq!(traversal.next_page(), Ok(page(2)));
    assert_eq!(traversal.progress().requests(), 1);
}

#[test]
fn accepts_empty_terminal_and_rejects_empty_continuation() {
    let mut terminal = traversal(1, 1);
    assert!(
        terminal
            .observe(metadata(1, None), 0, None, None)
            .is_ok_and(|value| value.is_terminal())
    );

    let mut nonterminal = traversal(2, 10);
    assert_eq!(
        nonterminal.observe(metadata(1, Some(2)), 0, None, None),
        Err(PaginationError::EmptyPageWithContinuation)
    );
}