|
1 | | -// Copyright 2024 Google LLC |
| 1 | +// Copyright 2025 Google LLC |
2 | 2 | // |
3 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | 4 | // you may not use this file except in compliance with the License. |
|
14 | 14 |
|
15 | 15 | import Foundation |
16 | 16 |
|
17 | | -/// Represents an error returned by the DataConnect service |
| 17 | +// MARK: - Base Error Definitions |
| 18 | + |
| 19 | +/// A type representing an error returned by the DataConnect service |
| 20 | +/// |
| 21 | +/// - SeeAlso: ``DataConnectError`` for the base error type. |
| 22 | +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) |
| 23 | +public protocol DataConnectError: Error, CustomDebugStringConvertible, CustomStringConvertible { |
| 24 | + var message: String? { get } |
| 25 | + var underlyingError: Error? { get } |
| 26 | +} |
| 27 | + |
| 28 | +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) |
| 29 | +public extension DataConnectError { |
| 30 | + var debugDescription: String { |
| 31 | + return "{\(Self.self), message: \(message ?? "nil"), underlyingError: \(String(describing: underlyingError))}" |
| 32 | + } |
| 33 | + |
| 34 | + var description: String { |
| 35 | + return debugDescription |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +/// A structure representing a type-erased ``DataConnectError``. |
18 | 40 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) |
19 | | -public enum DataConnectError: Error { |
20 | | - /// no firebase app specified. configure not complete |
21 | | - case appNotConfigured |
| 41 | +public struct AnyDataConnectError: Error { |
| 42 | + /// Contained ``DataConnectError`` |
| 43 | + public let dataConnectError: DataConnectError |
| 44 | + |
| 45 | + init<E: DataConnectError>(dataConnectError: E) { |
| 46 | + self.dataConnectError = dataConnectError |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +/// A type that represents an error domain with granular error codes. |
| 51 | +/// - SeeAlso: ``DataConnectError`` for the base error type. |
| 52 | +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) |
| 53 | +public protocol DataConnectDomainError: DataConnectError { |
| 54 | + associatedtype ErrorCode: DataConnectErrorCode |
| 55 | + var code: ErrorCode { get } |
| 56 | +} |
| 57 | + |
| 58 | +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) |
| 59 | +public extension DataConnectDomainError { |
| 60 | + var debugDescription: String { |
| 61 | + return "{\(Self.self), code: \(code), message: \(message ?? "nil"), underlyingError: \(String(describing: underlyingError))}" |
| 62 | + } |
| 63 | + |
| 64 | + var description: String { |
| 65 | + return debugDescription |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +/// A type that represents an error code within an error domain. |
| 70 | +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) |
| 71 | +public protocol DataConnectErrorCode: CustomStringConvertible, Equatable, Sendable, CaseIterable {} |
| 72 | + |
| 73 | +// MARK: - Data Connect Initialization Errors |
| 74 | + |
| 75 | +/// An error that occurs during the initialization of the Data Connect service. |
| 76 | +/// |
| 77 | +/// This error can arise due to various reasons, such as missing configurations or |
| 78 | +/// issues with the underlying gRPC setup. It provides specific error codes |
| 79 | +/// to pinpoint the cause of the initialization failure. |
| 80 | +/// |
| 81 | +/// - SeeAlso: ``DataConnectDomainError`` for the base error type. |
| 82 | +public struct DataConnectInitError: DataConnectDomainError { |
| 83 | + public struct Code: DataConnectErrorCode { |
| 84 | + private let code: String |
| 85 | + private init(_ code: String) { self.code = code } |
| 86 | + |
| 87 | + public static let appNotConfigured = Code("appNotConfigured") |
| 88 | + public static let grpcNotConfigured = Code("grpcNotConfigured") |
| 89 | + |
| 90 | + public static var allCases: [DataConnectInitError.Code] { |
| 91 | + return [appNotConfigured, grpcNotConfigured] |
| 92 | + } |
| 93 | + |
| 94 | + public var description: String { return code } |
| 95 | + } |
| 96 | + |
| 97 | + public let code: Code |
| 98 | + |
| 99 | + public let message: String? |
| 100 | + |
| 101 | + public let underlyingError: Error? |
| 102 | + |
| 103 | + private init(code: Code, message: String? = nil, cause: Error? = nil) { |
| 104 | + self.code = code |
| 105 | + underlyingError = cause |
| 106 | + self.message = message |
| 107 | + } |
| 108 | + |
| 109 | + static func appNotConfigured(message: String? = nil, |
| 110 | + cause: Error? = nil) -> DataConnectInitError { |
| 111 | + return DataConnectInitError(code: .appNotConfigured, message: message, cause: cause) |
| 112 | + } |
| 113 | + |
| 114 | + static func grpcNotConfigured(message: String? = nil, |
| 115 | + cause: Error? = nil) -> DataConnectInitError { |
| 116 | + return DataConnectInitError(code: .grpcNotConfigured, message: message, cause: cause) |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +// MARK: - Data Codec Errors |
| 121 | + |
| 122 | +/// An error that occurs during the encoding or decoding of data within the Data Connect service. |
| 123 | +/// |
| 124 | +/// This error can arise due to various reasons, such as invalid data formats, |
| 125 | +/// incorrect UUIDs, or issues with timestamp/date formats. It provides specific error codes |
| 126 | +/// to pinpoint the cause of the encoding/decoding failure. |
| 127 | +/// |
| 128 | +/// - SeeAlso: ``DataConnectDomainError`` for the base error type. |
| 129 | +public struct DataConnectCodecError: DataConnectDomainError { |
| 130 | + public struct Code: DataConnectErrorCode { |
| 131 | + private let code: String |
| 132 | + |
| 133 | + private init(_ code: String) { self.code = code } |
| 134 | + |
| 135 | + public static let encodingFailed = Code("encodingFailed") |
| 136 | + public static let decodingFailed = Code("decodingFailed") |
| 137 | + public static let invalidUUID = Code("invalidUUID") |
| 138 | + public static let invalidTimestampFormat = Code("invalidTimestampFormat") |
| 139 | + public static let invalidLocalDateFormat = Code("invalidLocalDateFormat") |
| 140 | + |
| 141 | + public static var allCases: [DataConnectCodecError.Code] { |
| 142 | + return [ |
| 143 | + encodingFailed, |
| 144 | + decodingFailed, |
| 145 | + invalidUUID, |
| 146 | + invalidTimestampFormat, |
| 147 | + invalidLocalDateFormat, |
| 148 | + ] |
| 149 | + } |
| 150 | + |
| 151 | + public var description: String { return code } |
| 152 | + } |
| 153 | + |
| 154 | + public let code: Code |
| 155 | + |
| 156 | + public let message: String? |
| 157 | + |
| 158 | + public let underlyingError: (any Error)? |
| 159 | + |
| 160 | + private init(code: Code, message: String? = nil, cause: Error? = nil) { |
| 161 | + self.code = code |
| 162 | + self.message = message |
| 163 | + underlyingError = cause |
| 164 | + } |
| 165 | + |
| 166 | + static func encodingFailed(message: String? = nil, cause: Error? = nil) -> DataConnectCodecError { |
| 167 | + return DataConnectCodecError(code: .encodingFailed, message: message, cause: cause) |
| 168 | + } |
| 169 | + |
| 170 | + static func decodingFailed(message: String? = nil, cause: Error? = nil) -> DataConnectCodecError { |
| 171 | + return DataConnectCodecError(code: .decodingFailed, message: message, cause: cause) |
| 172 | + } |
| 173 | + |
| 174 | + static func invalidUUID(message: String? = nil, cause: Error? = nil) -> DataConnectCodecError { |
| 175 | + return DataConnectCodecError(code: .invalidUUID, message: message, cause: cause) |
| 176 | + } |
| 177 | + |
| 178 | + static func invalidTimestampFormat(message: String? = nil, |
| 179 | + cause: Error? = nil) -> DataConnectCodecError { |
| 180 | + return DataConnectCodecError(code: .invalidTimestampFormat, message: message, cause: cause) |
| 181 | + } |
| 182 | + |
| 183 | + static func invalidLocalDateFormat(message: String? = nil, |
| 184 | + cause: Error? = nil) -> DataConnectCodecError { |
| 185 | + return DataConnectCodecError(code: .invalidLocalDateFormat, message: message, cause: cause) |
| 186 | + } |
| 187 | +} |
| 188 | + |
| 189 | +// MARK: - Operation Execution Error including Partial Errors |
| 190 | + |
| 191 | +/// An error that occurs during the execution of a Data Connect operation. |
| 192 | +/// |
| 193 | +/// This error can arise due to various reasons, such as network issues, server-side errors, |
| 194 | +/// or problems with the operation itself. It may include an optional underlying error, |
| 195 | +/// a message describing the failure, and an optional response from the failed operation. |
| 196 | +/// |
| 197 | +/// When available, the ``response`` will contain more error information and any partially decoded |
| 198 | +/// result |
| 199 | +/// |
| 200 | +/// - SeeAlso: `DataConnectError` for the base error type. |
| 201 | +/// |
| 202 | + |
| 203 | +public struct DataConnectOperationError: DataConnectError { |
| 204 | + public let message: String? |
| 205 | + public let underlyingError: (any Error)? |
| 206 | + public let response: OperationFailureResponse? |
| 207 | + |
| 208 | + private init(message: String? = nil, cause: Error? = nil, |
| 209 | + response: OperationFailureResponse? = nil) { |
| 210 | + self.response = response |
| 211 | + underlyingError = cause |
| 212 | + self.message = message |
| 213 | + } |
| 214 | + |
| 215 | + static func executionFailed(message: String? = nil, cause: Error? = nil, |
| 216 | + response: OperationFailureResponse? = nil) |
| 217 | + -> DataConnectOperationError { |
| 218 | + return DataConnectOperationError(message: message, cause: cause, response: response) |
| 219 | + } |
| 220 | +} |
22 | 221 |
|
23 | | - /// failed to configure gRPC |
24 | | - case grpcNotConfigured |
| 222 | +/// Contains the data and errors sent to us from the backend in its response. |
| 223 | +/// The ``OperationFailureResponse`` if present, is available as part of the |
| 224 | +/// ``DataConnectOperationError`` |
| 225 | +public struct OperationFailureResponse: Sendable { |
| 226 | + /// JSON string whose value is the "data" property provided by the backend in |
| 227 | + /// its response payload; may be `nil` if the "data" property was not provided |
| 228 | + /// in the backend response and/or was `null` in the backend response. |
| 229 | + public let rawJsonData: String? |
25 | 230 |
|
26 | | - /// failed to decode results from server |
27 | | - case decodeFailed |
| 231 | + /// The list of errors in the "error" property provided by the backend in |
| 232 | + /// its response payload; may be empty if the "errors" property was not |
| 233 | + /// provided in the backend response and/or was an empty list in the backend response. |
| 234 | + public let errors: [ErrorInfo] |
28 | 235 |
|
29 | | - /// Invalid uuid format during encoding / decoding of data |
30 | | - case invalidUUID |
| 236 | + // (Partially) decoded data |
| 237 | + private let data: Sendable? |
31 | 238 |
|
32 | | - /// date components specified to initialize LocalDate are invalid |
33 | | - case invalidLocalDateFormat |
| 239 | + /// Returns `rawJsonData` string decoded into the given type, if decoding was |
| 240 | + /// successful when the operation was executed. |
| 241 | + /// |
| 242 | + /// - Parameter asType: The type to decode the `rawJsonData` into (defaults to the inferred |
| 243 | + /// generic parameter). |
| 244 | + /// - Returns: The decoded data of type `Data` (generic parameter), if decoding to the |
| 245 | + /// generic parameter was successful when the operation was executed, `nil` otherwise. |
| 246 | + public func data<Data: Decodable>(asType: Data.Type = Data.self) -> Data? { |
| 247 | + return data as? Data |
| 248 | + } |
34 | 249 |
|
35 | | - /// timestamp components specified to initialize Timestamp are invalid |
36 | | - case invalidTimestampFormat |
| 250 | + init(rawJsonData: String? = nil, |
| 251 | + errors: [ErrorInfo], |
| 252 | + data: Sendable?) { |
| 253 | + self.rawJsonData = rawJsonData |
| 254 | + self.errors = errors |
| 255 | + self.data = data |
| 256 | + } |
37 | 257 |
|
38 | | - /// generic operation execution error |
39 | | - case operationExecutionFailed(messages: String?) |
| 258 | + public struct ErrorInfo: Codable, Sendable { |
| 259 | + /// The error message (if available) |
| 260 | + public let message: String |
| 261 | + /// The path to the field to which this error applies. |
| 262 | + public let path: [DataConnectPathSegment] |
| 263 | + } |
40 | 264 | } |
0 commit comments