From 8868baa34a608dc4dd7bcc7b414cba44a7bb7e09 Mon Sep 17 00:00:00 2001 From: David Motsonashvili Date: Wed, 22 May 2024 11:01:18 -0700 Subject: [PATCH] add new error for disabled service, with test (#148) Co-authored-by: David Motsonashvili --- .../generativeai/common/APIController.kt | 10 ++++--- .../client/generativeai/common/Exceptions.kt | 4 +++ .../generativeai/common/server/Types.kt | 3 +++ .../generativeai/common/UnarySnapshotTests.kt | 10 +++++++ .../unary/failure-service-disabled.json | 27 +++++++++++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 common/src/test/resources/golden-files/unary/failure-service-disabled.json diff --git a/common/src/main/kotlin/com/google/ai/client/generativeai/common/APIController.kt b/common/src/main/kotlin/com/google/ai/client/generativeai/common/APIController.kt index ba331325..9d006516 100644 --- a/common/src/main/kotlin/com/google/ai/client/generativeai/common/APIController.kt +++ b/common/src/main/kotlin/com/google/ai/client/generativeai/common/APIController.kt @@ -223,12 +223,13 @@ private fun fullModelName(name: String): String = name.takeIf { it.contains("/") private suspend fun validateResponse(response: HttpResponse) { if (response.status == HttpStatusCode.OK) return val text = response.bodyAsText() - val message = + val error = try { - JSON.decodeFromString(text).error.message + JSON.decodeFromString(text).error } catch (e: Throwable) { - "Unexpected Response:\n$text" + throw ServerException("Unexpected Response:\n$text $e") } + val message = error.message if (message.contains("API key not valid")) { throw InvalidAPIKeyException(message) } @@ -239,6 +240,9 @@ private suspend fun validateResponse(response: HttpResponse) { if (message.contains("quota")) { throw QuotaExceededException(message) } + if (error.details.any { "SERVICE_DISABLED" == it.reason }) { + throw ServiceDisabledException(message) + } throw ServerException(message) } diff --git a/common/src/main/kotlin/com/google/ai/client/generativeai/common/Exceptions.kt b/common/src/main/kotlin/com/google/ai/client/generativeai/common/Exceptions.kt index 491b8cb4..15cd25d9 100644 --- a/common/src/main/kotlin/com/google/ai/client/generativeai/common/Exceptions.kt +++ b/common/src/main/kotlin/com/google/ai/client/generativeai/common/Exceptions.kt @@ -112,6 +112,10 @@ class RequestTimeoutException(message: String, cause: Throwable? = null) : class QuotaExceededException(message: String, cause: Throwable? = null) : GoogleGenerativeAIException(message, cause) +/** The service is not enabled for this project. Visit the Firebase Console to enable it. */ +class ServiceDisabledException(message: String, cause: Throwable? = null) : + GoogleGenerativeAIException(message, cause) + /** Catch all case for exceptions not explicitly expected. */ class UnknownException(message: String, cause: Throwable? = null) : GoogleGenerativeAIException(message, cause) diff --git a/common/src/main/kotlin/com/google/ai/client/generativeai/common/server/Types.kt b/common/src/main/kotlin/com/google/ai/client/generativeai/common/server/Types.kt index 9e79999d..fffd4f3b 100644 --- a/common/src/main/kotlin/com/google/ai/client/generativeai/common/server/Types.kt +++ b/common/src/main/kotlin/com/google/ai/client/generativeai/common/server/Types.kt @@ -141,4 +141,7 @@ enum class FinishReason { data class GRpcError( val code: Int, val message: String, + val details: List, ) + +@Serializable data class GRpcErrorDetails(val reason: String? = null) diff --git a/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt b/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt index f7844fa4..a3f8d77f 100644 --- a/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt +++ b/common/src/test/java/com/google/ai/client/generativeai/common/UnarySnapshotTests.kt @@ -291,4 +291,14 @@ internal class UnarySnapshotTests { } } } + + @Test + fun `service disabled`() = + goldenUnaryFile("failure-service-disabled.json", HttpStatusCode.Forbidden) { + withTimeout(testTimeout) { + shouldThrow { + apiController.generateContent(textGenerateContentRequest("prompt")) + } + } + } } diff --git a/common/src/test/resources/golden-files/unary/failure-service-disabled.json b/common/src/test/resources/golden-files/unary/failure-service-disabled.json new file mode 100644 index 00000000..ed842833 --- /dev/null +++ b/common/src/test/resources/golden-files/unary/failure-service-disabled.json @@ -0,0 +1,27 @@ +{ + "error": { + "code": 403, + "message": "Firebase ML API has not been used in project 12345 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/firebaseml.googleapis.com/overview?project=12345 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.Help", + "links": [ + { + "description": "Google developers console API activation", + "url": "https://console.developers.google.com/apis/api/firebaseml.googleapis.com/overview?project=12345" + } + ] + }, + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "SERVICE_DISABLED", + "domain": "googleapis.com", + "metadata": { + "service": "firebaseml.googleapis.com", + "consumer": "projects/12345" + } + } + ] + } +} \ No newline at end of file