Updated on 2026-08-14
This commit is contained in:
parent
5aa2af25f1
commit
90e81f1868
3 changed files with 133 additions and 60 deletions
|
|
@ -64,14 +64,10 @@ internal class DefaultMarketsTokenRepository(
|
|||
|
||||
// we shouldn't infinitely retry on the first batch request
|
||||
val res = if (isFirstBatchFetching) {
|
||||
catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
|
||||
requestCall()
|
||||
}
|
||||
catchListErrorAndSendEvent { requestCall() }
|
||||
} else {
|
||||
retryOnError(priority = true) {
|
||||
catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
|
||||
requestCall()
|
||||
}
|
||||
catchListErrorAndSendEvent { requestCall() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +96,7 @@ internal class DefaultMarketsTokenRepository(
|
|||
marketsApi = marketsApi,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
onApiError = {
|
||||
analyticsEventHandler.send(MarketsDataAnalyticsEvent.List.Error.toEvent())
|
||||
analyticsEventHandler.send(createListErrorEvent(it).toEvent())
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -128,11 +124,9 @@ internal class DefaultMarketsTokenRepository(
|
|||
interval = interval.toRequestParam(),
|
||||
)
|
||||
|
||||
val result = catchApiErrorAndSendEvent(
|
||||
errorEvent = MarketsDataAnalyticsEvent.Details.Error(
|
||||
request = MarketsDataAnalyticsEvent.Details.Error.Request.Chart,
|
||||
tokenSymbol = tokenSymbol,
|
||||
),
|
||||
val result = catchDetailsErrorAndSendEvent(
|
||||
request = MarketsDataAnalyticsEvent.Details.Error.Request.Chart,
|
||||
tokenSymbol = tokenSymbol,
|
||||
) {
|
||||
response.getOrThrow()
|
||||
}
|
||||
|
|
@ -146,6 +140,7 @@ internal class DefaultMarketsTokenRepository(
|
|||
analyticsEventHandler.send(
|
||||
MarketsDataAnalyticsEvent.ChartNullValuesError(
|
||||
requestPath = "coins/history",
|
||||
errorType = MarketsDataAnalyticsEvent.Type.Custom,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -159,18 +154,16 @@ internal class DefaultMarketsTokenRepository(
|
|||
tokenSymbol: String,
|
||||
) = withContext(dispatcherProvider.io) {
|
||||
val mappedTokenId = getTokenIdIfL2Network(tokenId)
|
||||
val response = marketsApi.getCoinsListCharts(
|
||||
coinIds = mappedTokenId,
|
||||
currency = fiatCurrencyCode,
|
||||
interval = interval.toRequestParam(),
|
||||
)
|
||||
|
||||
val chart = catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
|
||||
response.getOrThrow()[mappedTokenId] ?: error(
|
||||
val chart = catchListErrorAndSendEvent {
|
||||
marketsApi.getCoinsListCharts(
|
||||
coinIds = mappedTokenId,
|
||||
currency = fiatCurrencyCode,
|
||||
interval = interval.toRequestParam(),
|
||||
).getOrThrow()[mappedTokenId] ?: error(
|
||||
"No chart preview data for the token $mappedTokenId",
|
||||
)
|
||||
}
|
||||
|
||||
return@withContext TokenChartConverter.convert(
|
||||
interval = interval,
|
||||
value = chart,
|
||||
|
|
@ -180,6 +173,7 @@ internal class DefaultMarketsTokenRepository(
|
|||
analyticsEventHandler.send(
|
||||
MarketsDataAnalyticsEvent.ChartNullValuesError(
|
||||
requestPath = "coins/history_preview",
|
||||
errorType = MarketsDataAnalyticsEvent.Type.Custom,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -192,19 +186,15 @@ internal class DefaultMarketsTokenRepository(
|
|||
tokenSymbol: String,
|
||||
languageCode: String,
|
||||
) = withContext(dispatcherProvider.io) {
|
||||
val response = marketsApi.getCoinMarketData(
|
||||
currency = fiatCurrencyCode,
|
||||
coinId = tokenId,
|
||||
language = languageCode,
|
||||
)
|
||||
|
||||
val result = catchApiErrorAndSendEvent(
|
||||
errorEvent = MarketsDataAnalyticsEvent.Details.Error(
|
||||
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
|
||||
tokenSymbol = tokenSymbol,
|
||||
),
|
||||
val result = catchDetailsErrorAndSendEvent(
|
||||
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
|
||||
tokenSymbol = tokenSymbol,
|
||||
) {
|
||||
response.getOrThrow()
|
||||
marketsApi.getCoinMarketData(
|
||||
currency = fiatCurrencyCode,
|
||||
coinId = tokenId,
|
||||
language = languageCode,
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
val resultResponse = result.applyL2Compatibility(tokenId)
|
||||
|
|
@ -214,19 +204,16 @@ internal class DefaultMarketsTokenRepository(
|
|||
override suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String, tokenSymbol: String) =
|
||||
withContext(dispatcherProvider.io) {
|
||||
// for second markets iteration we should use extended api method with all required fields
|
||||
val response = tangemTechApi.getQuotes(
|
||||
currencyId = fiatCurrencyCode,
|
||||
coinIds = tokenId,
|
||||
fields = marketsQuoteFields.joinToString(separator = ","),
|
||||
)
|
||||
|
||||
val result = catchApiErrorAndSendEvent(
|
||||
errorEvent = MarketsDataAnalyticsEvent.Details.Error(
|
||||
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
|
||||
tokenSymbol = tokenSymbol,
|
||||
),
|
||||
val result = catchDetailsErrorAndSendEvent(
|
||||
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
|
||||
tokenSymbol = tokenSymbol,
|
||||
) {
|
||||
response.getOrThrow()
|
||||
tangemTechApi.getQuotes(
|
||||
currencyId = fiatCurrencyCode,
|
||||
coinIds = tokenId,
|
||||
fields = marketsQuoteFields.joinToString(separator = ","),
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
return@withContext TokenQuotesShortConverter.convert(tokenId, result).toFull()
|
||||
|
|
@ -272,15 +259,75 @@ internal class DefaultMarketsTokenRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private inline fun <T> catchApiErrorAndSendEvent(errorEvent: MarketsDataAnalyticsEvent, block: () -> T): T {
|
||||
inline fun <T> catchListErrorAndSendEvent(block: () -> T): T {
|
||||
return catchErrorAndSendEvent(block, ::createListErrorEvent)
|
||||
}
|
||||
|
||||
private inline fun <T> catchDetailsErrorAndSendEvent(
|
||||
request: MarketsDataAnalyticsEvent.Details.Error.Request,
|
||||
tokenSymbol: String,
|
||||
block: () -> T,
|
||||
): T {
|
||||
return catchErrorAndSendEvent(block) { error ->
|
||||
createDetailsErrorEvent(error, request, tokenSymbol)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> catchErrorAndSendEvent(
|
||||
block: () -> T,
|
||||
createErrorEvent: (ApiResponseError) -> MarketsDataAnalyticsEvent,
|
||||
): T {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: ApiResponseError.HttpException) {
|
||||
analyticsEventHandler.send(errorEvent.toEvent())
|
||||
throw e
|
||||
} catch (e: ApiResponseError.TimeoutException) {
|
||||
} catch (e: ApiResponseError) {
|
||||
val errorEvent = createErrorEvent(e)
|
||||
|
||||
analyticsEventHandler.send(errorEvent.toEvent())
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun createListErrorEvent(error: ApiResponseError): MarketsDataAnalyticsEvent.List.Error {
|
||||
return createErrorEvent(error) { errorType, errorCode ->
|
||||
MarketsDataAnalyticsEvent.List.Error(
|
||||
errorType = errorType,
|
||||
errorCode = errorCode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDetailsErrorEvent(
|
||||
error: ApiResponseError,
|
||||
request: MarketsDataAnalyticsEvent.Details.Error.Request,
|
||||
tokenSymbol: String,
|
||||
): MarketsDataAnalyticsEvent.Details.Error {
|
||||
return createErrorEvent(error) { errorType, errorCode ->
|
||||
MarketsDataAnalyticsEvent.Details.Error(
|
||||
errorType = errorType,
|
||||
errorCode = errorCode,
|
||||
request = request,
|
||||
tokenSymbol = tokenSymbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> createErrorEvent(
|
||||
error: ApiResponseError,
|
||||
createEvent: (MarketsDataAnalyticsEvent.Type, Int?) -> T,
|
||||
): T {
|
||||
return when (error) {
|
||||
is ApiResponseError.HttpException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Http, error.code.code)
|
||||
}
|
||||
is ApiResponseError.TimeoutException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Timeout, null)
|
||||
}
|
||||
is ApiResponseError.NetworkException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Network, null)
|
||||
}
|
||||
is ApiResponseError.UnknownException -> {
|
||||
createEvent(MarketsDataAnalyticsEvent.Type.Unknown, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ internal class MarketsBatchUpdateFetcher(
|
|||
private val marketsApi: TangemTechMarketsApi,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val onApiError: () -> Unit,
|
||||
private val onApiError: (ApiResponseError) -> Unit,
|
||||
) : BatchUpdateFetcher<Int, List<TokenMarket>, TokenMarketUpdateRequest> {
|
||||
|
||||
override suspend fun BatchUpdateFetcher.UpdateContext<Int, List<TokenMarket>>.fetchUpdateAsync(
|
||||
|
|
@ -124,6 +124,7 @@ internal class MarketsBatchUpdateFetcher(
|
|||
analyticsEventHandler.send(
|
||||
MarketsDataAnalyticsEvent.ChartNullValuesError(
|
||||
requestPath = "coins/history_preview",
|
||||
errorType = MarketsDataAnalyticsEvent.Type.Custom,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -133,11 +134,11 @@ internal class MarketsBatchUpdateFetcher(
|
|||
}
|
||||
}
|
||||
|
||||
private inline fun <T> catchApiError(onError: () -> Unit, block: () -> T): T {
|
||||
private inline fun <T> catchApiError(onError: (ApiResponseError) -> Unit, block: () -> T): T {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: ApiResponseError) {
|
||||
onError()
|
||||
onError(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,16 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category = "Markets", event = event, params = params), MarketsDataAnalyticsEvent {
|
||||
|
||||
data object Error : List(event = "Data Error")
|
||||
data class Error(
|
||||
val errorType: Type,
|
||||
val errorCode: Int? = null,
|
||||
) : List(
|
||||
event = "Data Error",
|
||||
params = buildMap {
|
||||
put("Error Type", errorType.value)
|
||||
errorCode?.let { put("Error Code", it.toString()) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Details(
|
||||
|
|
@ -20,12 +29,16 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
data class Error(
|
||||
val request: Request,
|
||||
val tokenSymbol: String,
|
||||
val errorType: Type,
|
||||
val errorCode: Int? = null,
|
||||
) : Details(
|
||||
event = "Data Error",
|
||||
params = mapOf(
|
||||
"Source" to request.source,
|
||||
"Token" to tokenSymbol,
|
||||
),
|
||||
params = buildMap {
|
||||
put("Source", request.source)
|
||||
put("Token", tokenSymbol)
|
||||
errorCode?.let { put("Error Code", it.toString()) }
|
||||
put("Error Type", errorType.value)
|
||||
},
|
||||
) {
|
||||
|
||||
enum class Request(val source: String) {
|
||||
|
|
@ -43,13 +56,25 @@ sealed interface MarketsDataAnalyticsEvent {
|
|||
|
||||
data class ChartNullValuesError(
|
||||
val requestPath: String,
|
||||
val errorType: Type,
|
||||
val errorCode: Int? = null,
|
||||
) : AnalyticsEvent(
|
||||
category = "Markets / Chart",
|
||||
event = "Data Error",
|
||||
params = mapOf("Request path" to requestPath),
|
||||
error = IllegalStateException(
|
||||
"Chart data contains null values from the API",
|
||||
),
|
||||
params = buildMap {
|
||||
put("Request path", requestPath)
|
||||
errorCode?.let { put("Error Code", it.toString()) }
|
||||
put("Error Type", errorType.value)
|
||||
put("Error Description", "Chart data contains null values from the API")
|
||||
},
|
||||
),
|
||||
MarketsDataAnalyticsEvent
|
||||
|
||||
enum class Type(val value: String) {
|
||||
Http("Http"),
|
||||
Timeout("Timeout"),
|
||||
Network("Network"),
|
||||
Custom("Custom"),
|
||||
Unknown("Unknown"),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue