Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -46,7 +47,7 @@ class LoginRepositoryImpl @Inject constructor(
emit(true)
}.suspendOnFailure {
clearUserPreferences()
throw Exception(message())
throw ApiException.UnprocessableError(rawMessage = message())
}
}.flowOn(ioDispatcher)

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ inline fun <reified T : Any> 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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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<Exception> {
val exception = assertFailsWith<ApiException.UnprocessableError> {
flow { emitApiResponse<String>(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<Exception> {
val exception = assertFailsWith<ApiException.UnprocessableError> {
flow { emitApiResponse<String, Boolean>(response) { true } }.first()
}
assertEquals("network error", exception.rawMessage)
}

@Test
fun throwApiError_includesErrorMessageInException() {
fun throwApiError_throwsUnprocessableError_withMessage() {
val response: ApiResponse<String> = ApiResponse.Failure.Exception(Exception("timeout"))
val exception = assertFailsWith<Exception> {
val exception = assertFailsWith<ApiException.UnprocessableError> {
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<String> = ApiResponse.Failure.Exception(Exception("server error"))
val exception = assertFailsWith<Exception> {
val exception = assertFailsWith<ApiException> {
flow { emitApiResponse<String>(response) }.first()
}
assertTrue(exception.message!!.contains("Not processable error"))
assertIs<ApiException.UnprocessableError>(exception)
}

@Test
fun emitApiResponse_withTransform_onFailure_exceptionContainsNotProcessableError() = runTest {
fun emitApiResponse_withTransform_onFailure_exceptionIsApiException() = runTest {
val response: ApiResponse<String> = ApiResponse.Failure.Exception(Exception("server error"))
val exception = assertFailsWith<Exception> {
val exception = assertFailsWith<ApiException> {
flow { emitApiResponse<String, Boolean>(response) { true } }.first()
}
assertTrue(exception.message!!.contains("Not processable error"))
assertIs<ApiException.UnprocessableError>(exception)
}
}
Loading