Skip to content

Commit 752c05b

Browse files
authored
refactor: remove duplicate TokenPaginationStrategy in favor of CursorPaginationStrategy (#94)
PR: #94
1 parent 79ad548 commit 752c05b

7 files changed

Lines changed: 80 additions & 208 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ See [docs/pipelines.md](docs/pipelines.md) for the step-author walkthrough.
256256
| `http.auth` | `Credential` sealed hierarchy (`KeyCredential`, `NamedKeyCredential`, `BearerToken`), `BearerTokenProvider`, `AuthScheme`, `AuthMetadata`, RFC 7235 challenge parser, `BasicChallengeHandler`, `DigestChallengeHandler`, `CompositeChallengeHandler`. |
257257
| `http.sse` | `ServerSentEventReader` (WHATWG spec), `ServerSentEvent`, `ServerSentEventListener`, `BufferedSource.readServerSentEvents()`. |
258258
| `http.paging` | `PagedIterable<T>`, `PagedResponse<T>`, `PagingOptions` with `byPage()` and `stream()` accessors. |
259-
| `pagination` | `Paginator<T>` (with a `maxPages` safety cap) over cursor / page-number / token / link-header `PaginationStrategy` implementations, plus `Page<T>` / `SimplePage<T>`. |
259+
| `pagination` | `Paginator<T>` (with a `maxPages` safety cap) over cursor / page-number / link-header `PaginationStrategy` implementations, plus `Page<T>` / `SimplePage<T>`. |
260260
| `pipeline` | Recovery-aware primitives: `RequestPipeline`, `ResponsePipeline`, `ExecutionPipeline` over a sealed `ResponseOutcome`, with steps (`pipeline.step`, `pipeline.step.retry`) like `RetryStep`, `ResponseRecoveryStep`, `IdempotencyKeyStep`, `ClientIdentityStep`. |
261261
| `serde` | `Serde`, `Serializer`, `Deserializer` abstractions and `Tristate<T>` (absent / null / present). |
262262
| `io` | `Source`, `Sink`, `Buffer`, `BufferedSource`, `BufferedSink`, `IoProvider`, `Io`, `TeeSink`. |
@@ -266,6 +266,9 @@ See [docs/pipelines.md](docs/pipelines.md) for the step-author walkthrough.
266266
| `util` | `Clock`, `Uuids` (non-blocking v4), `DateTimeRfc1123`, `RetryUtils`, `ProxyOptions`, `Futures`. |
267267
| `generics` | `Builder<T>` — the generic builder interface every SDK builder implements. |
268268

269+
Token-style APIs (`next_page_token`, `pageToken`, …) are served by `CursorPaginationStrategy`:
270+
construct it with the desired query-param name, e.g. `CursorPaginationStrategy(items, extractor, "page_token")`.
271+
269272
## Building
270273

271274
```bash

docs/architecture.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,12 @@ Two complementary surfaces for walking multi-page responses.
350350
|-----------------------------------------------------------------|-----------------------------------------------------------------------|
351351
| `Paginator<T>` | Lazily iterates pages by re-issuing requests through an `HttpClient`; carries a `maxPages` safety cap |
352352
| `PaginationStrategy<T>` | Computes the next-page request (or stops) from the current page |
353-
| `CursorPaginationStrategy` / `PageNumberPaginationStrategy` / `TokenPaginationStrategy` / `LinkHeaderPaginationStrategy` | The four shipped strategies |
353+
| `CursorPaginationStrategy` / `PageNumberPaginationStrategy` / `LinkHeaderPaginationStrategy` | The shipped strategies |
354354
| `PagedIterable<T>` | First/next-page fetcher abstraction over `PagedResponse`, with its own `maxPages` cap |
355355

356+
Token-style APIs (`next_page_token`, `pageToken`, …) are handled by `CursorPaginationStrategy`
357+
constructed with the query-param name set (e.g. `"page_token"`), so no separate token strategy is needed.
358+
356359
### Serialization
357360

358361
**Package**: `org.dexpace.sdk.core.serde`

docs/implementation-plan.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,8 @@ defaults (per Square: `FAIL_ON_UNKNOWN_PROPERTIES=false`, `WRITE_DATES_AS_TIMEST
372372

373373
### WU-9: Pagination primitives
374374

375-
**Status: shipped.** `Page`, `Paginator`, `PaginationStrategy`, and the four strategies
376-
(`Cursor` / `PageNumber` / `LinkHeader` / `Token`) are in `sdk-core/.../pagination`, alongside
375+
**Status: shipped.** `Page`, `Paginator`, `PaginationStrategy`, and the three strategies
376+
(`Cursor` / `PageNumber` / `LinkHeader`) are in `sdk-core/.../pagination`, alongside
377377
helper types `SimplePage` and `RequestRebuilder`. `Paginator` gained a `maxPages` safety cap
378378
(default `Long.MAX_VALUE`) beyond the original sketch, to bound runaway iteration against servers
379379
that never advance their cursor.
@@ -390,7 +390,6 @@ link-header strategies without over-engineering. Sync first; async adapter follo
390390
- `CursorPaginationStrategy<T>(cursorPath, itemsPath, parser)` — read `next_cursor` from body
391391
- `PageNumberPaginationStrategy<T>(pageParam, itemsPath, parser)` — increment page number
392392
- `LinkHeaderPaginationStrategy<T>(itemsPath, parser)` — RFC 5988 `Link: <url>; rel="next"`
393-
- `TokenPaginationStrategy<T>(tokenPath, tokenParam, itemsPath, parser)` — token in body, sent as query param
394393
- `sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/PaginatorTests.kt` (test) — table-driven tests against MockWebServer fixtures.
395394

396395
**Acceptance criteria:**

sdk-core/api/sdk-core.api

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,13 +1958,6 @@ public final class org/dexpace/sdk/core/pagination/Paginator {
19581958
public final fun streamAll ()Ljava/util/stream/Stream;
19591959
}
19601960

1961-
public final class org/dexpace/sdk/core/pagination/TokenPaginationStrategy : org/dexpace/sdk/core/pagination/PaginationStrategy {
1962-
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
1963-
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;)V
1964-
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
1965-
public fun parse (Lorg/dexpace/sdk/core/http/response/Response;Lorg/dexpace/sdk/core/http/request/Request;)Lorg/dexpace/sdk/core/pagination/Page;
1966-
}
1967-
19681961
public final class org/dexpace/sdk/core/pipeline/ExecutionPipeline {
19691962
public fun <init> (Lorg/dexpace/sdk/core/client/HttpClient;)V
19701963
public fun <init> (Lorg/dexpace/sdk/core/client/HttpClient;Lorg/dexpace/sdk/core/pipeline/RequestPipeline;)V

sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/TokenPaginationStrategy.kt

Lines changed: 0 additions & 58 deletions
This file was deleted.

sdk-core/src/test/kotlin/org/dexpace/sdk/core/pagination/CursorPaginationTest.kt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.dexpace.sdk.core.http.request.Method
1111
import org.dexpace.sdk.core.http.request.Request
1212
import org.dexpace.sdk.core.http.response.Response
1313
import java.util.IdentityHashMap
14+
import java.util.stream.Collectors
1415
import kotlin.test.BeforeTest
1516
import kotlin.test.Test
1617
import kotlin.test.assertEquals
@@ -127,4 +128,73 @@ class CursorPaginationTest {
127128
val paginator = Paginator(client, authRequest, strategy)
128129
assertEquals(listOf("a", "b"), paginator.iterateAll().toList())
129130
}
131+
132+
@Test
133+
fun `streamAll yields the same items as iterateAll`() {
134+
val client = StubHttpClient()
135+
client.on("https://api.example.com/items") { req ->
136+
textResponse(req, "items=1,2\ncursor=c1")
137+
}
138+
client.on("https://api.example.com/items?cursor=c1") { req ->
139+
textResponse(req, "items=3,4\ncursor=")
140+
}
141+
142+
val (items, cursor) = buildCachedExtractors()
143+
val strategy = CursorPaginationStrategy(items, cursor)
144+
val paginator = Paginator(client, initialRequest(), strategy)
145+
val streamed: List<String> = paginator.streamAll().collect(Collectors.toList())
146+
assertEquals(listOf("1", "2", "3", "4"), streamed)
147+
}
148+
149+
@Test
150+
fun `cursor with special characters is URL encoded in next request`() {
151+
// Opaque cursors may contain `=` `+` `/` characters (base64) — the rebuilder must
152+
// URL-encode them so the server sees the original value unmangled.
153+
val rawCursor = "a+b/c="
154+
val encoded = "a%2Bb%2Fc%3D"
155+
val client = StubHttpClient()
156+
client.on("https://api.example.com/items") { req ->
157+
textResponse(req, "items=one\ncursor=$rawCursor")
158+
}
159+
client.on("https://api.example.com/items?cursor=$encoded") { req ->
160+
textResponse(req, "items=two\ncursor=")
161+
}
162+
163+
val (items, cursor) = buildCachedExtractors()
164+
val strategy = CursorPaginationStrategy(items, cursor)
165+
val paginator = Paginator(client, initialRequest(), strategy)
166+
assertEquals(listOf("one", "two"), paginator.iterateAll().toList())
167+
assertEquals(
168+
listOf(
169+
"https://api.example.com/items",
170+
"https://api.example.com/items?cursor=$encoded",
171+
),
172+
client.receivedUrls,
173+
)
174+
}
175+
176+
@Test
177+
fun `custom query-param name is used for the next-page cursor`() {
178+
// Token-style APIs (next_page_token, pageToken, …) are served by setting
179+
// cursorQueryParam; the next request must carry the cursor under that name.
180+
val client = StubHttpClient()
181+
client.on("https://api.example.com/items") { req ->
182+
textResponse(req, "items=one\ncursor=tok1")
183+
}
184+
client.on("https://api.example.com/items?page_token=tok1") { req ->
185+
textResponse(req, "items=two\ncursor=")
186+
}
187+
188+
val (items, cursor) = buildCachedExtractors()
189+
val strategy = CursorPaginationStrategy(items, cursor, cursorQueryParam = "page_token")
190+
val paginator = Paginator(client, initialRequest(), strategy)
191+
assertEquals(listOf("one", "two"), paginator.iterateAll().toList())
192+
assertEquals(
193+
listOf(
194+
"https://api.example.com/items",
195+
"https://api.example.com/items?page_token=tok1",
196+
),
197+
client.receivedUrls,
198+
)
199+
}
130200
}

sdk-core/src/test/kotlin/org/dexpace/sdk/core/pagination/TokenPaginationTest.kt

Lines changed: 0 additions & 138 deletions
This file was deleted.

0 commit comments

Comments
 (0)