Skip to content

feat: add a single-pass cursor extractor for CursorPaginationStrategy#98

Merged
OmarAlJarrah merged 5 commits into
mainfrom
feat/single-read-pagination
Jun 16, 2026
Merged

feat: add a single-pass cursor extractor for CursorPaginationStrategy#98
OmarAlJarrah merged 5 commits into
mainfrom
feat/single-read-pagination

Conversation

@OmarAlJarrah

@OmarAlJarrah OmarAlJarrah commented Jun 16, 2026

Copy link
Copy Markdown
Member

Summary

CursorPaginationStrategy took two (Response) -> … extractor lambdas — one for the items, one for the next cursor. A response body is single-use, so reading items and cursor in two passes drained it twice; paginating a streamed body forced a per-response caching workaround.

This replaces that with a single-pass path:

  • CursorResult<T> — an immutable (items, nextCursor) holder.
  • the strategy now takes one (Response) -> CursorResult<T> extractor that reads and parses the response exactly once.
  • the old two-lambda constructor is removed outright (no deprecation) — the single-pass extractor is the only way to construct the strategy.

Token-style APIs (next_page_token, pageToken, …) are served by the same strategy constructed with the query-param name set (e.g. "page_token"), so the separate TokenPaginationStrategy is removed.

This is a breaking API change, which is acceptable at this alpha stage.

Tests

  • A single read per page is proven with a response whose source() throws on a second call, so a double-read is a hard failure rather than a silently-cached read.
  • Custom cursor query-param and URL-encoding of opaque (base64) cursors are exercised.

The sdk-core API snapshot is updated.

Closes #40

TokenPaginationStrategy was behaviorally identical to
CursorPaginationStrategy: same constructor shape, same parse() logic,
differing only in identifier naming and a default query-param value
("page_token" vs "cursor"). Since CursorPaginationStrategy already
exposes an overridable query-param name, token-style APIs
(next_page_token, pageToken, etc.) are fully served by constructing it
with the desired parameter name.

Remove the redundant type and migrate the two test cases that exercised
unique behavior (streamAll() equivalence and base64 value URL-encoding
through RequestRebuilder) onto CursorPaginationStrategy, preserving
coverage. Regenerate the sdk-core API snapshot for the net surface
reduction and update the docs that enumerated the shipped strategies.
CursorPaginationStrategy took two `(Response) -> …` lambdas — one for the
items, one for the next cursor — and called both per page. A response body is
single-use, so the second lambda either re-read an exhausted stream or had to
be propped up with a per-response cache to read the body only once. That made
the single-read constraint a hidden trap in a public API.

Add a CursorResult<T> holder (items + next cursor) and a constructor that takes
one `(Response) -> CursorResult<T>` extractor, so a caller parses the response
once and returns both pieces. The two-lambda constructor is kept for source and
binary compatibility but deprecated, with a ReplaceWith pointing at the
single-pass form; it now delegates to that path.

Tests cover a page walk over the single extractor, a body that throws on a
second read (proving exactly one read per page), and the retained two-lambda
constructor. The cursor suite no longer needs its IdentityHashMap caching
workaround and parses each page in one pass.
CursorResult was a public value type with hand-rolled equals/hashCode/
toString. Switch it to a data class to match the repo convention for
public value holders, which also gives componentN/copy; regenerate the
sdk-core API snapshot for the added members. Make the thread-safety KDoc
honest about the stored list reference instead of claiming an
immutability the constructor does not enforce.

Replace the public KDoc link to the internal RequestRebuilder with plain
prose so the published docs have no dangling reference, and note on the
deprecated two-lambda constructor that the suggested replacement still
double-drains unless the caller collapses the two reads into one pass.
Add a test that pins that double-drain failure mode through the
deprecated path.
@OmarAlJarrah OmarAlJarrah changed the base branch from refactor/remove-duplicate-token-pagination to main June 16, 2026 20:20
…nation

# Conflicts:
#	README.md
#	docs/architecture.md
#	sdk-core/src/test/kotlin/org/dexpace/sdk/core/pagination/CursorPaginationTest.kt
The single-pass (Response) -> CursorResult extractor is now the only way
to construct the strategy. The two-lambda form drained the single-use
response body twice per page and only existed for back-compat; drop it
outright rather than deprecate.
@OmarAlJarrah OmarAlJarrah merged commit 0f09ae9 into main Jun 16, 2026
1 check passed
@OmarAlJarrah OmarAlJarrah deleted the feat/single-read-pagination branch June 16, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Single-read pagination extractor (items + cursor in one pass)

1 participant