Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-29 11:21:52 +03:00
commit 0f83c529a7
9 changed files with 152 additions and 15 deletions

View file

@ -12,7 +12,11 @@ sealed class ApiResponseError : Exception() {
* @property code The HTTP status code.
* @property message A human-readable message describing the error.
*/
data class HttpException(val code: Code, override val message: String?) : ApiResponseError() {
data class HttpException(
val code: Code,
override val message: String?,
val errorBody: String?,
) : ApiResponseError() {
// region Error Codes
enum class Code(val code: Int) {

View file

@ -16,7 +16,7 @@ internal fun <T : Any> Response<T>.toSafeApiResponse(): ApiResponse<T> {
val e = if (code == null) {
ApiResponseError.UnknownException(IllegalArgumentException("Unknown error status code: ${code()}"))
} else {
ApiResponseError.HttpException(code, message())
ApiResponseError.HttpException(code, message(), errorBody()?.string())
}
apiError(e)

View file

@ -0,0 +1,37 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import java.math.BigDecimal
data class ExpressErrorResponse(
@Json(name = "error")
val error: ExpressError,
)
data class ExpressError(
@Json(name = "code")
val code: Int,
@Json(name = "description")
val description: String?,
@Json(name = "value")
val value: ExpressErrorValue?,
)
data class ExpressErrorValue(
@Json(name = "minAmount")
val minAmount: BigDecimal?,
@Json(name = "decimals")
val decimals: Int?,
@Json(name = "currentAllowance")
val currentAllowance: BigDecimal?,
@Json(name = "receivedFromDecimals")
val receivedFromDecimals: Int?,
@Json(name = "expressFromDecimals")
val expressFromDecimals: Int?,
)

View file

@ -6,6 +6,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.extensions.Result
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.request.PairsRequestBody
@ -23,10 +24,10 @@ import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.converters.*
import com.tangem.feature.swap.domain.SwapRepository
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.createFromAmountWithOffset
import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.mapErrors
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
@ -43,6 +44,7 @@ internal class SwapRepositoryImpl @Inject constructor(
private val configManager: ConfigManager,
private val walletManagersFacade: WalletManagersFacade,
private val walletsStateHolder: WalletsStateHolder,
private val errorsDataConverter: ErrorsDataConverter,
) : SwapRepository {
private val tokensConverter = TokensConverter()
@ -165,7 +167,7 @@ internal class SwapRepositoryImpl @Inject constructor(
),
)
} catch (ex: Exception) {
AggregatedSwapDataModel(null, mapErrors(ex.message))
AggregatedSwapDataModel(null, getDataError(ex))
}
}
}
@ -204,7 +206,7 @@ internal class SwapRepositoryImpl @Inject constructor(
dataModel = expressDataConverter.convert(response),
)
} catch (ex: Exception) {
AggregatedSwapDataModel(null, mapErrors(ex.message))
AggregatedSwapDataModel(null, getDataError(ex))
}
}
}
@ -301,6 +303,14 @@ internal class SwapRepositoryImpl @Inject constructor(
)
}
private fun getDataError(ex: Exception): DataError {
return if (ex is ApiResponseError.HttpException) {
errorsDataConverter.convert(ex.errorBody ?: "")
} else {
DataError.UnknownError
}
}
companion object {
// TODO("get this ids from blockchain enum later")
private const val OPTIMISM_ID = "optimistic-ethereum"

View file

@ -0,0 +1,48 @@
package com.tangem.feature.swap.converters
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.utils.converter.Converter
internal class ErrorsDataConverter(
private val jsonAdapter: JsonAdapter<ExpressErrorResponse>,
) : Converter<String, DataError> {
@Suppress("MagicNumber")
override fun convert(value: String): DataError {
try {
val error = jsonAdapter.fromJson(value)?.error ?: return DataError.UnknownError
return when (error.code) {
2010 -> DataError.BadRequest(code = error.code)
2210 -> DataError.ExchangeProviderNotFoundError(code = error.code)
2220 -> DataError.ExchangeProviderNotActiveError(code = error.code)
2230 -> DataError.ExchangeProviderNotAvailableError(code = error.code)
2240 -> DataError.ExchangeNotPossibleError(code = error.code)
2250 -> DataError.ExchangeTooSmallAmountError(
code = error.code,
amount = SwapAmount(
requireNotNull(error.value?.minAmount),
requireNotNull(error.value?.decimals),
),
)
2260 -> DataError.ExchangeNotEnoughAllowanceError(
code = error.code,
currentAllowance = requireNotNull(error.value?.currentAllowance),
)
2270 -> DataError.ExchangeNotEnoughBalanceError(code = error.code)
2280 -> DataError.ExchangeInvalidAddressError(code = error.code)
2290 -> DataError.ExchangeInvalidFromDecimalsError(
code = error.code,
receivedFromDecimals = requireNotNull(error.value?.receivedFromDecimals),
expressFromDecimals = requireNotNull(error.value?.expressFromDecimals),
)
else -> DataError.UnknownError
}
} catch (e: Exception) {
return DataError.UnknownError
}
}
}

View file

@ -1,12 +1,16 @@
package com.tangem.feature.swap.di
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
import com.tangem.datasource.api.oneinch.OneInchApiFactory
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.config.ConfigManager
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.feature.swap.SwapRepositoryImpl
import com.tangem.feature.swap.converters.ErrorsDataConverter
import com.tangem.feature.swap.domain.SwapRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -17,11 +21,11 @@ import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class SwapDataModule {
internal class SwapDataModule {
@Provides
@Singleton
fun provideSwapRepository(
internal fun provideSwapRepository(
tangemTechApi: TangemTechApi,
tangemExpressApi: TangemExpressApi,
oneInchApiFactory: OneInchApiFactory,
@ -29,6 +33,7 @@ class SwapDataModule {
configManager: ConfigManager,
walletManagerFacade: WalletManagersFacade,
walletsStateHolder: WalletsStateHolder,
errorsDataConverter: ErrorsDataConverter,
): SwapRepository {
return SwapRepositoryImpl(
tangemTechApi = tangemTechApi,
@ -38,6 +43,14 @@ class SwapDataModule {
configManager = configManager,
walletManagersFacade = walletManagerFacade,
walletsStateHolder = walletsStateHolder,
errorsDataConverter = errorsDataConverter,
)
}
@Provides
@Singleton
internal fun provideErrorsConverter(@NetworkMoshi moshi: Moshi): ErrorsDataConverter {
val jsonAdapter = moshi.adapter(ExpressErrorResponse::class.java)
return ErrorsDataConverter(jsonAdapter)
}
}

View file

@ -14,7 +14,6 @@ import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.swap.domain.*
import com.tangem.feature.swap.domain.cache.SwapDataCacheImpl
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider

View file

@ -1,10 +1,36 @@
package com.tangem.feature.swap.domain.models
sealed class DataError {
object UnknownError : DataError()
data class Error(val message: String) : DataError()
}
import java.math.BigDecimal
fun mapErrors(error: String?): DataError {
return error?.let { DataError.Error(it) } ?: DataError.UnknownError
sealed class DataError {
abstract val code: Int
data class BadRequest(override val code: Int) : DataError()
data class ExchangeProviderNotFoundError(override val code: Int) : DataError()
data class ExchangeProviderNotActiveError(override val code: Int) : DataError()
data class ExchangeProviderNotAvailableError(override val code: Int) : DataError()
data class ExchangeNotPossibleError(override val code: Int) : DataError()
data class ExchangeTooSmallAmountError(override val code: Int, val amount: SwapAmount) : DataError()
data class ExchangeNotEnoughAllowanceError(override val code: Int, val currentAllowance: BigDecimal) : DataError()
data class ExchangeNotEnoughBalanceError(override val code: Int) : DataError()
data class ExchangeInvalidAddressError(override val code: Int) : DataError()
data class ExchangeInvalidFromDecimalsError(
override val code: Int,
val receivedFromDecimals: Int,
val expressFromDecimals: Int,
) : DataError()
object UnknownError : DataError() {
override val code: Int = -1
}
}

View file

@ -502,7 +502,7 @@ internal class StateBuilder(
// todo use if needed later
// DataError.InsufficientLiquidity -> TODO()
// DataError.NoError -> TODO()
is DataError.Error -> addWarning(uiState, error.message, true, onClick)
is DataError.ExchangeTooSmallAmountError -> addWarning(uiState, error.amount.toString(), true, onClick)
else -> addWarning(uiState, null, false) {}
}
}