Simplify MealieDataSourceImpl

This commit is contained in:
Kirill Kamakin
2022-08-07 11:42:36 +02:00
parent 35566d8fa9
commit 725b75211d
4 changed files with 49 additions and 86 deletions

View File

@@ -2,7 +2,6 @@ package gq.kirmanak.mealient.data.auth.impl
import gq.kirmanak.mealient.data.auth.AuthDataSource import gq.kirmanak.mealient.data.auth.AuthDataSource
import gq.kirmanak.mealient.datasource.MealieDataSource import gq.kirmanak.mealient.datasource.MealieDataSource
import gq.kirmanak.mealient.extensions.logAndMapErrors
import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.logging.Logger
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -13,13 +12,6 @@ class AuthDataSourceImpl @Inject constructor(
private val mealieDataSource: MealieDataSource, private val mealieDataSource: MealieDataSource,
) : AuthDataSource { ) : AuthDataSource {
override suspend fun authenticate(username: String, password: String, baseUrl: String): String { override suspend fun authenticate(username: String, password: String, baseUrl: String): String =
logger.v { "authenticate() called with: username = $username, password = $password" } mealieDataSource.authenticate(baseUrl, username, password)
val accessToken = logger.logAndMapErrors(
block = { mealieDataSource.authenticate(baseUrl, username, password) },
logProvider = { "sendRequest: can't get token" },
)
logger.v { "authenticate() returned: $accessToken" }
return accessToken
}
} }

View File

@@ -1,12 +0,0 @@
package gq.kirmanak.mealient.extensions
import gq.kirmanak.mealient.datasource.mapToNetworkError
import gq.kirmanak.mealient.logging.Logger
inline fun <T> Logger.logAndMapErrors(
block: () -> T,
noinline logProvider: () -> String
): T = runCatchingExceptCancel(block).getOrElse {
e(it, messageSupplier = logProvider)
throw it.mapToNetworkError()
}

View File

