diff --git a/ktor-client/ktor-client-core/api/ktor-client-core.api b/ktor-client/ktor-client-core/api/ktor-client-core.api index 39297bd98a..002ef34f02 100644 --- a/ktor-client/ktor-client-core/api/ktor-client-core.api +++ b/ktor-client/ktor-client-core/api/ktor-client-core.api @@ -363,6 +363,9 @@ public final class io/ktor/client/plugins/HttpRequestRetryConfig { public final fun exponentialDelay (DJJJZ)V public static synthetic fun exponentialDelay$default (Lio/ktor/client/plugins/HttpRequestRetryConfig;DJJJZILjava/lang/Object;)V public final fun getMaxRetries ()I + public final fun getModifyRequest ()Lkotlin/jvm/functions/Function2; + public final fun getRetryIf ()Lkotlin/jvm/functions/Function3; + public final fun getRetryOnExceptionIf ()Lkotlin/jvm/functions/Function3; public final fun modifyRequest (Lkotlin/jvm/functions/Function2;)V public final fun noRetry ()V public final fun retryIf (ILkotlin/jvm/functions/Function3;)V diff --git a/ktor-client/ktor-client-core/api/ktor-client-core.klib.api b/ktor-client/ktor-client-core/api/ktor-client-core.klib.api index 004a3baaa8..f859311741 100644 --- a/ktor-client/ktor-client-core/api/ktor-client-core.klib.api +++ b/ktor-client/ktor-client-core/api/ktor-client-core.klib.api @@ -653,9 +653,16 @@ final class io.ktor.client.plugins/HttpRedirectConfig { // io.ktor.client.plugin final class io.ktor.client.plugins/HttpRequestRetryConfig { // io.ktor.client.plugins/HttpRequestRetryConfig|null[0] constructor () // io.ktor.client.plugins/HttpRequestRetryConfig.|(){}[0] + final val retryIf // io.ktor.client.plugins/HttpRequestRetryConfig.retryIf|{}retryIf[0] + final fun (): kotlin/Function3? // io.ktor.client.plugins/HttpRequestRetryConfig.retryIf.|(){}[0] + final val retryOnExceptionIf // io.ktor.client.plugins/HttpRequestRetryConfig.retryOnExceptionIf|{}retryOnExceptionIf[0] + final fun (): kotlin/Function3? // io.ktor.client.plugins/HttpRequestRetryConfig.retryOnExceptionIf.|(){}[0] + final var maxRetries // io.ktor.client.plugins/HttpRequestRetryConfig.maxRetries|{}maxRetries[0] final fun (): kotlin/Int // io.ktor.client.plugins/HttpRequestRetryConfig.maxRetries.|(){}[0] final fun (kotlin/Int) // io.ktor.client.plugins/HttpRequestRetryConfig.maxRetries.|(kotlin.Int){}[0] + final var modifyRequest // io.ktor.client.plugins/HttpRequestRetryConfig.modifyRequest|{}modifyRequest[0] + final fun (): kotlin/Function2 // io.ktor.client.plugins/HttpRequestRetryConfig.modifyRequest.|(){}[0] final fun constantDelay(kotlin/Long = ..., kotlin/Long = ..., kotlin/Boolean = ...) // io.ktor.client.plugins/HttpRequestRetryConfig.constantDelay|constantDelay(kotlin.Long;kotlin.Long;kotlin.Boolean){}[0] final fun delay(kotlin.coroutines/SuspendFunction1) // io.ktor.client.plugins/HttpRequestRetryConfig.delay|delay(kotlin.coroutines.SuspendFunction1){}[0] diff --git a/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt b/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt index 72a7123deb..fc75f11640 100644 --- a/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt +++ b/ktor-client/ktor-client-core/common/src/io/ktor/client/plugins/HttpRequestRetry.kt @@ -35,9 +35,26 @@ public class HttpRequestRetryConfig { internal lateinit var shouldRetry: HttpRetryShouldRetryContext.(HttpRequest, HttpResponse) -> Boolean internal lateinit var shouldRetryOnException: HttpRetryShouldRetryContext.(HttpRequestBuilder, Throwable) -> Boolean internal lateinit var delayMillis: HttpRetryDelayContext.(Int) -> Long - internal var modifyRequest: HttpRetryModifyRequestContext.(HttpRequestBuilder) -> Unit = {} internal var delay: suspend (Long) -> Unit = { kotlinx.coroutines.delay(it) } + /** + * Function that determines whether a request should be retried based on the response. + */ + public val retryIf: (HttpRetryShouldRetryContext.(HttpRequest, HttpResponse) -> Boolean)? + get() = if (::shouldRetry.isInitialized) shouldRetry else null + + /** + * Function that determines whether a request should be retried based on the exception. + */ + public val retryOnExceptionIf: (HttpRetryShouldRetryContext.(HttpRequestBuilder, Throwable) -> Boolean)? + get() = if (::shouldRetryOnException.isInitialized) shouldRetryOnException else null + + /** + * Indicated how the request should be modified before retrying. + */ + public var modifyRequest: HttpRetryModifyRequestContext.(HttpRequestBuilder) -> Unit = {} + private set + /** * The maximum amount of retries to perform for a request. */ diff --git a/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTokenHolderTest.kt b/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTokenHolderTest.kt index aec5041ad0..ee58852ca3 100644 --- a/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTokenHolderTest.kt +++ b/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/AuthTokenHolderTest.kt @@ -5,15 +5,15 @@ package io.ktor.client.plugins.auth import io.ktor.client.plugins.auth.providers.* -import io.ktor.test.dispatcher.* import kotlinx.coroutines.* +import kotlinx.coroutines.test.runTest import kotlin.test.* class AuthTokenHolderTest { @Test @OptIn(DelicateCoroutinesApi::class) - fun testSetTokenCalledOnce() = testSuspend { + fun testSetTokenCalledOnce() = runTest { val holder = AuthTokenHolder { TODO() } val monitor = Job() @@ -44,7 +44,7 @@ class AuthTokenHolderTest { @Test @OptIn(DelicateCoroutinesApi::class) - fun testLoadTokenWaitsUntilTokenIsLoaded() = testSuspend { + fun testLoadTokenWaitsUntilTokenIsLoaded() = runTest { val monitor = Job() val holder = AuthTokenHolder { monitor.join() @@ -66,7 +66,7 @@ class AuthTokenHolderTest { @Test @OptIn(DelicateCoroutinesApi::class) - fun testClearCalledWhileLoadingTokens() = testSuspend { + fun testClearCalledWhileLoadingTokens() = runTest { val monitor = Job() var clearTokenCalled = false @@ -97,7 +97,7 @@ class AuthTokenHolderTest { @Test @OptIn(DelicateCoroutinesApi::class) - fun testClearCalledWhileSettingTokens() = testSuspend { + fun testClearCalledWhileSettingTokens() = runTest { val monitor = Job() var clearTokenCalled = false @@ -128,7 +128,7 @@ class AuthTokenHolderTest { } @Test - fun testExceptionInLoadTokens() = testSuspend { + fun testExceptionInLoadTokens() = runTest { var firstCall = true val holder = AuthTokenHolder { if (firstCall) { @@ -142,7 +142,7 @@ class AuthTokenHolderTest { } @Test - fun testExceptionInSetTokens() = testSuspend { + fun testExceptionInSetTokens() = runTest { val holder = AuthTokenHolder { fail("loadTokens argument function shouldn't be invoked") } diff --git a/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/DigestProviderTest.kt b/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/DigestProviderTest.kt index a6195b54ba..aeb8ab93bf 100644 --- a/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/DigestProviderTest.kt +++ b/ktor-client/ktor-client-plugins/ktor-client-auth/common/test/io/ktor/client/plugins/auth/DigestProviderTest.kt @@ -1,17 +1,14 @@ /* * Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ - -@file:Suppress("DEPRECATION") - package io.ktor.client.plugins.auth import io.ktor.client.plugins.auth.providers.* import io.ktor.client.request.* import io.ktor.http.* import io.ktor.http.auth.* -import io.ktor.test.dispatcher.* import io.ktor.util.* +import kotlinx.coroutines.test.runTest import kotlin.test.* class DigestProviderTest { @@ -66,8 +63,8 @@ class DigestProviderTest { } @Test - fun addRequestHeadersSetsExpectedAuthHeaderFields() = testSuspend { - if (!PlatformUtils.IS_JVM) return@testSuspend + fun addRequestHeadersSetsExpectedAuthHeaderFields() = runTest { + if (!PlatformUtils.IS_JVM) return@runTest runIsApplicable(authAllFields) val authHeader = addRequestHeaders(authAllFields) @@ -78,8 +75,8 @@ class DigestProviderTest { } @Test - fun addRequestHeadersMissingRealm() = testSuspend { - if (!PlatformUtils.IS_JVM) return@testSuspend + fun addRequestHeadersMissingRealm() = runTest { + if (!PlatformUtils.IS_JVM) return@runTest @Suppress("DEPRECATION_ERROR") val providerWithoutRealm = DigestAuthProvider("username", "pass", null) @@ -93,8 +90,8 @@ class DigestProviderTest { } @Test - fun addRequestHeadersChangedRealm() = testSuspend { - if (!PlatformUtils.IS_JVM) return@testSuspend + fun addRequestHeadersChangedRealm() = runTest { + if (!PlatformUtils.IS_JVM) return@runTest @Suppress("DEPRECATION_ERROR") val providerWithoutRealm = DigestAuthProvider("username", "pass", "wrong!") @@ -104,8 +101,8 @@ class DigestProviderTest { } @Test - fun addRequestHeadersOmitsQopAndOpaqueWhenMissing() = testSuspend { - if (!PlatformUtils.IS_JVM) return@testSuspend + fun addRequestHeadersOmitsQopAndOpaqueWhenMissing() = runTest { + if (!PlatformUtils.IS_JVM) return@runTest runIsApplicable(authMissingQopAndOpaque) val authHeader = addRequestHeaders(authMissingQopAndOpaque) @@ -116,8 +113,8 @@ class DigestProviderTest { } @Test - fun testTokenWhenMissingRealmAndQop() = testSuspend { - if (!PlatformUtils.IS_JVM) return@testSuspend + fun testTokenWhenMissingRealmAndQop() = runTest { + if (!PlatformUtils.IS_JVM) return@runTest @Suppress("DEPRECATION_ERROR") val providerWithoutRealm = DigestAuthProvider("username", "pass", null)