diff --git a/app/src/main/java/com/plainstudio/stackcasino/data/ApiCall.kt b/app/src/main/java/com/plainstudio/stackcasino/data/ApiCall.kt new file mode 100644 index 0000000..0eb216b --- /dev/null +++ b/app/src/main/java/com/plainstudio/stackcasino/data/ApiCall.kt @@ -0,0 +1,33 @@ +package com.plainstudio.stackcasino.data + +import android.util.Log + +/** + * Runs a remote [block] behind the two guards every API-backed + * repository shares: + * + * 1. A blank credential short-circuits to a failed [Result] without + * touching the network, logging [blankKeyMessage] under [tag]. + * 2. Any exception thrown by [block] is folded into [Result.failure] + * and logged under [tag] with its full stack trace, so a developer + * reading logcat can tell a bad key from a network error at a + * glance instead of seeing only the UI's generic failure copy. + * + * The function is `inline` so [block] can call suspend functions from + * the calling repository's own suspend context. + */ +internal inline fun guardedApiCall( + apiKey: String, + tag: String, + blankKeyMessage: String, + failureMessage: String, + block: () -> T, +): Result { + if (apiKey.isBlank()) { + Log.w(tag, blankKeyMessage) + return Result.failure(IllegalStateException(blankKeyMessage)) + } + return runCatching(block).onFailure { throwable -> + Log.w(tag, failureMessage, throwable) + } +} diff --git a/app/src/main/java/com/plainstudio/stackcasino/data/assistant/GeminiAssistantRepository.kt b/app/src/main/java/com/plainstudio/stackcasino/data/assistant/GeminiAssistantRepository.kt index cd4b650..eef2f3a 100644 --- a/app/src/main/java/com/plainstudio/stackcasino/data/assistant/GeminiAssistantRepository.kt +++ b/app/src/main/java/com/plainstudio/stackcasino/data/assistant/GeminiAssistantRepository.kt @@ -1,10 +1,10 @@ package com.plainstudio.stackcasino.data.assistant -import android.util.Log import com.google.ai.client.generativeai.GenerativeModel import com.google.ai.client.generativeai.type.Content import com.google.ai.client.generativeai.type.content import com.plainstudio.stackcasino.BuildConfig +import com.plainstudio.stackcasino.data.guardedApiCall import com.plainstudio.stackcasino.domain.assistant.AssistantRepository import com.plainstudio.stackcasino.domain.assistant.ChatTurn import com.plainstudio.stackcasino.domain.assistant.Role @@ -35,20 +35,18 @@ class GeminiAssistantRepository override suspend fun sendMessage( history: List, userMessage: String, - ): Result { - if (BuildConfig.GEMINI_API_KEY.isBlank()) { - Log.w(TAG, "GEMINI_API_KEY is empty; aborting before hitting the SDK.") - return Result.failure(IllegalStateException("GEMINI_API_KEY missing")) - } - return runCatching { + ): Result = + guardedApiCall( + apiKey = BuildConfig.GEMINI_API_KEY, + tag = TAG, + blankKeyMessage = "GEMINI_API_KEY is blank; skipping the Gemini call.", + failureMessage = "Nep request failed", + ) { val chat = model.startChat(history = history.toGeminiHistory()) val response = chat.sendMessage(userMessage) response.text?.takeIf { it.isNotBlank() } ?: error("Gemini returned an empty response.") - }.onFailure { throwable -> - Log.w(TAG, "Nep request failed", throwable) } - } private fun List.toGeminiHistory(): List = map { turn -> diff --git a/app/src/main/java/com/plainstudio/stackcasino/data/news/NewsRepositoryImpl.kt b/app/src/main/java/com/plainstudio/stackcasino/data/news/NewsRepositoryImpl.kt index 82a2629..403bc9c 100644 --- a/app/src/main/java/com/plainstudio/stackcasino/data/news/NewsRepositoryImpl.kt +++ b/app/src/main/java/com/plainstudio/stackcasino/data/news/NewsRepositoryImpl.kt @@ -1,7 +1,7 @@ package com.plainstudio.stackcasino.data.news -import android.util.Log import com.plainstudio.stackcasino.BuildConfig +import com.plainstudio.stackcasino.data.guardedApiCall import com.plainstudio.stackcasino.domain.news.NewsArticle import com.plainstudio.stackcasino.domain.news.NewsRepository import java.util.concurrent.ConcurrentHashMap @@ -29,12 +29,13 @@ class NewsRepositoryImpl ) : NewsRepository { private val cache = ConcurrentHashMap() - override suspend fun refresh(): Result> { - if (BuildConfig.NEWSAPI_KEY.isBlank()) { - Log.w(TAG, "NEWSAPI_KEY is empty; refusing to hit NewsAPI.") - return Result.failure(IllegalStateException("NEWSAPI_KEY missing")) - } - return runCatching { + override suspend fun refresh(): Result> = + guardedApiCall( + apiKey = BuildConfig.NEWSAPI_KEY, + tag = TAG, + blankKeyMessage = "NEWSAPI_KEY is blank; skipping the NewsAPI call.", + failureMessage = "NewsAPI refresh failed", + ) { val response = service.fetchEverything(query = NEWSAPI_CASINO_QUERY) if (response.status != "ok") { error("NewsAPI returned ${response.status}: ${response.code} ${response.message}") @@ -43,10 +44,7 @@ class NewsRepositoryImpl }.onSuccess { articles -> cache.clear() articles.forEach { cache[it.id] = it } - }.onFailure { throwable -> - Log.w(TAG, "NewsAPI refresh failed", throwable) } - } override fun findById(id: String): NewsArticle? = cache[id]