cloud-sdk 0.21.0

no_std-first multi-provider cloud SDK foundations.
Documentation
use super::{PageLimit, PageMetadata, PageNumber, PaginationCursor, PaginationError};
use crate::rate_limit::RateLimit;

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

fn metadata(current: u64, next: Option<u64>) -> PageMetadata {
    PageMetadata::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_explicit_pages_and_propagates_rate_limits() {
    let mut cursor = PaginationCursor::new(
        page(1),
        25,
        PageLimit::new(3).unwrap_or_else(|_| unreachable!()),
    )
    .unwrap_or_else(|_| unreachable!());
    let rate_limit = RateLimit::new(3600, 3599, 42).ok();
    let first = cursor.observe(metadata(1, Some(2)), 25, rate_limit);
    assert!(first.is_ok());
    assert_eq!(first.map(|value| value.rate_limit()), Ok(rate_limit));
    assert_eq!(cursor.next_page(), Ok(page(2)));

    let last = cursor.observe(metadata(2, None), 3, None);
    assert!(last.is_ok());
    assert!(last.is_ok_and(|value| value.is_terminal()));
    assert_eq!(cursor.pages_seen(), 2);
    assert_eq!(cursor.next_page(), Err(PaginationError::Complete));
}

#[test]
fn accepts_an_empty_terminal_page() {
    let mut cursor = PaginationCursor::new(
        page(1),
        25,
        PageLimit::new(1).unwrap_or_else(|_| unreachable!()),
    )
    .unwrap_or_else(|_| unreachable!());
    let boundary = cursor.observe(metadata(1, None), 0, None);
    assert!(boundary.is_ok_and(|value| value.is_terminal() && value.entries() == 0));
}

#[test]
fn rejects_empty_and_repeated_nonterminal_pages() {
    let mut cursor = PaginationCursor::new(
        page(1),
        25,
        PageLimit::new(3).unwrap_or_else(|_| unreachable!()),
    )
    .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(metadata(1, Some(2)), 0, None),
        Err(PaginationError::EmptyPageWithNextPage)
    );
    assert_eq!(cursor.pages_seen(), 0);

    assert!(cursor.observe(metadata(1, Some(2)), 1, None).is_ok());
    assert_eq!(
        cursor.observe(metadata(1, Some(2)), 1, None),
        Err(PaginationError::UnexpectedPage)
    );
}

#[test]
fn page_limit_fails_before_advancing_and_does_not_mutate() {
    let mut cursor = PaginationCursor::new(
        page(1),
        25,
        PageLimit::new(1).unwrap_or_else(|_| unreachable!()),
    )
    .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(metadata(1, Some(2)), 25, None),
        Err(PaginationError::PageLimitExceeded)
    );
    assert_eq!(cursor.pages_seen(), 0);
    assert_eq!(cursor.next_page(), Ok(page(1)));
}

#[test]
fn validates_provider_navigation_metadata() {
    assert_eq!(PageNumber::new(0), Err(PaginationError::PageZero));
    assert_eq!(PageLimit::new(0), Err(PaginationError::PageLimitZero));
    assert_eq!(
        PaginationCursor::new(
            page(1),
            0,
            PageLimit::new(1).unwrap_or_else(|_| unreachable!())
        ),
        Err(PaginationError::PerPageZero)
    );
    assert_eq!(
        PageMetadata::new(page(2), 25, None, Some(page(2)), None, None),
        Err(PaginationError::InvalidNextPage)
    );
    assert_eq!(
        PageMetadata::new(page(2), 25, None, Some(page(4)), Some(page(4)), None),
        Err(PaginationError::InvalidNextPage)
    );
    assert_eq!(
        PageMetadata::new(page(2), 25, Some(page(2)), None, None, None),
        Err(PaginationError::InvalidPreviousPage)
    );
    assert_eq!(
        PageMetadata::new(page(3), 25, Some(page(1)), None, None, None),
        Err(PaginationError::InvalidPreviousPage)
    );
    assert_eq!(
        PageMetadata::new(page(u64::MAX), 25, None, Some(page(1)), None, None),
        Err(PaginationError::InvalidNextPage)
    );
    assert_eq!(
        PageMetadata::new(page(2), 25, None, Some(page(3)), Some(page(2)), None),
        Err(PaginationError::InvalidLastPage)
    );
    assert_eq!(
        PageMetadata::new(page(1), 25, None, None, Some(page(4)), Some(100)),
        Err(PaginationError::InvalidLastPage)
    );
}

#[test]
fn rejects_entry_counts_and_totals_without_mutating_the_cursor() {
    let limit = PageLimit::new(4).unwrap_or_else(|_| unreachable!());
    let mut cursor = PaginationCursor::new(page(1), 25, limit).unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(metadata(1, None), 26, None),
        Err(PaginationError::InvalidEntryCount)
    );

    let short = PageMetadata::new(page(1), 25, None, Some(page(2)), Some(page(4)), Some(100))
        .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(short, 24, None),
        Err(PaginationError::InvalidEntryCount)
    );

    let premature = PageMetadata::new(page(1), 25, None, None, None, Some(100))
        .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(premature, 25, None),
        Err(PaginationError::InvalidEntryCount)
    );
    assert_eq!(cursor.pages_seen(), 0);
    assert_eq!(cursor.next_page(), Ok(page(1)));
}

#[test]
fn binds_page_size_total_and_last_page_across_the_traversal() {
    let limit = PageLimit::new(4).unwrap_or_else(|_| unreachable!());
    let mut cursor = PaginationCursor::new(page(1), 25, limit).unwrap_or_else(|_| unreachable!());
    let wrong_first = PageMetadata::new(page(1), 50, None, None, Some(page(1)), Some(0))
        .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(wrong_first, 0, None),
        Err(PaginationError::PageSizeChanged)
    );
    assert_eq!(cursor.pages_seen(), 0);

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

    let changed_size =
        PageMetadata::new(page(2), 50, Some(page(1)), None, Some(page(2)), Some(100))
            .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(changed_size, 50, None),
        Err(PaginationError::PageSizeChanged)
    );

    let changed_total = PageMetadata::new(
        page(2),
        25,
        Some(page(1)),
        Some(page(3)),
        Some(page(4)),
        Some(101),
    )
    .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(changed_total, 25, None),
        Err(PaginationError::TraversalChanged)
    );

    let changed_last = PageMetadata::new(
        page(2),
        25,
        Some(page(1)),
        Some(page(3)),
        Some(page(5)),
        Some(100),
    )
    .unwrap_or_else(|_| unreachable!());
    assert_eq!(
        cursor.observe(changed_last, 25, None),
        Err(PaginationError::TraversalChanged)
    );
    assert_eq!(cursor.pages_seen(), 1);
    assert_eq!(cursor.next_page(), Ok(page(2)));

    let second = PageMetadata::new(
        page(2),
        25,
        Some(page(1)),
        Some(page(3)),
        Some(page(4)),
        Some(100),
    )
    .unwrap_or_else(|_| unreachable!());
    assert!(cursor.observe(second, 25, None).is_ok());
    assert_eq!(cursor.pages_seen(), 2);
    assert_eq!(cursor.next_page(), Ok(page(3)));
}