diff --git a/FirebaseVertexAI/Sources/Errors.swift b/FirebaseVertexAI/Sources/Errors.swift index 770dc3b12d3..064f0fcd050 100644 --- a/FirebaseVertexAI/Sources/Errors.swift +++ b/FirebaseVertexAI/Sources/Errors.swift @@ -14,59 +14,6 @@ import Foundation -struct RPCError: Error { - let httpResponseCode: Int - let message: String - let status: RPCStatus - let details: [ErrorDetails] - - private var errorInfo: ErrorDetails? { - return details.first { $0.isErrorInfo() } - } - - init(httpResponseCode: Int, message: String, status: RPCStatus, details: [ErrorDetails]) { - self.httpResponseCode = httpResponseCode - self.message = message - self.status = status - self.details = details - } - - func isVertexAIInFirebaseServiceDisabledError() -> Bool { - return details.contains { $0.isVertexAIInFirebaseServiceDisabledErrorDetails() } - } -} - -extension RPCError: Decodable { - enum CodingKeys: CodingKey { - case error - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let status = try container.decode(ErrorStatus.self, forKey: .error) - - if let code = status.code { - httpResponseCode = code - } else { - httpResponseCode = -1 - } - - if let message = status.message { - self.message = message - } else { - message = "Unknown error." - } - - if let rpcStatus = status.status { - self.status = rpcStatus - } else { - self.status = .unknown - } - - details = status.details - } -} - struct ErrorStatus { let code: Int? let message: String? diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index 30a24c13c6b..ff62a14eb19 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -251,7 +251,7 @@ struct GenerativeAIService { private func parseError(responseData: Data) -> Error { do { - let rpcError = try JSONDecoder().decode(RPCError.self, from: responseData) + let rpcError = try JSONDecoder().decode(BackendError.self, from: responseData) logRPCError(rpcError) return rpcError } catch { @@ -262,7 +262,7 @@ struct GenerativeAIService { // Log specific RPC errors that cannot be mitigated or handled by user code. // These errors do not produce specific GenerateContentError or CountTokensError cases. - private func logRPCError(_ error: RPCError) { + private func logRPCError(_ error: BackendError) { if error.isVertexAIInFirebaseServiceDisabledError() { VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ The Vertex AI in Firebase SDK requires the Vertex AI in Firebase API \ diff --git a/FirebaseVertexAI/Sources/Types/Internal/Errors/BackendError.swift b/FirebaseVertexAI/Sources/Types/Internal/Errors/BackendError.swift new file mode 100644 index 00000000000..1dd88c285f1 --- /dev/null +++ b/FirebaseVertexAI/Sources/Types/Internal/Errors/BackendError.swift @@ -0,0 +1,68 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +struct BackendError: Error { + let httpResponseCode: Int + let message: String + let status: RPCStatus + let details: [ErrorDetails] + + private var errorInfo: ErrorDetails? { + return details.first { $0.isErrorInfo() } + } + + init(httpResponseCode: Int, message: String, status: RPCStatus, details: [ErrorDetails]) { + self.httpResponseCode = httpResponseCode + self.message = message + self.status = status + self.details = details + } + + func isVertexAIInFirebaseServiceDisabledError() -> Bool { + return details.contains { $0.isVertexAIInFirebaseServiceDisabledErrorDetails() } + } +} + +extension BackendError: Decodable { + enum CodingKeys: CodingKey { + case error + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let status = try container.decode(ErrorStatus.self, forKey: .error) + + if let code = status.code { + httpResponseCode = code + } else { + httpResponseCode = -1 + } + + if let message = status.message { + self.message = message + } else { + message = "Unknown error." + } + + if let rpcStatus = status.status { + self.status = rpcStatus + } else { + self.status = .unknown + } + + details = status.details + } +} diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index dc9acd02d55..a0fd9346cd7 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -502,7 +502,7 @@ final class GenerativeModelTests: XCTestCase { do { _ = try await model.generateContent(testPrompt) XCTFail("Should throw GenerateContentError.internalError; no error thrown.") - } catch let GenerateContentError.internalError(error as RPCError) { + } catch let GenerateContentError.internalError(error as BackendError) { XCTAssertEqual(error.httpResponseCode, 400) XCTAssertEqual(error.status, .invalidArgument) XCTAssertEqual(error.message, "API key not valid. Please pass a valid API key.") @@ -524,7 +524,7 @@ final class GenerativeModelTests: XCTestCase { do { _ = try await model.generateContent(testPrompt) XCTFail("Should throw GenerateContentError.internalError; no error thrown.") - } catch let GenerateContentError.internalError(error as RPCError) { + } catch let GenerateContentError.internalError(error as BackendError) { XCTAssertEqual(error.httpResponseCode, expectedStatusCode) XCTAssertEqual(error.status, .permissionDenied) XCTAssertTrue(error.message @@ -607,7 +607,7 @@ final class GenerativeModelTests: XCTestCase { do { _ = try await model.generateContent(testPrompt) XCTFail("Should throw GenerateContentError.internalError; no error thrown.") - } catch let GenerateContentError.internalError(underlying: rpcError as RPCError) { + } catch let GenerateContentError.internalError(underlying: rpcError as BackendError) { XCTAssertEqual(rpcError.status, .invalidArgument) XCTAssertEqual(rpcError.httpResponseCode, expectedStatusCode) XCTAssertEqual(rpcError.message, "Request contains an invalid argument.") @@ -706,7 +706,7 @@ final class GenerativeModelTests: XCTestCase { do { _ = try await model.generateContent(testPrompt) XCTFail("Should throw GenerateContentError.internalError; no error thrown.") - } catch let GenerateContentError.internalError(underlying: rpcError as RPCError) { + } catch let GenerateContentError.internalError(underlying: rpcError as BackendError) { XCTAssertEqual(rpcError.status, .notFound) XCTAssertEqual(rpcError.httpResponseCode, expectedStatusCode) XCTAssertTrue(rpcError.message.hasPrefix("models/unknown is not found")) @@ -849,7 +849,7 @@ final class GenerativeModelTests: XCTestCase { for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } - } catch let GenerateContentError.internalError(error as RPCError) { + } catch let GenerateContentError.internalError(error as BackendError) { XCTAssertEqual(error.httpResponseCode, 400) XCTAssertEqual(error.status, .invalidArgument) XCTAssertEqual(error.message, "API key not valid. Please pass a valid API key.") @@ -873,7 +873,7 @@ final class GenerativeModelTests: XCTestCase { for try await _ in stream { XCTFail("No content is there, this shouldn't happen.") } - } catch let GenerateContentError.internalError(error as RPCError) { + } catch let GenerateContentError.internalError(error as BackendError) { XCTAssertEqual(error.httpResponseCode, expectedStatusCode) XCTAssertEqual(error.status, .permissionDenied) XCTAssertTrue(error.message @@ -1190,7 +1190,7 @@ final class GenerativeModelTests: XCTestCase { XCTAssertNotNil(content.text) responseCount += 1 } - } catch let GenerateContentError.internalError(rpcError as RPCError) { + } catch let GenerateContentError.internalError(rpcError as BackendError) { XCTAssertEqual(rpcError.httpResponseCode, 499) XCTAssertEqual(rpcError.status, .cancelled) @@ -1374,7 +1374,7 @@ final class GenerativeModelTests: XCTestCase { do { _ = try await model.countTokens("Why is the sky blue?") XCTFail("Request should not have succeeded.") - } catch let rpcError as RPCError { + } catch let rpcError as BackendError { XCTAssertEqual(rpcError.httpResponseCode, 404) XCTAssertEqual(rpcError.status, .notFound) XCTAssert(rpcError.message.hasPrefix("models/test-model-name is not found"))