Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[runtime] Add failFastIfOffline #5725

Merged
merged 1 commit into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libraries/apollo-api/api/apollo-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public final class com/apollographql/apollo3/api/ApolloOptionalAdapter : com/apo
}

public final class com/apollographql/apollo3/api/ApolloRequest : com/apollographql/apollo3/api/ExecutionOptions {
public synthetic fun <init> (Lcom/apollographql/apollo3/api/Operation;Ljava/util/UUID;Lcom/apollographql/apollo3/api/ExecutionContext;Lcom/apollographql/apollo3/api/http/HttpMethod;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lcom/apollographql/apollo3/api/Operation;Ljava/util/UUID;Lcom/apollographql/apollo3/api/ExecutionContext;Lcom/apollographql/apollo3/api/http/HttpMethod;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getCanBeBatched ()Ljava/lang/Boolean;
public fun getEnableAutoPersistedQueries ()Ljava/lang/Boolean;
public fun getExecutionContext ()Lcom/apollographql/apollo3/api/ExecutionContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ private constructor(
override val canBeBatched: Boolean?,
@ApolloExperimental
val retryOnError: Boolean?,
@ApolloExperimental
val failFastIfOffline: Boolean?,
) : ExecutionOptions {

fun newBuilder(): Builder<D> = newBuilder(operation)
Expand All @@ -38,6 +40,7 @@ private constructor(
.enableAutoPersistedQueries(enableAutoPersistedQueries)
.canBeBatched(canBeBatched)
.retryOnError(retryOnError)
.failFastIfOffline(failFastIfOffline)
}

class Builder<D : Operation.Data>(
Expand All @@ -62,6 +65,14 @@ private constructor(
@ApolloExperimental
var retryOnError: Boolean? = null
private set
@ApolloExperimental
var failFastIfOffline: Boolean? = null
private set

@ApolloExperimental
fun failFastIfOffline(failFastIfOffline: Boolean?): Builder<D> = apply {
this.failFastIfOffline = failFastIfOffline
}

override fun httpMethod(httpMethod: HttpMethod?): Builder<D> = apply {
this.httpMethod = httpMethod
Expand Down Expand Up @@ -119,7 +130,8 @@ private constructor(
sendDocument = sendDocument,
enableAutoPersistedQueries = enableAutoPersistedQueries,
canBeBatched = canBeBatched,
retryOnError = retryOnError
retryOnError = retryOnError,
failFastIfOffline = failFastIfOffline,
)
}
}
Expand Down
1 change: 1 addition & 0 deletions libraries/apollo-runtime/api/android/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public final class com/apollographql/apollo3/ApolloCall : com/apollographql/apol
public synthetic fun enableAutoPersistedQueries (Ljava/lang/Boolean;)Ljava/lang/Object;
public final fun execute (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun executeV3 (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun failFastIfOffline (Ljava/lang/Boolean;)Lcom/apollographql/apollo3/ApolloCall;
public fun getCanBeBatched ()Ljava/lang/Boolean;
public fun getEnableAutoPersistedQueries ()Ljava/lang/Boolean;
public fun getExecutionContext ()Lcom/apollographql/apollo3/api/ExecutionContext;
Expand Down
1 change: 1 addition & 0 deletions libraries/apollo-runtime/api/jvm/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public final class com/apollographql/apollo3/ApolloCall : com/apollographql/apol
public synthetic fun enableAutoPersistedQueries (Ljava/lang/Boolean;)Ljava/lang/Object;
public final fun execute (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun executeV3 (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun failFastIfOffline (Ljava/lang/Boolean;)Lcom/apollographql/apollo3/ApolloCall;
public fun getCanBeBatched ()Ljava/lang/Boolean;
public fun getEnableAutoPersistedQueries ()Ljava/lang/Boolean;
public fun getExecutionContext ()Lcom/apollographql/apollo3/api/ExecutionContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class ApolloCall<D : Operation.Data> internal constructor(
@ApolloExperimental
var retryOnError: Boolean? = null
private set
@ApolloExperimental
var failFastIfOffline: Boolean? = null
private set

/**
* The HTTP headers to be sent with the request.
Expand All @@ -52,6 +55,10 @@ class ApolloCall<D : Operation.Data> internal constructor(
var ignoreApolloClientHttpHeaders: Boolean? = null
private set

fun failFastIfOffline(failFastIfOffline: Boolean?) = apply {
this.failFastIfOffline = failFastIfOffline
}

override fun addExecutionContext(executionContext: ExecutionContext) = apply {
this.executionContext = this.executionContext + executionContext
}
Expand Down Expand Up @@ -119,6 +126,7 @@ class ApolloCall<D : Operation.Data> internal constructor(
.enableAutoPersistedQueries(enableAutoPersistedQueries)
.canBeBatched(canBeBatched)
.retryOnError(retryOnError)
.failFastIfOffline(failFastIfOffline)
}

/**
Expand Down Expand Up @@ -166,6 +174,7 @@ class ApolloCall<D : Operation.Data> internal constructor(
.enableAutoPersistedQueries(enableAutoPersistedQueries)
.canBeBatched(canBeBatched)
.retryOnError(retryOnError)
.failFastIfOffline(failFastIfOffline)
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ private constructor(
private val networkMonitor: NetworkMonitor?
private val retryOnError: ((ApolloRequest<*>) -> Boolean)? = builder.retryOnError
private val retryOnErrorInterceptor: ApolloInterceptor
private val failFastIfOffline = builder.failFastIfOffline

override val executionContext: ExecutionContext = builder.executionContext
override val httpMethod: HttpMethod? = builder.httpMethod
Expand Down Expand Up @@ -287,6 +288,12 @@ private constructor(
retryOnError = [email protected]?.invoke(apolloRequest) ?: false
}
retryOnError(retryOnError)

var failFastIfOffline = apolloRequest.failFastIfOffline
if (failFastIfOffline == null) {
failFastIfOffline = [email protected] ?: false
}
failFastIfOffline(failFastIfOffline)
}
.build()

Expand Down Expand Up @@ -377,6 +384,21 @@ private constructor(
@ApolloExperimental
var retryOnError: ((ApolloRequest<*>) -> Boolean)? = null
private set
@ApolloExperimental
var failFastIfOffline: Boolean? = null
private set

/**
* Whether to fail fast if the device is offline.
*
* In that case, the returned [ApolloResponse.exception] is an instance of [com.apollographql.apollo3.exception.ApolloNetworkException]
*
* @see NetworkMonitor
*/
@ApolloExperimental
fun failFastIfOffline(failFastIfOffline: Boolean?): Builder = apply {
this.failFastIfOffline = failFastIfOffline
}

/**
* Configures the [NetworkMonitor] for this [ApolloClient]
Expand Down Expand Up @@ -848,6 +870,7 @@ private constructor(
.retryOnError(retryOnError)
.retryOnErrorInterceptor(retryOnErrorInterceptor)
.networkMonitor(networkMonitor)
.failFastIfOffline(failFastIfOffline)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import com.apollographql.apollo3.api.ApolloRequest
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.ApolloNetworkException
import com.apollographql.apollo3.exception.SubscriptionOperationException
import com.apollographql.apollo3.network.NetworkMonitor
import com.benasher44.uuid.uuid4
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
Expand All @@ -18,10 +20,6 @@ import kotlin.time.Duration.Companion.seconds

internal class RetryOnErrorInterceptor(private val networkMonitor: NetworkMonitor?): ApolloInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
if (request.retryOnError != true) {
return chain.proceed(request)
}

var first = true
var attempt = 0
return flow {
Expand All @@ -31,9 +29,20 @@ internal class RetryOnErrorInterceptor(private val networkMonitor: NetworkMonito
} else {
request.newBuilder().requestUuid(uuid4()).build()
}

if (request.failFastIfOffline == true && networkMonitor?.isOnline == false) {
throw OfflineException
}

emitAll(chain.proceed(actualRequest))
}.catch {
if (it == OfflineException) {
emit(ApolloResponse.Builder(request.operation, request.requestUuid).exception(OfflineException).build())
} else {
throw it
}
}.onEach {
if (it.exception != null && it.exception!!.isTerminalAndRecoverable()) {
if (request.retryOnError == true && it.exception != null && it.exception!!.isTerminalAndRecoverable()) {
throw RetryException
} else {
attempt = 0
Expand Down Expand Up @@ -65,4 +74,5 @@ private fun ApolloException.isTerminalAndRecoverable(): Boolean {
}
}

private object RetryException: Exception()
private object RetryException: Exception()
private val OfflineException = ApolloNetworkException("The device is offline")
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import app.cash.turbine.test
import com.apollographql.apollo3.api.ApolloRequest
import com.apollographql.apollo3.api.ApolloResponse
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.exception.ApolloNetworkException
import com.apollographql.apollo3.interceptor.ApolloInterceptor
import com.apollographql.apollo3.interceptor.ApolloInterceptorChain
import com.apollographql.apollo3.mockserver.assertNoRequest
Expand All @@ -21,8 +22,34 @@ import kotlinx.coroutines.flow.takeWhile
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertIs
import kotlin.test.assertNull

class NetworkMonitorTest {
@Test
fun failFastIfOfflineTest(): ApolloTestResult {
val fakeNetworkMonitor = FakeNetworkMonitor()

return mockServerTest(clientBuilder = {
networkMonitor(fakeNetworkMonitor)
failFastIfOffline(true)
}) {

fakeNetworkMonitor._isOnline.value = false

apolloClient.query(FooQuery()).toFlow()
.test {
awaitItem().apply {
assertNull(data)
assertIs<ApolloNetworkException>(exception)
assertEquals("The device is offline", exception?.message)
}
mockServer.assertNoRequest()
awaitComplete()
}
}
}

class NetworkMonitorInterceptorTest {
@Test
fun networkMonitorInterceptorTest(): ApolloTestResult {
val fakeNetworkMonitor = FakeNetworkMonitor()
Expand Down
Loading