Skip to content

Commit

Permalink
feat: pagination (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
y9vad9 authored Jun 26, 2023
1 parent 7fa2875 commit 456baa7
Show file tree
Hide file tree
Showing 19 changed files with 329 additions and 90 deletions.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-rc-2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ import io.timemates.sdk.common.exceptions.PermissionDeniedException
import io.timemates.sdk.common.exceptions.UnauthorizedException
import io.timemates.sdk.common.exceptions.UnavailableException
import io.timemates.sdk.common.exceptions.UnsupportedException
import io.timemates.sdk.common.pagination.Page
import io.timemates.sdk.common.pagination.PageToken
import io.timemates.sdk.common.types.TimeMatesEntity
import io.timemates.sdk.common.types.TimeMatesRequest
import io.timemates.sdk.common.types.value.Count
import io.timemates.sdk.common.types.value.PageToken
import io.timemates.sdk.files.requests.GetFileBytesRequest
import io.timemates.sdk.files.requests.UploadFileRequest
import io.timemates.sdk.files.types.value.FileId
Expand Down Expand Up @@ -158,7 +159,7 @@ public class GrpcTimeMatesRequestsEngine(
.build(),
headers = authorizedMetadata(request.accessHash),
).let { response ->
GetAuthorizationSessionsRequest.Result(
Page(
response.authorizationsList.map(authMapper::grpcAuthorizationToSdkAuthorization),
nextPageToken = response.nextPageToken.takeIf { it.isNotEmpty() }
?.let(PageToken::createOrThrow),
Expand Down Expand Up @@ -246,7 +247,7 @@ public class GrpcTimeMatesRequestsEngine(
},
headers = authorizedMetadata(request.accessHash)
).let { response ->
GetUserTimersRequest.Result(
Page(
response.timersList.map { timersMapper.grpcTimerToSdkTimer(it) },
nextPageToken = response.nextPageToken.takeIf {
response.hasNextPageToken()
Expand All @@ -270,7 +271,7 @@ public class GrpcTimeMatesRequestsEngine(
},
headers = authorizedMetadata(request.accessHash)
).let { response ->
GetMembersRequest.Response(
Page(
response.usersList.map { usersMapper.grpcUserToSdkUser(it) },
nextPageToken = response.nextPageToken?.takeIf { it.isNotEmpty() }
?.let { PageToken.createOrThrow(it) }
Expand Down Expand Up @@ -299,8 +300,8 @@ public class GrpcTimeMatesRequestsEngine(
},
headers = authorizedMetadata(request.accessHash),
).let { response ->
GetInvitesRequest.Result(
invites = response.invitesList.map { timersMapper.grpcInviteToSdkInvite(it) },
Page(
results = response.invitesList.map { timersMapper.grpcInviteToSdkInvite(it) },
nextPageToken = response.nextPageToken.takeIf { it.isNotEmpty() }
?.let { PageToken.createOrThrow(it) }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import io.timemates.sdk.authorization.sessions.requests.TerminateCurrentAuthoriz
import io.timemates.sdk.authorization.sessions.types.Authorization
import io.timemates.sdk.authorization.sessions.types.value.AuthorizationId
import io.timemates.sdk.common.annotations.UnimplementedApi
import io.timemates.sdk.common.constructor.createOrThrow
import io.timemates.sdk.common.engine.TimeMatesRequestsEngine
import io.timemates.sdk.common.internal.flatMap
import io.timemates.sdk.common.pagination.Page
import io.timemates.sdk.common.pagination.PageToken
import io.timemates.sdk.common.pagination.PagesIterator
import io.timemates.sdk.common.pagination.PagesIteratorImpl
import io.timemates.sdk.common.providers.AccessHashProvider
import io.timemates.sdk.common.providers.getAsResult
import io.timemates.sdk.common.types.Empty
import io.timemates.sdk.common.types.value.PageToken
import io.timemates.sdk.common.types.value.Count
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

public class AuthorizedSessionsApi(
private val engine: TimeMatesRequestsEngine,
Expand All @@ -25,7 +32,7 @@ public class AuthorizedSessionsApi(
* @param pageToken The token representing the page to retrieve. Pass `null` to start from the beginning.
* @return The result of the API call, containing the list of authorizations and the next page token.
*/
public suspend fun getSessions(pageToken: PageToken?): Result<GetAuthorizationSessionsRequest.Result> {
public suspend fun getSessions(pageToken: PageToken?): Result<Page<Authorization>> {
return tokenProvider.getAsResult()
.flatMap { token -> engine.execute(GetAuthorizationSessionsRequest(pageToken, token)) }
}
Expand All @@ -48,38 +55,13 @@ public class AuthorizedSessionsApi(
}
}

/**
* Retrieves a flow of authorization sessions across multiple pages.
*
* @param pageToken The token representing the page to retrieve. Pass `null` to start from the beginning.
* @return A flow emitting lists of authorizations retrieved from each page.
*/
public fun AuthorizedSessionsApi.getSessionPages(
public fun AuthorizedSessionsApi.getSessionsPages(
pageToken: PageToken?,
maxRetryCount: Int,
): Flow<List<Authorization>> = flow {
var retryCount = 0
var currentToken = pageToken

while (true) {
val response = getSessions(currentToken)

if (response.isSuccess) {
val result = response.getOrThrow()
emit(result.authorizations)

if (currentToken == result.nextPageToken)
break

currentToken = result.nextPageToken
retryCount = 0
} else {
retryCount++
if (retryCount > maxRetryCount) {
break
}

delay(1000) // Add delay before retrying
}
}
}
maxRetries: Count = Count.createOrThrow(5),
initialDelayOnRetries: Duration = 1.seconds,
): PagesIterator<Authorization> = PagesIteratorImpl(
initialPageToken = pageToken,
delayOnServerErrors = initialDelayOnRetries,
maxRetries = maxRetries,
provider = { _, pageToken -> getSessions(pageToken) }
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ package io.timemates.sdk.authorization.sessions.requests

import io.timemates.sdk.authorization.sessions.types.Authorization
import io.timemates.sdk.authorization.types.value.AccessHash
import io.timemates.sdk.common.pagination.Page
import io.timemates.sdk.common.pagination.PageToken
import io.timemates.sdk.common.types.TimeMatesEntity
import io.timemates.sdk.common.types.TimeMatesRequest
import io.timemates.sdk.common.types.value.PageToken

public data class GetAuthorizationSessionsRequest(
val nextPageToken: PageToken?,
val accessHash: AccessHash,
) : TimeMatesRequest<GetAuthorizationSessionsRequest.Result>() {
public data class Result(
val authorizations: List<Authorization>,
val nextPageToken: PageToken?
) : TimeMatesEntity()
}
) : TimeMatesRequest<Page<Authorization>>()
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
package io.timemates.sdk.common.annotations

@RequiresOptIn(message = "Given Sdk API is experimental", level = RequiresOptIn.Level.WARNING)
public annotation class ExperimentalApi
/**
* Annotation used to indicate that a given SDK API is experimental.
*/
@RequiresOptIn(message = "Given SDK API is experimental", level = RequiresOptIn.Level.ERROR)
public annotation class ExperimentalApi(val status: ApiStatus)

public enum class ApiStatus {
PROTOTYPE,
IN_PROGRESS,
NEEDS_REVISION,
STABLE_CANDIDATE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package io.timemates.sdk.common.exceptions.handler

import io.timemates.sdk.common.annotations.ApiStatus
import io.timemates.sdk.common.annotations.ExperimentalApi
import io.timemates.sdk.common.annotations.InternalApi
import io.timemates.sdk.common.types.TimeMatesEntity
Expand All @@ -17,6 +18,7 @@ import io.timemates.sdk.common.exceptions.*
* @param E The type of the exception.
* @property result The original result of the operation.
*/
@ExperimentalApi(status = ApiStatus.NEEDS_REVISION)
@JvmInline
public value class SafeExceptionResult<T, E : Throwable> @InternalApi constructor(
@property:InternalApi public val result: Result<T>
Expand All @@ -31,7 +33,7 @@ public value class SafeExceptionResult<T, E : Throwable> @InternalApi constructo
* @receiver The original result of the operation.
* @return The [SafeExceptionResult] containing the original result.
*/
@ExperimentalApi
@ExperimentalApi(status = ApiStatus.NEEDS_REVISION)
public fun <T : TimeMatesEntity> Result<T>.safeResult(): SafeExceptionResult<T, UnauthorizedException> {
return SafeExceptionResult(this)
}
Expand Down Expand Up @@ -136,7 +138,7 @@ public fun <T> SafeExceptionResult<T, NotFoundException>.ignoreNotFound(): SafeE
* @param block The block of code to be executed if any exception is encountered.
* @return The [SafeExceptionResult] after executing the block.
*/
@ExperimentalApi
@ExperimentalApi(status = ApiStatus.NEEDS_REVISION)
public inline fun <T, E : Throwable> SafeExceptionResult<T, E>.whenOtherError(
block: (E) -> Unit
): SafeExceptionResult<T, Nothing> {
Expand All @@ -155,7 +157,7 @@ public inline fun <T, E : Throwable> SafeExceptionResult<T, E>.whenOtherError(
*
* But, if it's possible, it's better to use just [Result] API instead of [SafeExceptionResult].
*/
@ExperimentalApi
@ExperimentalApi(status = ApiStatus.NEEDS_REVISION)
public inline fun <T> SafeExceptionResult<T, *>.whenAnyError(
block: (Throwable) -> Unit
): SafeExceptionResult<T, Nothing> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.timemates.sdk.common.pagination

import io.timemates.sdk.common.types.TimeMatesEntity

public data class Page<T : TimeMatesEntity>(
val results: List<T>,
val nextPageToken: PageToken?,
) : TimeMatesEntity()
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package io.timemates.sdk.common.types.value
package io.timemates.sdk.common.pagination

import io.timemates.sdk.common.constructor.Factory

/**
* A value class representing a page token.
* A page token is used to propagate pagination in APIs. It is a string token that
* identifies a specific page of results, and is used to retrieve the next page
* of results.
* @param string The string representation of the page token.
*/
@JvmInline
public value class PageToken private constructor(public val string: String) {
public companion object : Factory<PageToken, String>() {
Expand Down
Loading

0 comments on commit 456baa7

Please sign in to comment.