From 725b75211d124cba7da57a937a697bc53ec9ca70 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 7 Aug 2022 11:42:36 +0200 Subject: [PATCH] Simplify MealieDataSourceImpl --- .../data/auth/impl/AuthDataSourceImpl.kt | 12 +- .../mealient/extensions/NetworkExtensions.kt | 12 -- .../datasource/MealieDataSourceImpl.kt | 108 ++++++++---------- .../mealient/datasource/MealieService.kt | 3 +- 4 files changed, 49 insertions(+), 86 deletions(-) delete mode 100644 app/src/main/java/gq/kirmanak/mealient/extensions/NetworkExtensions.kt diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt index 53cde84..5721c80 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt @@ -2,7 +2,6 @@ package gq.kirmanak.mealient.data.auth.impl import gq.kirmanak.mealient.data.auth.AuthDataSource import gq.kirmanak.mealient.datasource.MealieDataSource -import gq.kirmanak.mealient.extensions.logAndMapErrors import gq.kirmanak.mealient.logging.Logger import javax.inject.Inject import javax.inject.Singleton @@ -13,13 +12,6 @@ class AuthDataSourceImpl @Inject constructor( private val mealieDataSource: MealieDataSource, ) : AuthDataSource { - override suspend fun authenticate(username: String, password: String, baseUrl: String): String { - logger.v { "authenticate() called with: username = $username, password = $password" } - val accessToken = logger.logAndMapErrors( - block = { mealieDataSource.authenticate(baseUrl, username, password) }, - logProvider = { "sendRequest: can't get token" }, - ) - logger.v { "authenticate() returned: $accessToken" } - return accessToken - } + override suspend fun authenticate(username: String, password: String, baseUrl: String): String = + mealieDataSource.authenticate(baseUrl, username, password) } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/NetworkExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/NetworkExtensions.kt deleted file mode 100644 index 7e9a5af..0000000 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/NetworkExtensions.kt +++ /dev/null @@ -1,12 +0,0 @@ -package gq.kirmanak.mealient.extensions - -import gq.kirmanak.mealient.datasource.mapToNetworkError -import gq.kirmanak.mealient.logging.Logger - -inline fun Logger.logAndMapErrors( - block: () -> T, - noinline logProvider: () -> String -): T = runCatchingExceptCancel(block).getOrElse { - e(it, messageSupplier = logProvider) - throw it.mapToNetworkError() -} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt index 1d5a920..114e784 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt @@ -6,8 +6,8 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream +import okhttp3.ResponseBody import retrofit2.HttpException -import retrofit2.Response import javax.inject.Inject import javax.inject.Singleton @@ -20,70 +20,65 @@ class MealieDataSourceImpl @Inject constructor( override suspend fun addRecipe( baseUrl: String, token: String?, recipe: AddRecipeRequest - ): String { - logger.v { "addRecipe() called with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } - return mealieService.runCatching { addRecipe("$baseUrl/api/recipes/create", token, recipe) } - .onFailure { logger.e(it) { "addRecipe() request failed with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } } - .onSuccess { logger.d { "addRecipe() request succeeded with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } } - .getOrThrowUnauthorized() - } + ): String = makeCall( + block = { addRecipe("$baseUrl/api/recipes/create", token, recipe) }, + logMethod = { "addRecipe" }, + logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" } + ).getOrThrowUnauthorized() override suspend fun authenticate( baseUrl: String, username: String, password: String - ): String { - logger.v { "authenticate() called with: baseUrl = $baseUrl, username = $username, password = $password" } - return mealieService.runCatching { getToken("$baseUrl/api/auth/token", username, password) } - .onFailure { logger.e(it) { "authenticate() request failed with: baseUrl = $baseUrl, username = $username, password = $password" } } - .onSuccess { logger.d { "authenticate() request succeeded with: baseUrl = $baseUrl, username = $username, password = $password" } } - .mapCatching { parseToken(it) } - .getOrThrowUnauthorized() + ): String = makeCall( + block = { getToken("$baseUrl/api/auth/token", username, password) }, + logMethod = { "authenticate" }, + logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" } + ).map { it.accessToken }.getOrElse { + val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it + val errorDetail = errorBody.decode() + throw if (errorDetail.detail == "Unauthorized") NetworkError.Unauthorized(it) else it } - override suspend fun getVersionInfo(baseUrl: String): VersionResponse { - logger.v { "getVersionInfo() called with: baseUrl = $baseUrl" } - return mealieService.runCatching { getVersion("$baseUrl/api/debug/version") } - .onFailure { logger.e(it) { "getVersionInfo() request failed with: baseUrl = $baseUrl" } } - .onSuccess { logger.d { "getVersionInfo() request succeeded with: baseUrl = $baseUrl" } } - .getOrThrowUnauthorized() + override suspend fun getVersionInfo(baseUrl: String): VersionResponse = makeCall( + block = { getVersion("$baseUrl/api/debug/version") }, + logMethod = { "getVersionInfo" }, + logParameters = { "baseUrl = $baseUrl" }, + ).getOrElse { + throw when (it) { + is HttpException, is SerializationException -> NetworkError.NotMealie(it) + else -> NetworkError.MalformedUrl(it) + } } override suspend fun requestRecipes( baseUrl: String, token: String?, start: Int, limit: Int - ): List { - logger.v { "requestRecipes() called with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } - return mealieService.runCatching { - getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) - } - .onFailure { logger.e(it) { "requestRecipes() request failed with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } } - .onSuccess { logger.d { "requestRecipes() request succeeded with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } } - .getOrThrowUnauthorized() - } + ): List = + makeCall( + block = { getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) }, + logMethod = { "requestRecipes" }, + logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } + ).getOrThrowUnauthorized() override suspend fun requestRecipeInfo( baseUrl: String, token: String?, slug: String - ): GetRecipeResponse { - logger.v { "requestRecipeInfo() called with: baseUrl = $baseUrl, token = $token, slug = $slug" } - return mealieService.runCatching { getRecipe("$baseUrl/api/recipes/$slug", token) } - .onFailure { logger.e(it) { "requestRecipeInfo() request failed with: baseUrl = $baseUrl, token = $token, slug = $slug" } } - .onSuccess { logger.d { "requestRecipeInfo() request succeeded with: baseUrl = $baseUrl, token = $token, slug = $slug" } } - .getOrThrowUnauthorized() + ): GetRecipeResponse = makeCall( + block = { getRecipe("$baseUrl/api/recipes/$slug", token) }, + logMethod = { "requestRecipeInfo" }, + logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } + ).getOrThrowUnauthorized() + + private suspend inline fun makeCall( + crossinline block: suspend MealieService.() -> T, + crossinline logMethod: () -> String, + crossinline logParameters: () -> String, + ): Result { + logger.v { "${logMethod()} called with: ${logParameters()}" } + return mealieService.runCatching { block() } + .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } + .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } } } - private fun parseToken( - response: Response - ): String = if (response.isSuccessful) { - response.body()?.accessToken - ?: throw NetworkError.NotMealie(NullPointerException("Body is null")) - } else { - val cause = HttpException(response) - val errorDetail = json.runCatching { decodeErrorBody(response) } - .onFailure { logger.e(it) { "Can't decode error body" } } - .getOrNull() - throw when (errorDetail?.detail) { - "Unauthorized" -> NetworkError.Unauthorized(cause) - else -> NetworkError.NotMealie(cause) - } - } + @OptIn(ExperimentalSerializationApi::class) + private inline fun ResponseBody.decode(): R = json.decodeFromStream(byteStream()) } private fun Result.getOrThrowUnauthorized(): T = getOrElse { @@ -92,15 +87,4 @@ private fun Result.getOrThrowUnauthorized(): T = getOrElse { } else { it } -} - -@OptIn(ExperimentalSerializationApi::class) -private inline fun Json.decodeErrorBody(response: Response): R { - val responseBody = checkNotNull(response.errorBody()) { "Can't decode absent error body" } - return decodeFromStream(responseBody.byteStream()) -} - -fun Throwable.mapToNetworkError(): NetworkError = when (this) { - is HttpException, is SerializationException -> NetworkError.NotMealie(this) - else -> NetworkError.NoServerConnection(this) } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt index 72034f8..9750cf9 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt @@ -2,7 +2,6 @@ package gq.kirmanak.mealient.datasource import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME import gq.kirmanak.mealient.datasource.models.* -import retrofit2.Response import retrofit2.http.* interface MealieService { @@ -13,7 +12,7 @@ interface MealieService { @Url url: String, @Field("username") username: String, @Field("password") password: String, - ): Response + ): GetTokenResponse @POST suspend fun addRecipe(