From 6853ae86396612f778decaab5aecc0b3a6a04a17 Mon Sep 17 00:00:00 2001 From: Akshay Yadav Date: Fri, 25 Mar 2022 19:12:42 +0530 Subject: [PATCH] HttpCacheMissException masks ApolloException in case of NETWORK_FIRST (#3957) * HttpCacheMissException masks ApolloException in case of NETWORK_FIRST cache policy * Refactor ApolloCompositeException 1. Use Throwable.addSuppressed to ensure both stacktrace are thrown 2. Annotate with @Throws to make sure client is aware of RuntimeExceptions * Replace with ApolloCompositeException in respect of code consistency * Update apollo-http-cache/src/main/kotlin/com/apollographql/apollo3/cache/http/CachingHttpInterceptor.kt Co-authored-by: Martin Bonnin * Simplify catch block suggested by @martinbonnin * For backward compatibility keep cause to secondException itself * @Throws is only available for jvm packages * Reverted the parameter name changes * fix wrong calling argument name used * comments updated for ApolloCompositeException and NETWORK_FIRST fetch policy * Add both exceptions in ApolloCompositeExceptions as suppressed Also deprecate first and second exceptions usage * split if conditions Co-authored-by: Martin Bonnin --- .../apollo3/exception/Exceptions.kt | 25 ++++++++++++++++--- .../cache/http/CachingHttpInterceptor.kt | 25 +++++++++++++++++-- .../cache/normalized/ClientCacheExtensions.kt | 4 +-- .../normalized/FetchPolicyInterceptors.kt | 12 ++++----- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt b/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt index ad77a5a3b4b..b56610206f7 100644 --- a/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt +++ b/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt @@ -98,11 +98,30 @@ class CacheMissException(val key: String, val fieldName: String? = null) : Apoll class HttpCacheMissException(message: String, cause: Exception? = null) : ApolloException(message = message, cause = cause) /** - * Multiple exceptions happened, for an exemple with a [CacheFirst] fetch policy + * Multiple exceptions happened, for an example with a [CacheFirst] fetch policy + * [cause] might change in future where both exceptions will be added as suppressed exception */ class ApolloCompositeException(first: Throwable?, second: Throwable?) : ApolloException(message = "multiple exceptions happened", second) { - val first = (first as? ApolloException) ?: throw RuntimeException("unexpected first exception", first) - val second = (second as? ApolloException) ?: throw RuntimeException("unexpected second exception", second) + + @get:Deprecated("Use suppressedExceptions instead", ReplaceWith("suppressedExceptions.first()")) + val first: ApolloException + get() { + val firstException = suppressedExceptions.firstOrNull() + return (firstException as? ApolloException) ?: throw RuntimeException("unexpected first exception", firstException) + } + + @get:Deprecated("Use suppressedExceptions instead", ReplaceWith("suppressedExceptions.getOrNull(1)")) + val second: ApolloException + get() { + val secondException = suppressedExceptions.getOrNull(1) + return (secondException as? ApolloException) ?: throw RuntimeException("unexpected second exception", secondException) + } + + init { + if (first != null) addSuppressed(first) + if (second != null) addSuppressed(second) + } + } class AutoPersistedQueriesNotSupported : ApolloException(message = "The server does not support auto persisted queries") diff --git a/apollo-http-cache/src/main/kotlin/com/apollographql/apollo3/cache/http/CachingHttpInterceptor.kt b/apollo-http-cache/src/main/kotlin/com/apollographql/apollo3/cache/http/CachingHttpInterceptor.kt index d2977d1b1ec..9c977dd2738 100644 --- a/apollo-http-cache/src/main/kotlin/com/apollographql/apollo3/cache/http/CachingHttpInterceptor.kt +++ b/apollo-http-cache/src/main/kotlin/com/apollographql/apollo3/cache/http/CachingHttpInterceptor.kt @@ -7,7 +7,9 @@ import com.apollographql.apollo3.api.http.HttpMethod import com.apollographql.apollo3.api.http.HttpRequest import com.apollographql.apollo3.api.http.HttpResponse import com.apollographql.apollo3.api.http.valueOf +import com.apollographql.apollo3.exception.ApolloCompositeException import com.apollographql.apollo3.exception.ApolloException +import com.apollographql.apollo3.exception.ApolloHttpException import com.apollographql.apollo3.exception.HttpCacheMissException import com.apollographql.apollo3.network.http.HttpInterceptor import com.apollographql.apollo3.network.http.HttpInterceptorChain @@ -48,17 +50,36 @@ class CachingHttpInterceptor( return networkMightThrow(request, chain, cacheKey) } NETWORK_FIRST -> { + val networkException: ApolloException try { val response = networkMightThrow(request, chain, cacheKey) if (response.statusCode in 200..299) { // let HTTP errors through return response + } else { + throw ApolloHttpException( + statusCode = response.statusCode, + headers = response.headers, + body = null, + message = "Http request failed with status code `${response.statusCode}`" + ) } } catch (e: ApolloException) { - + // Original cause of network request failure + networkException = e } - return cacheMightThrow(request, cacheKey) + try { + return cacheMightThrow(request, cacheKey) + } catch (cacheMissException: HttpCacheMissException) { + // In case of exception thrown by network request, + // ApolloException will be suppressed and HttpCacheMissException will throw as cause + // this behavior might change in future where both will be treated as suppressed + throw ApolloCompositeException( + first = networkException, + second = cacheMissException + ) + } } else -> { error("Unknown HTTP fetch policy: $policy") diff --git a/apollo-normalized-cache/src/commonMain/kotlin/com/apollographql/apollo3/cache/normalized/ClientCacheExtensions.kt b/apollo-normalized-cache/src/commonMain/kotlin/com/apollographql/apollo3/cache/normalized/ClientCacheExtensions.kt index e8038040088..2beae8b82b6 100644 --- a/apollo-normalized-cache/src/commonMain/kotlin/com/apollographql/apollo3/cache/normalized/ClientCacheExtensions.kt +++ b/apollo-normalized-cache/src/commonMain/kotlin/com/apollographql/apollo3/cache/normalized/ClientCacheExtensions.kt @@ -212,8 +212,8 @@ fun ApolloCall.executeCacheAndNetwork(): Flow