Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-05 19:12:30 +04:00
parent 194e750c93
commit 04bcb44f76
6 changed files with 55 additions and 25 deletions

View file

@ -7,34 +7,51 @@ package com.tangem.datasource.api.common.response
*/
sealed class ApiResponse<T : Any> {
/**
* Represents a successful response from the API.
*
* @property data The data returned by the API.
*/
data class Success<T : Any>(val data: T) : ApiResponse<T>()
/** Map of headers (header name with list of values) */
abstract val headers: Map<String, List<String>>
/**
* Represents an error response or failure from the API.
* Represents a successful response from the API
*
* @property cause The cause of the error.
* @property data the data returned by the API
* @property headers the headers returned by the API
*/
data class Error(val cause: ApiResponseError) : ApiResponse<Nothing>()
data class Success<T : Any>(
val data: T,
override val headers: Map<String, List<String>> = emptyMap(),
) : ApiResponse<T>()
/**
* Represents an error response or failure from the API
*
* @property cause the cause of the error
* @property headers the headers returned by the API
*/
data class Error(
val cause: ApiResponseError,
override val headers: Map<String, List<String>> = emptyMap(),
) : ApiResponse<Nothing>()
}
/**
* Wraps data in a [ApiResponse.Success] instance.
* Wraps data in a [ApiResponse.Success] instance
*
* @param data The data to wrap.
* @return A [ApiResponse.Success] instance containing the provided data.
* @param data the data to wrap
* @param headers the headers returned by the API
* @return a [ApiResponse.Success] instance containing the provided data
*/
internal fun <T : Any> apiSuccess(data: T): ApiResponse<T> = ApiResponse.Success(data)
internal fun <T : Any> apiSuccess(data: T, headers: Map<String, List<String>>): ApiResponse<T> {
return ApiResponse.Success(data, headers)
}
/**
* Wraps an [ApiResponseError] in a [ApiResponse.Error] instance.
* Wraps an [ApiResponseError] in a [ApiResponse.Error] instance
*
* @param cause The error to wrap.
* @return A [ApiResponse.Error] instance containing the provided error.
* @param cause the error to wrap
* @param headers the headers returned by the API
* @return a [ApiResponse.Error] instance containing the provided error
*/
@Suppress("UNCHECKED_CAST")
internal fun <T : Any> apiError(cause: ApiResponseError): ApiResponse<T> = ApiResponse.Error(cause) as ApiResponse<T>
internal fun <T : Any> apiError(cause: ApiResponseError, headers: Map<String, List<String>>): ApiResponse<T> {
return ApiResponse.Error(cause, headers) as ApiResponse<T>
}

View file

@ -47,7 +47,8 @@ internal class ApiResponseCallDelegate<T : Any>(
Timber.e(e, "onFailure UnknownException")
ApiResponseError.UnknownException(e)
}
val safeResponse = apiError<T>(error)
val safeResponse = apiError<T>(cause = error, headers = emptyMap())
responseCallback.onResponse(this@ApiResponseCallDelegate, Response.success(safeResponse))
}

View file

@ -20,6 +20,8 @@ sealed class ApiResponseError : Exception() {
// region Error Codes
enum class Code(val numericCode: Int) {
// 3xx Server Errors
NOT_MODIFIED(numericCode = 304),
// 4xx Server Errors
BAD_REQUEST(numericCode = 400),
UNAUTHORIZED(numericCode = 401),
@ -64,10 +66,6 @@ sealed class ApiResponseError : Exception() {
;
override fun toString(): String = "$numericCode - $name"
companion object {
val values = values()
}
}
// endregion Error Codes
}

View file

@ -19,4 +19,14 @@ inline fun <T> catchApiResponseError(onError: (ApiResponseError) -> Unit, block:
onError(e)
throw e
}
}
/**
* Checks if the [ApiResponseError] is a network-related error.
* You can provide a custom predicate [codePredicate] to check for specific HTTP status codes.
*/
inline fun ApiResponseError.isNetworkError(
codePredicate: (ApiResponseError.HttpException.Code) -> Boolean = { true },
): Boolean {
return this is ApiResponseError.HttpException && codePredicate(this.code)
}

View file

@ -0,0 +1,3 @@
package com.tangem.datasource.api.common.response
const val IF_NONE_MATCH_HEADER = "IfNoneMatch"

View file

@ -12,12 +12,13 @@ import java.util.concurrent.TimeoutException
import javax.net.ssl.SSLHandshakeException
internal fun <T : Any> Response<T>.toSafeApiResponse(analyticsErrorHandler: AnalyticsErrorHandler): ApiResponse<T> {
val headers = headers().toMultimap()
val body = body()
return if (isSuccessful && body != null) {
apiSuccess(body)
apiSuccess(data = body, headers = headers)
} else {
val code = ApiResponseError.HttpException.Code.values
val code = ApiResponseError.HttpException.Code.entries
.firstOrNull { it.numericCode == code() }
val e = try {
if (code == null) {
@ -33,7 +34,7 @@ internal fun <T : Any> Response<T>.toSafeApiResponse(analyticsErrorHandler: Anal
ApiResponseError.UnknownException(e)
}
apiError(e)
apiError(e, headers)
}
}