Sources/GoogleAI/Errors.swift (118 lines of code) (raw):
// 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 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 isInvalidAPIKeyError() -> Bool {
return errorInfo?.reason == "API_KEY_INVALID"
}
func isUnsupportedUserLocationError() -> Bool {
return message == RPCErrorMessage.unsupportedUserLocation.rawValue
}
}
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?
let status: RPCStatus?
let details: [ErrorDetails]
}
struct ErrorDetails {
static let errorInfoType = "type.googleapis.com/google.rpc.ErrorInfo"
let type: String
let reason: String?
let domain: String?
func isErrorInfo() -> Bool {
return type == ErrorDetails.errorInfoType
}
}
extension ErrorDetails: Decodable, Equatable {
enum CodingKeys: String, CodingKey {
case type = "@type"
case reason
case domain
}
}
extension ErrorStatus: Decodable {
enum CodingKeys: CodingKey {
case code
case message
case status
case details
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
code = try container.decodeIfPresent(Int.self, forKey: .code)
message = try container.decodeIfPresent(String.self, forKey: .message)
do {
status = try container.decodeIfPresent(RPCStatus.self, forKey: .status)
} catch {
status = .unknown
}
if container.contains(.details) {
details = try container.decode([ErrorDetails].self, forKey: .details)
} else {
details = []
}
}
}
enum RPCStatus: String, Decodable {
// Not an error; returned on success.
case ok = "OK"
// The operation was cancelled, typically by the caller.
case cancelled = "CANCELLED"
// Unknown error.
case unknown = "UNKNOWN"
// The client specified an invalid argument.
case invalidArgument = "INVALID_ARGUMENT"
// The deadline expired before the operation could complete.
case deadlineExceeded = "DEADLINE_EXCEEDED"
// Some requested entity (e.g., file or directory) was not found.
case notFound = "NOT_FOUND"
// The entity that a client attempted to create (e.g., file or directory) already exists.
case alreadyExists = "ALREADY_EXISTS"
// The caller does not have permission to execute the specified operation.
case permissionDenied = "PERMISSION_DENIED"
// The request does not have valid authentication credentials for the operation.
case unauthenticated = "UNAUTHENTICATED"
// Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
// is out of space.
case resourceExhausted = "RESOURCE_EXHAUSTED"
// The operation was rejected because the system is not in a state required for the operation's
// execution.
case failedPrecondition = "FAILED_PRECONDITION"
// The operation was aborted, typically due to a concurrency issue such as a sequencer check
// failure or transaction abort.
case aborted = "ABORTED"
// The operation was attempted past the valid range.
case outOfRange = "OUT_OF_RANGE"
// The operation is not implemented or is not supported/enabled in this service.
case unimplemented = "UNIMPLEMENTED"
// Internal errors.
case internalError = "INTERNAL"
// The service is currently unavailable.
case unavailable = "UNAVAILABLE"
// Unrecoverable data loss or corruption.
case dataLoss = "DATA_LOSS"
}
enum RPCErrorMessage: String {
case unsupportedUserLocation = "User location is not supported for the API use."
}
enum InvalidCandidateError: Error {
case emptyContent(underlyingError: Error)
case malformedContent(underlyingError: Error)
}