Skip to content

Commit

Permalink
fix: allow retry on NoTransformationFoundException (requested json bu…
Browse files Browse the repository at this point in the history
…t got xml)
  • Loading branch information
David Ly committed Sep 2, 2023
1 parent 383ac16 commit d83065c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.ktor.client.HttpClient
import io.ktor.client.call.NoTransformationFoundException
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.HttpResponseValidator
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import javax.inject.Singleton
import ly.david.data.BuildConfig
import ly.david.data.coverart.api.CoverArtArchiveApi
import ly.david.data.core.network.ApiHttpClient
import ly.david.data.core.network.RecoverableNetworkException
import ly.david.data.coverart.api.CoverArtArchiveApi
import ly.david.data.network.MusicBrainzAuthState
import ly.david.data.network.api.MusicBrainzApi
import ly.david.data.spotify.api.SpotifyApi
Expand All @@ -22,7 +28,30 @@ object NetworkModule {
@Singleton
@Provides
fun provideHttpClient(): HttpClient {
return ApiHttpClient.create()
return ApiHttpClient.configAndCreate {
HttpResponseValidator {
handleResponseExceptionWithRequest { exception, _ ->
handleRecoverableException(exception)
}
}
}
}

private suspend fun handleRecoverableException(exception: Throwable) {
when (exception) {
is NoTransformationFoundException -> {
// TODO: should be logged
throw RecoverableNetworkException("Requested json but got xml")
}

is ClientRequestException -> {
val exceptionResponse = exception.response
if (exceptionResponse.status == HttpStatusCode.Unauthorized) {
val exceptionResponseText = exceptionResponse.bodyAsText()
throw RecoverableNetworkException(exceptionResponseText)
}
}
}
}

@Singleton
Expand All @@ -38,10 +67,12 @@ object NetworkModule {
fun provideMusicBrainzApi(
httpClient: HttpClient,
musicBrainzAuthState: MusicBrainzAuthState,
): MusicBrainzApi = MusicBrainzApi.create(
httpClient = httpClient,
musicBrainzAuthState = musicBrainzAuthState,
)
): MusicBrainzApi {
return MusicBrainzApi.create(
httpClient = httpClient,
musicBrainzAuthState = musicBrainzAuthState,
)
}

@Singleton
@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ly.david.data.core.network

import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.LogLevel
Expand All @@ -9,7 +10,7 @@ import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json

object ApiHttpClient {
fun create(): HttpClient {
fun configAndCreate(block: HttpClientConfig<*>.() -> Unit): HttpClient {
return HttpClient(OkHttp) {
expectSuccess = true

Expand All @@ -25,6 +26,8 @@ object ApiHttpClient {
}
)
}

block()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
package ly.david.data.network.api

import io.ktor.client.HttpClient
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.HttpResponseValidator
import io.ktor.client.plugins.auth.Auth
import io.ktor.client.plugins.auth.providers.BearerTokens
import io.ktor.client.plugins.auth.providers.bearer
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.http.userAgent
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import ly.david.data.core.network.RecoverableNetworkException
import ly.david.data.network.MusicBrainzAuthState

private const val MUSIC_BRAINZ_API_BASE_URL = "$MUSIC_BRAINZ_BASE_URL/ws/2/"
Expand All @@ -31,33 +21,11 @@ interface MusicBrainzApi : SearchApi, BrowseApi, LookupApi, CollectionApi, Music
musicBrainzAuthState: MusicBrainzAuthState,
): MusicBrainzApi {
val extendedClient = httpClient.config {
HttpResponseValidator {
handleResponseExceptionWithRequest { exception, _ ->
val clientException =
exception as? ClientRequestException ?: return@handleResponseExceptionWithRequest
val exceptionResponse = clientException.response
if (exceptionResponse.status == HttpStatusCode.Unauthorized) {
val exceptionResponseText = exceptionResponse.bodyAsText()
throw RecoverableNetworkException(exceptionResponseText)
}
}
}
defaultRequest {
url(MUSIC_BRAINZ_API_BASE_URL)
userAgent(USER_AGENT_VALUE)
header(ACCEPT, ACCEPT_VALUE)
}
install(Logging) {
level = LogLevel.ALL
}
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
isLenient = true
}
)
}
install(Auth) {
bearer {
loadTokens {
Expand Down

0 comments on commit d83065c

Please sign in to comment.