diff --git a/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/LinkHeaderPaginationStrategy.kt b/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/LinkHeaderPaginationStrategy.kt index a470993..524f551 100644 --- a/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/LinkHeaderPaginationStrategy.kt +++ b/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/LinkHeaderPaginationStrategy.kt @@ -55,11 +55,10 @@ public class LinkHeaderPaginationStrategy ): Page { val items: List = itemsExtractor(response) // Some servers emit multiple Link headers (one per link-value) instead of a single - // comma-separated header. Join with ',' so a single parser handles both shapes. - val linkValues: List = response.headers.values(linkHeader) - val combined: String = - if (linkValues.isEmpty()) "" else linkValues.joinToString(separator = ",") - val nextUrlString: String? = if (combined.isEmpty()) null else extractNextUrl(combined) + // comma-separated header. Join with ',' so a single parser handles both shapes; an + // empty values list joins to "", and extractNextUrl("") already returns null. + val nextUrlString: String? = + extractNextUrl(response.headers.values(linkHeader).joinToString(separator = ",")) // Resolve the (possibly relative) next-link target against the originating page's // URL. A target that cannot be parsed into a valid URL ends the stream instead of // aborting the whole iteration with a MalformedURLException. diff --git a/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/Paginator.kt b/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/Paginator.kt index 387e0f4..7b7bab8 100644 --- a/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/Paginator.kt +++ b/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/Paginator.kt @@ -145,13 +145,10 @@ public class Paginator // Index into currentPage.items for the next item to emit. private var currentItemIndex: Int = 0 - // null = first iteration (use initialRequest); else the next-page request scheduled by - // the previous page's strategy.parse() call. - private var nextRequest: Request? = null - - // true once we have fetched at least one page. Used to distinguish "never fetched" from - // "exhausted after fetching" — both have currentPage == null right after iteration ends. - private var started: Boolean = false + // The next request to fetch: seeded with initialRequest, then replaced after each page + // with that page's nextPageRequest(), or null once a page reports no successor (which + // ends the stream on the following advance()). + private var nextRequest: Request? = initialRequest // true after iteration is definitively over; prevents further fetches. private var done: Boolean = false @@ -190,26 +187,11 @@ public class Paginator * [currentPage]; `false` if iteration is now done. */ private fun advance(): Boolean { - if (pagesFetched >= maxPages) { - // Safety cap reached: stop before fetching a page we would otherwise yield, - // even if the previous page still reports hasNext. - done = true - currentPage = null - return false - } - val request: Request? = - if (!started) { - initialRequest - } else { - // We have a current page that's exhausted — see if it can produce a next. - val finishedPage = currentPage - if (finishedPage == null || !finishedPage.hasNext) { - null - } else { - nextRequest ?: finishedPage.nextPageRequest() - } - } - if (request == null) { + val request = nextRequest + if (request == null || pagesFetched >= maxPages) { + // Either no next request is scheduled (the previous page had no successor) or + // the safety cap is reached — stop before fetching a page we would otherwise + // yield, even if the previous page still reports hasNext. done = true currentPage = null return false @@ -222,11 +204,10 @@ public class Paginator } finally { response.close() } - started = true currentPage = page currentItemIndex = 0 // Compute the next request now so we don't have to retain the (closed) response. - // If this page has no next, nextRequest stays null; advance() will treat that as done. + // If this page has no next, nextRequest goes null; the next advance() ends the stream. nextRequest = if (page.hasNext) page.nextPageRequest() else null return true } diff --git a/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/RequestRebuilder.kt b/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/RequestRebuilder.kt index d9cc884..50cee4a 100644 --- a/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/RequestRebuilder.kt +++ b/sdk-core/src/main/kotlin/org/dexpace/sdk/core/pagination/RequestRebuilder.kt @@ -114,11 +114,9 @@ internal object RequestRebuilder { if (existing.isEmpty()) return null for (segment in existing.split('&')) { if (segment.isEmpty()) continue - val eq = segment.indexOf('=') - val rawKey = if (eq >= 0) segment.substring(0, eq) else segment - if (decodeOrRaw(rawKey) == name) { - val rawValue = if (eq >= 0) segment.substring(eq + 1) else "" - return decodeOrRaw(rawValue) + val key = segment.substringBefore('=', segment) + if (decodeOrRaw(key) == name) { + return decodeOrRaw(segment.substringAfter('=', "")) } } return null