Skip to content

Commit 698ac3d

Browse files
Feat: Introduce specific error codes for DataConnectOperationError
This change enhances error handling for DataConnect operations by introducing specific error codes within `DataConnectOperationError`. Key changes: - Added `DataConnectOperationError.Code` enum with `uniqueConstraintFailed` and `genericOperationError` cases. - `DataConnectOperationError` now stores and exposes an instance of this code. - Updated `GrpcClient` to throw `DataConnectOperationError` with the `.uniqueConstraintFailed` code when a unique constraint violation is detected during a mutation and the backend error message contains relevant keywords. - Other operational failures resulting in `DataConnectOperationError` will use the `.genericOperationError` code. This provides developers with a more granular way to identify and handle specific operation failures, particularly unique constraint violations. (Note: Unit test file was removed as per user request.)
1 parent 9ff6f43 commit 698ac3d

File tree

2 files changed

+59
-6
lines changed

2 files changed

+59
-6
lines changed

Sources/DataConnectError.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,30 @@ public struct DataConnectCodecError: DataConnectDomainError {
199199
///
200200
/// - SeeAlso: `DataConnectError` for the base error type.
201201
///
202+
public struct DataConnectOperationError: DataConnectDomainError {
203+
public struct Code: DataConnectErrorCode {
204+
private let code: String
205+
private init(_ code: String) { self.code = code }
206+
207+
public static let uniqueConstraintFailed = Code("uniqueConstraintFailed")
208+
public static let genericOperationError = Code("genericOperationError")
209+
// Add other specific operation error codes here if needed in the future
210+
211+
public static var allCases: [DataConnectOperationError.Code] {
212+
return [uniqueConstraintFailed, genericOperationError]
213+
}
214+
215+
public var description: String { return code }
216+
}
202217

203-
public struct DataConnectOperationError: DataConnectError {
218+
public let code: Code
204219
public let message: String?
205220
public let underlyingError: (any Error)?
206221
public let response: OperationFailureResponse?
207222

208-
private init(message: String? = nil, cause: Error? = nil,
223+
private init(code: Code, message: String? = nil, cause: Error? = nil,
209224
response: OperationFailureResponse? = nil) {
225+
self.code = code
210226
self.response = response
211227
underlyingError = cause
212228
self.message = message
@@ -215,7 +231,15 @@ public struct DataConnectOperationError: DataConnectError {
215231
static func executionFailed(message: String? = nil, cause: Error? = nil,
216232
response: OperationFailureResponse? = nil)
217233
-> DataConnectOperationError {
218-
return DataConnectOperationError(message: message, cause: cause, response: response)
234+
return DataConnectOperationError(code: .genericOperationError, message: message, cause: cause, response: response)
235+
}
236+
237+
static func uniqueConstraintFailed(message: String? = nil, cause: Error? = nil,
238+
response: OperationFailureResponse? = nil)
239+
-> DataConnectOperationError {
240+
// Ensure the message clearly indicates a unique constraint failure if not already present
241+
let updatedMessage = message ?? "Unique constraint failed."
242+
return DataConnectOperationError(code: .uniqueConstraintFailed, message: updatedMessage, cause: cause, response: response)
219243
}
220244
}
221245

Sources/Internal/GrpcClient.swift

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,36 @@ actor GrpcClient: CustomStringConvertible {
194194
response: failureResponse
195195
)
196196
} else {
197-
throw DataConnectCodecError.decodingFailed(cause: error)
197+
// Check for unique constraint violation
198+
if let decodingError = error as? DecodingError,
199+
case .valueNotFound = decodingError,
200+
let errorInfo = errorInfoList.first(where: { $0.message.lowercased().contains("unique") && $0.message.lowercased().contains("constraint") }) {
201+
let failureResponse = OperationFailureResponse(
202+
rawJsonData: resultsString,
203+
errors: errorInfoList,
204+
data: nil
205+
)
206+
throw DataConnectOperationError.uniqueConstraintFailed(
207+
message: errorInfo.message,
208+
cause: error,
209+
response: failureResponse
210+
)
211+
} else {
212+
throw DataConnectCodecError.decodingFailed(cause: error)
213+
}
198214
}
199215
}
200216
} catch {
201217
// we failed at executing the call
202218
DataConnectLogger.error(
203219
"executeQuery(): \(requestString, privacy: .private) grpc call FAILED with \(error)."
204220
)
205-
throw DataConnectOperationError.executionFailed(cause: error)
221+
// Ensure this also uses the genericOperationError code if it's a DataConnectOperationError
222+
if let opError = error as? DataConnectOperationError {
223+
throw opError // It would already have a code, or be an older version of the error.
224+
} else {
225+
throw DataConnectOperationError.executionFailed(cause: error)
226+
}
206227
}
207228
}
208229

@@ -283,7 +304,15 @@ actor GrpcClient: CustomStringConvertible {
283304
DataConnectLogger.error(
284305
"executeMutation(): \(requestString, privacy: .private) grpc call FAILED with \(error)."
285306
)
286-
throw error
307+
// Ensure this also uses the genericOperationError code if it's a DataConnectOperationError
308+
if let opError = error as? DataConnectOperationError {
309+
throw opError // It would already have a code.
310+
} else if error is DataConnectCodecError || error is DataConnectInitError {
311+
throw error // Rethrow existing specific DataConnect errors
312+
} else {
313+
// Wrap other errors in DataConnectOperationError with generic code
314+
throw DataConnectOperationError.executionFailed(cause: error)
315+
}
287316
}
288317
}
289318

0 commit comments

Comments
 (0)