@@ -6,8 +6,8 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import okhttp3.ResponseBody
import retrofit2.HttpException import retrofit2.HttpException
import retrofit2.Response
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -20,70 +20,65 @@ class MealieDataSourceImpl @Inject constructor(
override suspend fun addRecipe( override suspend fun addRecipe(
baseUrl: String, token: String?, recipe: AddRecipeRequest baseUrl: String, token: String?, recipe: AddRecipeRequest
): String { ): String = makeCall(
logger.v { "addRecipe() called with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } block = { addRecipe("$baseUrl/api/recipes/create", token, recipe) },
return mealieService.runCatching { addRecipe("$baseUrl/api/recipes/create", token, recipe) } logMethod = { "addRecipe" },
.onFailure { logger.e(it) { "addRecipe() request failed with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } } logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" }
.onSuccess { logger.d { "addRecipe() request succeeded with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } } ).getOrThrowUnauthorized()
.getOrThrowUnauthorized()
}
override suspend fun authenticate( override suspend fun authenticate(
baseUrl: String, username: String, password: String baseUrl: String, username: String, password: String
): String { ): String = makeCall(
logger.v { "authenticate() called with: baseUrl = $baseUrl, username = $username, password = $password" } block = { getToken("$baseUrl/api/auth/token", username, password) },
return mealieService.runCatching { getToken("$baseUrl/api/auth/token", username, password) } logMethod = { "authenticate" },
.onFailure { logger.e(it) { "authenticate() request failed with: baseUrl = $baseUrl, username = $username, password = $password" } } logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" }
.onSuccess { logger.d { "authenticate() request succeeded with: baseUrl = $baseUrl, username = $username, password = $password" } } ).map { it.accessToken }.getOrElse {
.mapCatching { parseToken(it) } val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
.getOrThrowUnauthorized() val errorDetail = errorBody.decode<ErrorDetail>()
throw if (errorDetail.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
} }
override suspend fun getVersionInfo(baseUrl: String): VersionResponse { override suspend fun getVersionInfo(baseUrl: String): VersionResponse = makeCall(
logger.v { "getVersionInfo() called with: baseUrl = $baseUrl" } block = { getVersion("$baseUrl/api/debug/version") },
return mealieService.runCatching { getVersion("$baseUrl/api/debug/version") } logMethod = { "getVersionInfo" },
.onFailure { logger.e(it) { "getVersionInfo() request failed with: baseUrl = $baseUrl" } } logParameters = { "baseUrl = $baseUrl" },
.onSuccess { logger.d { "getVersionInfo() request succeeded with: baseUrl = $baseUrl" } } ).getOrElse {
.getOrThrowUnauthorized() throw when (it) {
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
else -> NetworkError.MalformedUrl(it)
}
} }
override suspend fun requestRecipes( override suspend fun requestRecipes(
baseUrl: String, token: String?, start: Int, limit: Int baseUrl: String, token: String?, start: Int, limit: Int
): List<GetRecipeSummaryResponse> { ): List<GetRecipeSummaryResponse> =
logger.v { "requestRecipes() called with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } makeCall(
return mealieService.runCatching { block = { getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) },
getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) logMethod = { "requestRecipes" },
} logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" }
.onFailure { logger.e(it) { "requestRecipes() request failed with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } } ).getOrThrowUnauthorized()
.onSuccess { logger.d { "requestRecipes() request succeeded with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } }
.getOrThrowUnauthorized()
}
override suspend fun requestRecipeInfo( override suspend fun requestRecipeInfo(
baseUrl: String, token: String?, slug: String baseUrl: String, token: String?, slug: String
): GetRecipeResponse { ): GetRecipeResponse = makeCall(
logger.v { "requestRecipeInfo() called with: baseUrl = $baseUrl, token = $token, slug = $slug" } block = { getRecipe("$baseUrl/api/recipes/$slug", token) },
return mealieService.runCatching { getRecipe("$baseUrl/api/recipes/$slug", token) } logMethod = { "requestRecipeInfo" },
.onFailure { logger.e(it) { "requestRecipeInfo() request failed with: baseUrl = $baseUrl, token = $token, slug = $slug" } } logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
.onSuccess { logger.d { "requestRecipeInfo() request succeeded with: baseUrl = $baseUrl, token = $token, slug = $slug" } } ).getOrThrowUnauthorized()
.getOrThrowUnauthorized()
private suspend inline fun <T> makeCall(
crossinline block: suspend MealieService.() -> T,
crossinline logMethod: () -> String,
crossinline logParameters: () -> String,
): Result<T> {
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( @OptIn(ExperimentalSerializationApi::class)
response: Response<GetTokenResponse> private inline fun <reified R> ResponseBody.decode(): R = json.decodeFromStream(byteStream())
): String = if (response.isSuccessful) {
response.body()?.accessToken
?: throw NetworkError.NotMealie(NullPointerException("Body is null"))
} else {
val cause = HttpException(response)
val errorDetail = json.runCatching<Json, ErrorDetail> { decodeErrorBody(response) }
.onFailure { logger.e(it) { "Can't decode error body" } }
.getOrNull()
throw when (errorDetail?.detail) {
"Unauthorized" -> NetworkError.Unauthorized(cause)
else -> NetworkError.NotMealie(cause)
}
}
} }
private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse { private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse {
@@ -93,14 +88,3 @@ private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse {
it it
} }
} }
@OptIn(ExperimentalSerializationApi::class)
private inline fun <T, reified R> Json.decodeErrorBody(response: Response<T>): 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)
}

View File

@@ -2,7 +2,6 @@ package gq.kirmanak.mealient.datasource
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
import gq.kirmanak.mealient.datasource.models.* import gq.kirmanak.mealient.datasource.models.*
import retrofit2.Response
import retrofit2.http.* import retrofit2.http.*
interface MealieService { interface MealieService {
@@ -13,7 +12,7 @@ interface MealieService {
@Url url: String, @Url url: String,
@Field("username") username: String, @Field("username") username: String,
@Field("password") password: String, @Field("password") password: String,
): Response<GetTokenResponse> ): GetTokenResponse
@POST @POST
suspend fun addRecipe( suspend fun addRecipe(