Map auth errors to internal representation

This commit is contained in:
Kirill Kamakin
2021-11-21 17:27:22 +03:00
parent e8089c6684
commit 808e1ce359
6 changed files with 92 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
package gq.kirmanak.mealient.data.auth
import gq.kirmanak.mealient.data.auth.impl.GetTokenResponse
import retrofit2.Response
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
@@ -15,5 +16,5 @@ interface AuthService {
@Field("scope") scope: String? = null,
@Field("client_id") clientId: String? = null,
@Field("client_secret") clientSecret: String? = null
): GetTokenResponse
): Response<GetTokenResponse>
}

View File

@@ -2,15 +2,25 @@ package gq.kirmanak.mealient.data.auth.impl
import gq.kirmanak.mealient.data.auth.AuthDataSource
import gq.kirmanak.mealient.data.auth.AuthService
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.*
import gq.kirmanak.mealient.data.impl.ErrorDetail
import gq.kirmanak.mealient.data.impl.RetrofitBuilder
import kotlinx.coroutines.CancellationException
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import retrofit2.HttpException
import retrofit2.Response
import retrofit2.create
import timber.log.Timber
import java.io.InputStream
import javax.inject.Inject
@ExperimentalSerializationApi
class AuthDataSourceImpl @Inject constructor(
private val retrofitBuilder: RetrofitBuilder
private val retrofitBuilder: RetrofitBuilder,
private val json: Json,
) : AuthDataSource {
override suspend fun authenticate(
username: String,
@@ -19,8 +29,37 @@ class AuthDataSourceImpl @Inject constructor(
): String {
Timber.v("authenticate() called with: username = $username, password = $password, baseUrl = $baseUrl")
val authService = retrofitBuilder.buildRetrofit(baseUrl).create<AuthService>()
val response = authService.getToken(username, password)
Timber.d("authenticate() response is $response")
return response.accessToken
val accessToken = runCatching {
authService.getToken(username, password)
}.mapCatching {
Timber.d("authenticate() response is $it")
if (!it.isSuccessful) {
val cause = HttpException(it)
throw when (it.decodeErrorBodyOrNull<GetTokenResponse, ErrorDetail>()?.detail) {
"Unauthorized" -> Unauthorized(cause)
else -> NotMealie(cause)
}
}
checkNotNull(it.body()).accessToken // Can't be null here, would throw SerializationException
}.onFailure {
Timber.e(it, "authenticate: getToken failed")
throw when (it) {
is CancellationException, is AuthenticationError -> it
is SerializationException, is IllegalStateException -> NotMealie(it)
else -> NoServerConnection(it)
}
}.getOrThrow()
Timber.v("authenticate() returned: $accessToken")
return accessToken
}
private inline fun <reified T, reified R> Response<T>.decodeErrorBodyOrNull(): R? =
errorBody()?.byteStream()?.let { json.decodeFromStreamOrNull<R>(it) }
private inline fun <reified T> Json.decodeFromStreamOrNull(stream: InputStream): T? =
runCatching { decodeFromStream<T>(stream) }
.onFailure { Timber.e(it, "decodeFromStreamOrNull: can't decode") }
.getOrNull()
}

View File

@@ -0,0 +1,7 @@
package gq.kirmanak.mealient.data.auth.impl
sealed class AuthenticationError(cause: Throwable) : RuntimeException(cause) {
class Unauthorized(cause: Throwable) : AuthenticationError(cause)
class NoServerConnection(cause: Throwable) : AuthenticationError(cause)
class NotMealie(cause: Throwable) : AuthenticationError(cause)
}

View File

@@ -0,0 +1,7 @@
package gq.kirmanak.mealient.data.impl
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ErrorDetail(@SerialName("detail") val detail: String? = null)