diff --git a/Legacy/src/main/java/com/infomaniak/lib/core/api/ApiController.kt b/Legacy/src/main/java/com/infomaniak/lib/core/api/ApiController.kt index 91552c5b5..184a32963 100644 --- a/Legacy/src/main/java/com/infomaniak/lib/core/api/ApiController.kt +++ b/Legacy/src/main/java/com/infomaniak/lib/core/api/ApiController.kt @@ -1,6 +1,6 @@ /* * Infomaniak Core - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2022-2025 Infomaniak Network SA * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,14 +17,12 @@ */ package com.infomaniak.lib.core.api -import androidx.annotation.StringRes import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonParser import com.google.gson.reflect.TypeToken import com.infomaniak.lib.core.BuildConfig.LOGIN_ENDPOINT_URL import com.infomaniak.lib.core.InfomaniakCore -import com.infomaniak.lib.core.R import com.infomaniak.lib.core.auth.TokenInterceptorListener import com.infomaniak.lib.core.models.ApiError import com.infomaniak.lib.core.models.ApiResponse @@ -32,6 +30,7 @@ import com.infomaniak.lib.core.models.ApiResponseStatus.ERROR import com.infomaniak.lib.core.networking.HttpClient import com.infomaniak.lib.core.networking.HttpUtils import com.infomaniak.lib.core.utils.CustomDateTypeAdapter +import com.infomaniak.lib.core.utils.ErrorCodeTranslated import com.infomaniak.lib.core.utils.isNetworkException import com.infomaniak.lib.core.utils.isSerializationException import com.infomaniak.lib.login.ApiToken @@ -191,8 +190,8 @@ object ApiController { scope.setExtra("bodyResponse", bodyResponse) } createErrorResponse( - apiError = createApiError(useKotlinxSerialization, bodyResponse, ServerErrorException(bodyResponse)), - translatedError = R.string.serverError, + InternalTranslatedErrorCode.UnknownError, + InternalErrorPayload(ServerErrorException(bodyResponse), useKotlinxSerialization, bodyResponse), buildErrorResult = buildErrorResult, ) } @@ -202,7 +201,7 @@ object ApiController { } } catch (refreshTokenException: RefreshTokenException) { refreshTokenException.printStackTrace() - return createErrorResponse(translatedError = R.string.anErrorHasOccurred, buildErrorResult = buildErrorResult) + return createErrorResponse(InternalTranslatedErrorCode.UnknownError, buildErrorResult = buildErrorResult) } catch (exception: Exception) { exception.printStackTrace() @@ -217,8 +216,8 @@ object ApiController { } createErrorResponse( - translatedError = R.string.anErrorHasOccurred, - apiError = createApiError(useKotlinxSerialization, bodyResponse, exception = exception), + InternalTranslatedErrorCode.UnknownError, + InternalErrorPayload(exception, useKotlinxSerialization, bodyResponse), buildErrorResult = buildErrorResult, ) } @@ -232,7 +231,9 @@ object ApiController { } if (apiResponse is ApiResponse<*> && apiResponse.result == ERROR) { - apiResponse.translatedError = R.string.anErrorHasOccurred + @Suppress("DEPRECATION") + apiResponse.translatedError = InternalTranslatedErrorCode.UnknownError.translateRes + apiResponse.error = InternalTranslatedErrorCode.UnknownError.toApiError() } return apiResponse @@ -245,26 +246,20 @@ object ApiController { inline fun createInternetErrorResponse( noNetwork: Boolean = false, noinline buildErrorResult: ((apiError: ApiError?, translatedErrorRes: Int) -> T)?, - ) = createErrorResponse( - translatedError = if (noNetwork) R.string.noConnection else R.string.connectionError, - apiError = ApiError(exception = NetworkException()), + ): T = createErrorResponse( + if (noNetwork) InternalTranslatedErrorCode.NoConnection else InternalTranslatedErrorCode.ConnectionError, + InternalErrorPayload(NetworkException()), buildErrorResult = buildErrorResult, ) - fun createApiError(useKotlinxSerialization: Boolean, bodyResponse: String, exception: Exception) = ApiError( - contextJson = if (useKotlinxSerialization) bodyResponse.bodyResponseToJson() else null, - contextGson = when { - useKotlinxSerialization -> null - else -> runCatching { JsonParser.parseString(bodyResponse).asJsonObject }.getOrDefault(null) - }, - exception = exception - ) - inline fun createErrorResponse( - @StringRes translatedError: Int, - apiError: ApiError? = null, + internalErrorCode: InternalTranslatedErrorCode, + payload: InternalErrorPayload? = null, noinline buildErrorResult: ((apiError: ApiError?, translatedErrorRes: Int) -> T)?, ): T { + val apiError = createDetailedApiError(internalErrorCode, payload) + val translatedError = internalErrorCode.translateRes + return buildErrorResult?.invoke(apiError, translatedError) ?: ApiResponse(result = ERROR, error = apiError, translatedError = translatedError) as T } @@ -275,4 +270,27 @@ object ApiController { enum class ApiMethod { GET, PUT, POST, DELETE, PATCH } + + data class InternalErrorPayload( + val exception: Exception? = null, + val useKotlinxSerialization: Boolean? = null, + val bodyResponse: String? = null, + ) + + fun ErrorCodeTranslated.toApiError(): ApiError = createDetailedApiError(this, null) + + fun createDetailedApiError(errorCode: ErrorCodeTranslated, payload: InternalErrorPayload? = null): ApiError { + val useKotlinxSerialization = payload?.useKotlinxSerialization + return ApiError( + code = errorCode.code, + contextJson = if (useKotlinxSerialization == true) payload.bodyResponse?.bodyResponseToJson() else null, + contextGson = when { + useKotlinxSerialization == true -> null + else -> errorCode.runCatching { + payload?.let { JsonParser.parseString(it.bodyResponse).asJsonObject } + }.getOrDefault(null) + }, + exception = payload?.exception + ) + } } diff --git a/Legacy/src/main/java/com/infomaniak/lib/core/api/InternalTranslatedErrorCode.kt b/Legacy/src/main/java/com/infomaniak/lib/core/api/InternalTranslatedErrorCode.kt new file mode 100644 index 000000000..fb3d9d0a3 --- /dev/null +++ b/Legacy/src/main/java/com/infomaniak/lib/core/api/InternalTranslatedErrorCode.kt @@ -0,0 +1,32 @@ +/* + * Infomaniak Core - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.lib.core.api + +import androidx.annotation.StringRes +import com.infomaniak.lib.core.R +import com.infomaniak.lib.core.utils.ErrorCodeTranslated + +enum class InternalTranslatedErrorCode( + override val code: String, + @StringRes override val translateRes: Int, +) : ErrorCodeTranslated { + NoConnection("no_connection", R.string.noConnection), + ConnectionError("connection_error", R.string.connectionError), + UnknownError("an_error_has_occurred", R.string.anErrorHasOccurred), + UserAlreadyPresent("user_already_present", R.string.errorUserAlreadyPresent), +} diff --git a/Legacy/src/main/java/com/infomaniak/lib/core/models/ApiResponse.kt b/Legacy/src/main/java/com/infomaniak/lib/core/models/ApiResponse.kt index 5e9ce8c65..487c90a9d 100644 --- a/Legacy/src/main/java/com/infomaniak/lib/core/models/ApiResponse.kt +++ b/Legacy/src/main/java/com/infomaniak/lib/core/models/ApiResponse.kt @@ -1,6 +1,6 @@ /* * Infomaniak Core - Android - * Copyright (C) 2022-2024 Infomaniak Network SA + * Copyright (C) 2022-2025 Infomaniak Network SA * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,13 +27,18 @@ open class ApiResponse( val result: ApiResponseStatus = ApiResponseStatus.UNKNOWN, val data: @RawValue T? = null, val uri: String? = null, - val error: ApiError? = null, + var error: ApiError? = null, val page: Int = 0, val pages: Int = 0, @SerialName("response_at") @SerializedName("response_at") val responseAt: Long = 0, val total: Int = 0, + @Deprecated( + "translatedError doesn't take into account project specific translated errors specified through " + + "InfomaniakCore.apiErrorCodes. Use translateError() instead", + ReplaceWith("translateError()", "com.infomaniak.lib.core.utils.ApiErrorCode.Companion.translateError"), + ) var translatedError: Int = 0, @SerialName("items_per_page") @SerializedName("items_per_page") diff --git a/Legacy/src/main/java/com/infomaniak/lib/core/utils/ApiErrorCode.kt b/Legacy/src/main/java/com/infomaniak/lib/core/utils/ApiErrorCode.kt index 68e76be54..cc96d8b37 100644 --- a/Legacy/src/main/java/com/infomaniak/lib/core/utils/ApiErrorCode.kt +++ b/Legacy/src/main/java/com/infomaniak/lib/core/utils/ApiErrorCode.kt @@ -1,6 +1,6 @@ /* * Infomaniak Core - Android - * Copyright (C) 2023-2024 Infomaniak Network SA + * Copyright (C) 2023-2025 Infomaniak Network SA * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,26 +19,28 @@ package com.infomaniak.lib.core.utils import androidx.annotation.StringRes import com.infomaniak.lib.core.InfomaniakCore -import com.infomaniak.lib.core.R +import com.infomaniak.lib.core.api.InternalTranslatedErrorCode import com.infomaniak.lib.core.models.ApiResponse -data class ApiErrorCode(val code: String, @StringRes val translateRes: Int) { +interface ErrorCodeTranslated { + val code: String + @get:StringRes + val translateRes: Int +} +data class ApiErrorCode(override val code: String, @StringRes override val translateRes: Int) : ErrorCodeTranslated { companion object { - - const val AN_ERROR_HAS_OCCURRED = "an_error_has_occured" - - private val defaultApiErrorCode = ApiErrorCode(AN_ERROR_HAS_OCCURRED, R.string.anErrorHasOccurred) - @StringRes fun ApiResponse.translateError(): Int = formatError().translateRes - fun ApiResponse.formatError(): ApiErrorCode { + fun ApiResponse.formatError(): ErrorCodeTranslated { val errorCode = error?.code return if (errorCode == null) { - defaultApiErrorCode + InternalTranslatedErrorCode.UnknownError } else { - InfomaniakCore.apiErrorCodes?.firstOrNull { it.code.equals(errorCode, ignoreCase = true) } ?: defaultApiErrorCode + InfomaniakCore.apiErrorCodes?.firstOrNull { it.code.equals(errorCode, ignoreCase = true) } + ?: InternalTranslatedErrorCode.entries.firstOrNull { it.code.equals(errorCode, ignoreCase = true) } + ?: InternalTranslatedErrorCode.UnknownError } } }