From 90e81f1868db57245243002a7d79c1d8318f08dc Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Nov 2024 19:18:58 +0200 Subject: [PATCH] Updated on 2026-08-14 --- .../markets/DefaultMarketsTokenRepository.kt | 143 ++++++++++++------ .../data/markets/MarketsBatchUpdateFetcher.kt | 7 +- .../analytics/MarketsDataAnalyticsEvent.kt | 43 ++++-- 3 files changed, 133 insertions(+), 60 deletions(-) diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index 2a8c0ed1d7..a329974a0d 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -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 catchApiErrorAndSendEvent(errorEvent: MarketsDataAnalyticsEvent, block: () -> T): T { + inline fun catchListErrorAndSendEvent(block: () -> T): T { + return catchErrorAndSendEvent(block, ::createListErrorEvent) + } + + private inline fun catchDetailsErrorAndSendEvent( + request: MarketsDataAnalyticsEvent.Details.Error.Request, + tokenSymbol: String, + block: () -> T, + ): T { + return catchErrorAndSendEvent(block) { error -> + createDetailsErrorEvent(error, request, tokenSymbol) + } + } + + private inline fun 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 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) + } + } + } } \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt index f1a92028fb..6a9952d71f 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt @@ -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, TokenMarketUpdateRequest> { override suspend fun BatchUpdateFetcher.UpdateContext>.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 catchApiError(onError: () -> Unit, block: () -> T): T { + private inline fun catchApiError(onError: (ApiResponseError) -> Unit, block: () -> T): T { return try { block() } catch (e: ApiResponseError) { - onError() + onError(e) throw e } } diff --git a/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt index 1c131a3cd4..e0d27728a0 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt @@ -9,7 +9,16 @@ sealed interface MarketsDataAnalyticsEvent { params: Map = 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"), + } } \ No newline at end of file