diff --git a/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/data/login/LoginRepositoryImpl.kt b/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/data/login/LoginRepositoryImpl.kt index b42b391..afdacf8 100644 --- a/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/data/login/LoginRepositoryImpl.kt +++ b/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/data/login/LoginRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.nativeapptemplate.nativeapptemplatefree.datastore.NatPreferencesDataS import com.nativeapptemplate.nativeapptemplatefree.model.* import com.nativeapptemplate.nativeapptemplatefree.model.LoggedInShopkeeper import com.nativeapptemplate.nativeapptemplatefree.model.Login +import com.nativeapptemplate.nativeapptemplatefree.network.ApiException import com.nativeapptemplate.nativeapptemplatefree.network.Dispatcher import com.nativeapptemplate.nativeapptemplatefree.network.NatDispatchers import com.nativeapptemplate.nativeapptemplatefree.network.emitApiResponse @@ -46,7 +47,7 @@ class LoginRepositoryImpl @Inject constructor( emit(true) }.suspendOnFailure { clearUserPreferences() - throw Exception(message()) + throw ApiException.UnprocessableError(rawMessage = message()) } }.flowOn(ioDispatcher) diff --git a/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiException.kt b/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiException.kt new file mode 100644 index 0000000..842d73b --- /dev/null +++ b/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiException.kt @@ -0,0 +1,23 @@ +package com.nativeapptemplate.nativeapptemplatefree.network + +/** + * Base exception for API errors thrown by repository implementations. + */ +sealed class ApiException(message: String, cause: Throwable? = null) : Exception(message, cause) { + + /** + * The API returned a structured error response that was successfully deserialized. + */ + class ApiError( + val code: Int, + val apiMessage: String, + ) : ApiException("$apiMessage [Status: $code]") + + /** + * The API returned an error response that could not be deserialized. + */ + class UnprocessableError( + val rawMessage: String, + cause: Throwable? = null, + ) : ApiException("Not processable error($rawMessage).", cause) +} diff --git a/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensions.kt b/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensions.kt index 5ca5ae1..c3a2004 100644 --- a/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensions.kt +++ b/app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensions.kt @@ -53,8 +53,11 @@ inline fun throwApiError( } if (nativeAppTemplateApiError != null) { - throw Exception("${nativeAppTemplateApiError.message} [Status: ${nativeAppTemplateApiError.code}]") + throw ApiException.ApiError( + code = nativeAppTemplateApiError.code, + apiMessage = nativeAppTemplateApiError.message, + ) } else { - throw Exception("Not processable error($errorMessage).") + throw ApiException.UnprocessableError(rawMessage = errorMessage) } } diff --git a/app/src/test/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensionsTest.kt b/app/src/test/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensionsTest.kt index fa8a6e7..763c3f1 100644 --- a/app/src/test/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensionsTest.kt +++ b/app/src/test/kotlin/com/nativeapptemplate/nativeapptemplatefree/network/ApiResponseExtensionsTest.kt @@ -8,6 +8,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test import kotlin.test.assertFailsWith +import kotlin.test.assertIs class ApiResponseExtensionsTest { @@ -26,45 +27,48 @@ class ApiResponseExtensionsTest { } @Test - fun emitApiResponse_onFailure_throwsException() = runTest { + fun emitApiResponse_onFailure_throwsUnprocessableError() = runTest { val response = ApiResponse.Failure.Exception(Exception("network error")) - assertFailsWith { + val exception = assertFailsWith { flow { emitApiResponse(response) }.first() } + assertEquals("network error", exception.rawMessage) } @Test - fun emitApiResponse_withTransform_onFailure_throwsException() = runTest { + fun emitApiResponse_withTransform_onFailure_throwsUnprocessableError() = runTest { val response = ApiResponse.Failure.Exception(Exception("network error")) - assertFailsWith { + val exception = assertFailsWith { flow { emitApiResponse(response) { true } }.first() } + assertEquals("network error", exception.rawMessage) } @Test - fun throwApiError_includesErrorMessageInException() { + fun throwApiError_throwsUnprocessableError_withMessage() { val response: ApiResponse = ApiResponse.Failure.Exception(Exception("timeout")) - val exception = assertFailsWith { + val exception = assertFailsWith { throwApiError(response, "timeout") } + assertEquals("timeout", exception.rawMessage) assertEquals("Not processable error(timeout).", exception.message) } @Test - fun emitApiResponse_onFailure_exceptionContainsNotProcessableError() = runTest { + fun emitApiResponse_onFailure_exceptionIsApiException() = runTest { val response: ApiResponse = ApiResponse.Failure.Exception(Exception("server error")) - val exception = assertFailsWith { + val exception = assertFailsWith { flow { emitApiResponse(response) }.first() } - assertTrue(exception.message!!.contains("Not processable error")) + assertIs(exception) } @Test - fun emitApiResponse_withTransform_onFailure_exceptionContainsNotProcessableError() = runTest { + fun emitApiResponse_withTransform_onFailure_exceptionIsApiException() = runTest { val response: ApiResponse = ApiResponse.Failure.Exception(Exception("server error")) - val exception = assertFailsWith { + val exception = assertFailsWith { flow { emitApiResponse(response) { true } }.first() } - assertTrue(exception.message!!.contains("Not processable error")) + assertIs(exception) } }