Skip to content

Commit 6ccafcc

Browse files
authored
Add helpers to conver between Status.Code and RPCError.Code (#1688)
Motivation: It's often useful to conver between RPCError and Status codes but we don't currently expose any API to do this; users have to go via the `rawValue` APIs. It should be easier than that. Modifications: - Add convenience APIs to `RPCError.Code` and `Status.Code` to create one from the other. Result: - Easier to convert between status and error codes
1 parent aa69fcb commit 6ccafcc

File tree

4 files changed

+93
-42
lines changed

4 files changed

+93
-42
lines changed

Sources/GRPCCore/RPCError.swift

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public struct RPCError: @unchecked Sendable, Hashable, Error {
7575
///
7676
/// - Parameter status: The status to convert.
7777
public init?(status: Status) {
78-
guard let code = Code(statusCode: status.code) else { return nil }
78+
guard let code = Code(status.code) else { return nil }
7979
self.init(code: code, message: status.message, metadata: [:])
8080
}
8181
}
@@ -119,16 +119,20 @@ extension RPCError {
119119
/// The numeric value of the error code.
120120
public var rawValue: Int { Int(self.wrapped.rawValue) }
121121

122-
private var wrapped: Status.Code.Wrapped
123-
private init(_ wrapped: Status.Code.Wrapped) {
124-
self.wrapped = wrapped
122+
internal var wrapped: Status.Code.Wrapped
123+
private init(code: Status.Code.Wrapped) {
124+
self.wrapped = code
125125
}
126126

127-
internal init?(statusCode: Status.Code) {
128-
if statusCode == .ok {
127+
/// Creates an error code from the given ``Status/Code-swift.struct``; returns `nil` if the
128+
/// code is ``Status/Code-swift.struct/ok``.
129+
///
130+
/// - Parameter code: The status code to create this ``RPCError/Code-swift.struct`` from.
131+
public init?(_ code: Status.Code) {
132+
if code == .ok {
129133
return nil
130134
} else {
131-
self.wrapped = statusCode.wrapped
135+
self.wrapped = code.wrapped
132136
}
133137
}
134138

@@ -140,44 +144,44 @@ extension RPCError {
140144

141145
extension RPCError.Code {
142146
/// The operation was cancelled (typically by the caller).
143-
public static let cancelled = Self(.cancelled)
147+
public static let cancelled = Self(code: .cancelled)
144148

145149
/// Unknown error. An example of where this error may be returned is if a
146150
/// Status value received from another address space belongs to an error-space
147151
/// that is not known in this address space. Also errors raised by APIs that
148152
/// do not return enough error information may be converted to this error.
149-
public static let unknown = Self(.unknown)
153+
public static let unknown = Self(code: .unknown)
150154

151155
/// Client specified an invalid argument. Note that this differs from
152156
/// ``failedPrecondition``. ``invalidArgument`` indicates arguments that are
153157
/// problematic regardless of the state of the system (e.g., a malformed file
154158
/// name).
155-
public static let invalidArgument = Self(.invalidArgument)
159+
public static let invalidArgument = Self(code: .invalidArgument)
156160

157161
/// Deadline expired before operation could complete. For operations that
158162
/// change the state of the system, this error may be returned even if the
159163
/// operation has completed successfully. For example, a successful response
160164
/// from a server could have been delayed long enough for the deadline to
161165
/// expire.
162-
public static let deadlineExceeded = Self(.deadlineExceeded)
166+
public static let deadlineExceeded = Self(code: .deadlineExceeded)
163167

164168
/// Some requested entity (e.g., file or directory) was not found.
165-
public static let notFound = Self(.notFound)
169+
public static let notFound = Self(code: .notFound)
166170

167171
/// Some entity that we attempted to create (e.g., file or directory) already
168172
/// exists.
169-
public static let alreadyExists = Self(.alreadyExists)
173+
public static let alreadyExists = Self(code: .alreadyExists)
170174

171175
/// The caller does not have permission to execute the specified operation.
172176
/// ``permissionDenied`` must not be used for rejections caused by exhausting
173177
/// some resource (use ``resourceExhausted`` instead for those errors).
174178
/// ``permissionDenied`` must not be used if the caller can not be identified
175179
/// (use ``unauthenticated`` instead for those errors).
176-
public static let permissionDenied = Self(.permissionDenied)
180+
public static let permissionDenied = Self(code: .permissionDenied)
177181

178182
/// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
179183
/// entire file system is out of space.
180-
public static let resourceExhausted = Self(.resourceExhausted)
184+
public static let resourceExhausted = Self(code: .resourceExhausted)
181185

182186
/// Operation was rejected because the system is not in a state required for
183187
/// the operation's execution. For example, directory to be deleted may be
@@ -197,14 +201,14 @@ extension RPCError.Code {
197201
/// REST Get/Update/Delete on a resource and the resource on the
198202
/// server does not match the condition. E.g., conflicting
199203
/// read-modify-write on the same resource.
200-
public static let failedPrecondition = Self(.failedPrecondition)
204+
public static let failedPrecondition = Self(code: .failedPrecondition)
201205

202206
/// The operation was aborted, typically due to a concurrency issue like
203207
/// sequencer check failures, transaction aborts, etc.
204208
///
205209
/// See litmus test above for deciding between ``failedPrecondition``, ``aborted``,
206210
/// and ``unavailable``.
207-
public static let aborted = Self(.aborted)
211+
public static let aborted = Self(code: .aborted)
208212

209213
/// Operation was attempted past the valid range. E.g., seeking or reading
210214
/// past end of file.
@@ -219,26 +223,26 @@ extension RPCError.Code {
219223
/// ``outOfRange``. We recommend using ``outOfRange`` (the more specific error)
220224
/// when it applies so that callers who are iterating through a space can
221225
/// easily look for an ``outOfRange`` error to detect when they are done.
222-
public static let outOfRange = Self(.outOfRange)
226+
public static let outOfRange = Self(code: .outOfRange)
223227

224228
/// Operation is not implemented or not supported/enabled in this service.
225-
public static let unimplemented = Self(.unimplemented)
229+
public static let unimplemented = Self(code: .unimplemented)
226230

227231
/// Internal errors. Means some invariants expected by underlying System has
228232
/// been broken. If you see one of these errors, Something is very broken.
229-
public static let internalError = Self(.internalError)
233+
public static let internalError = Self(code: .internalError)
230234

231235
/// The service is currently unavailable. This is a most likely a transient
232236
/// condition and may be corrected by retrying with a backoff.
233237
///
234238
/// See litmus test above for deciding between ``failedPrecondition``, ``aborted``,
235239
/// and ``unavailable``.
236-
public static let unavailable = Self(.unavailable)
240+
public static let unavailable = Self(code: .unavailable)
237241

238242
/// Unrecoverable data loss or corruption.
239-
public static let dataLoss = Self(.dataLoss)
243+
public static let dataLoss = Self(code: .dataLoss)
240244

241245
/// The request does not have valid authentication credentials for the
242246
/// operation.
243-
public static let unauthenticated = Self(.unauthenticated)
247+
public static let unauthenticated = Self(code: .unauthenticated)
244248
}

Sources/GRPCCore/Status.swift

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,16 @@ extension Status {
145145
}
146146
}
147147

148-
private init(_ wrapped: Wrapped) {
149-
self.wrapped = wrapped
148+
/// Creates a status code from an ``RPCError/Code-swift.struct``.
149+
///
150+
/// - Parameters:
151+
/// - code: The error code to create this ``Status/Code-swift.struct`` from.
152+
public init(_ code: RPCError.Code) {
153+
self.wrapped = code.wrapped
154+
}
155+
156+
private init(code: Wrapped) {
157+
self.wrapped = code
150158
}
151159

152160
public var description: String {
@@ -157,47 +165,47 @@ extension Status {
157165

158166
extension Status.Code {
159167
/// The operation completed successfully.
160-
public static let ok = Self(.ok)
168+
public static let ok = Self(code: .ok)
161169

162170
/// The operation was cancelled (typically by the caller).
163-
public static let cancelled = Self(.cancelled)
171+
public static let cancelled = Self(code: .cancelled)
164172

165173
/// Unknown error. An example of where this error may be returned is if a
166174
/// Status value received from another address space belongs to an error-space
167175
/// that is not known in this address space. Also errors raised by APIs that
168176
/// do not return enough error information may be converted to this error.
169-
public static let unknown = Self(.unknown)
177+
public static let unknown = Self(code: .unknown)
170178

171179
/// Client specified an invalid argument. Note that this differs from
172180
/// ``failedPrecondition``. ``invalidArgument`` indicates arguments that are
173181
/// problematic regardless of the state of the system (e.g., a malformed file
174182
/// name).
175-
public static let invalidArgument = Self(.invalidArgument)
183+
public static let invalidArgument = Self(code: .invalidArgument)
176184

177185
/// Deadline expired before operation could complete. For operations that
178186
/// change the state of the system, this error may be returned even if the
179187
/// operation has completed successfully. For example, a successful response
180188
/// from a server could have been delayed long enough for the deadline to
181189
/// expire.
182-
public static let deadlineExceeded = Self(.deadlineExceeded)
190+
public static let deadlineExceeded = Self(code: .deadlineExceeded)
183191

184192
/// Some requested entity (e.g., file or directory) was not found.
185-
public static let notFound = Self(.notFound)
193+
public static let notFound = Self(code: .notFound)
186194

187195
/// Some entity that we attempted to create (e.g., file or directory) already
188196
/// exists.
189-
public static let alreadyExists = Self(.alreadyExists)
197+
public static let alreadyExists = Self(code: .alreadyExists)
190198

191199
/// The caller does not have permission to execute the specified operation.
192200
/// ``permissionDenied`` must not be used for rejections caused by exhausting
193201
/// some resource (use ``resourceExhausted`` instead for those errors).
194202
/// ``permissionDenied`` must not be used if the caller can not be identified
195203
/// (use ``unauthenticated`` instead for those errors).
196-
public static let permissionDenied = Self(.permissionDenied)
204+
public static let permissionDenied = Self(code: .permissionDenied)
197205

198206
/// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
199207
/// entire file system is out of space.
200-
public static let resourceExhausted = Self(.resourceExhausted)
208+
public static let resourceExhausted = Self(code: .resourceExhausted)
201209

202210
/// Operation was rejected because the system is not in a state required for
203211
/// the operation's execution. For example, directory to be deleted may be
@@ -217,14 +225,14 @@ extension Status.Code {
217225
/// REST Get/Update/Delete on a resource and the resource on the
218226
/// server does not match the condition. E.g., conflicting
219227
/// read-modify-write on the same resource.
220-
public static let failedPrecondition = Self(.failedPrecondition)
228+
public static let failedPrecondition = Self(code: .failedPrecondition)
221229

222230
/// The operation was aborted, typically due to a concurrency issue like
223231
/// sequencer check failures, transaction aborts, etc.
224232
///
225233
/// See litmus test above for deciding between ``failedPrecondition``, ``aborted``,
226234
/// and ``unavailable``.
227-
public static let aborted = Self(.aborted)
235+
public static let aborted = Self(code: .aborted)
228236

229237
/// Operation was attempted past the valid range. E.g., seeking or reading
230238
/// past end of file.
@@ -239,26 +247,26 @@ extension Status.Code {
239247
/// ``outOfRange``. We recommend using ``outOfRange`` (the more specific error)
240248
/// when it applies so that callers who are iterating through a space can
241249
/// easily look for an ``outOfRange`` error to detect when they are done.
242-
public static let outOfRange = Self(.outOfRange)
250+
public static let outOfRange = Self(code: .outOfRange)
243251

244252
/// Operation is not implemented or not supported/enabled in this service.
245-
public static let unimplemented = Self(.unimplemented)
253+
public static let unimplemented = Self(code: .unimplemented)
246254

247255
/// Internal errors. Means some invariants expected by underlying System has
248256
/// been broken. If you see one of these errors, Something is very broken.
249-
public static let internalError = Self(.internalError)
257+
public static let internalError = Self(code: .internalError)
250258

251259
/// The service is currently unavailable. This is a most likely a transient
252260
/// condition and may be corrected by retrying with a backoff.
253261
///
254262
/// See litmus test above for deciding between ``failedPrecondition``, ``aborted``,
255263
/// and ``unavailable``.
256-
public static let unavailable = Self(.unavailable)
264+
public static let unavailable = Self(code: .unavailable)
257265

258266
/// Unrecoverable data loss or corruption.
259-
public static let dataLoss = Self(.dataLoss)
267+
public static let dataLoss = Self(code: .dataLoss)
260268

261269
/// The request does not have valid authentication credentials for the
262270
/// operation.
263-
public static let unauthenticated = Self(.unauthenticated)
271+
public static let unauthenticated = Self(code: .unauthenticated)
264272
}

Tests/GRPCCoreTests/RPCErrorTests.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ final class RPCErrorTests: XCTestCase {
6161
XCTAssertEqual(error.metadata, [:])
6262
}
6363

64+
func testErrorCodeFromStatusCode() throws {
65+
XCTAssertNil(RPCError.Code(Status.Code.ok))
66+
XCTAssertEqual(RPCError.Code(Status.Code.cancelled), .cancelled)
67+
XCTAssertEqual(RPCError.Code(Status.Code.unknown), .unknown)
68+
XCTAssertEqual(RPCError.Code(Status.Code.invalidArgument), .invalidArgument)
69+
XCTAssertEqual(RPCError.Code(Status.Code.deadlineExceeded), .deadlineExceeded)
70+
XCTAssertEqual(RPCError.Code(Status.Code.notFound), .notFound)
71+
XCTAssertEqual(RPCError.Code(Status.Code.alreadyExists), .alreadyExists)
72+
XCTAssertEqual(RPCError.Code(Status.Code.permissionDenied), .permissionDenied)
73+
XCTAssertEqual(RPCError.Code(Status.Code.resourceExhausted), .resourceExhausted)
74+
XCTAssertEqual(RPCError.Code(Status.Code.failedPrecondition), .failedPrecondition)
75+
XCTAssertEqual(RPCError.Code(Status.Code.aborted), .aborted)
76+
XCTAssertEqual(RPCError.Code(Status.Code.outOfRange), .outOfRange)
77+
XCTAssertEqual(RPCError.Code(Status.Code.unimplemented), .unimplemented)
78+
XCTAssertEqual(RPCError.Code(Status.Code.internalError), .internalError)
79+
XCTAssertEqual(RPCError.Code(Status.Code.unavailable), .unavailable)
80+
XCTAssertEqual(RPCError.Code(Status.Code.dataLoss), .dataLoss)
81+
XCTAssertEqual(RPCError.Code(Status.Code.unauthenticated), .unauthenticated)
82+
}
83+
6484
func testEquatableConformance() {
6585
XCTAssertEqual(
6686
RPCError(code: .cancelled, message: ""),

Tests/GRPCCoreTests/StatusTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ final class StatusTests: XCTestCase {
5050
}
5151
}
5252

53+
func testStatusCodeFromErrorCode() throws {
54+
XCTAssertEqual(Status.Code(RPCError.Code.cancelled), .cancelled)
55+
XCTAssertEqual(Status.Code(RPCError.Code.unknown), .unknown)
56+
XCTAssertEqual(Status.Code(RPCError.Code.invalidArgument), .invalidArgument)
57+
XCTAssertEqual(Status.Code(RPCError.Code.deadlineExceeded), .deadlineExceeded)
58+
XCTAssertEqual(Status.Code(RPCError.Code.notFound), .notFound)
59+
XCTAssertEqual(Status.Code(RPCError.Code.alreadyExists), .alreadyExists)
60+
XCTAssertEqual(Status.Code(RPCError.Code.permissionDenied), .permissionDenied)
61+
XCTAssertEqual(Status.Code(RPCError.Code.resourceExhausted), .resourceExhausted)
62+
XCTAssertEqual(Status.Code(RPCError.Code.failedPrecondition), .failedPrecondition)
63+
XCTAssertEqual(Status.Code(RPCError.Code.aborted), .aborted)
64+
XCTAssertEqual(Status.Code(RPCError.Code.outOfRange), .outOfRange)
65+
XCTAssertEqual(Status.Code(RPCError.Code.unimplemented), .unimplemented)
66+
XCTAssertEqual(Status.Code(RPCError.Code.internalError), .internalError)
67+
XCTAssertEqual(Status.Code(RPCError.Code.unavailable), .unavailable)
68+
XCTAssertEqual(Status.Code(RPCError.Code.dataLoss), .dataLoss)
69+
XCTAssertEqual(Status.Code(RPCError.Code.unauthenticated), .unauthenticated)
70+
}
71+
5372
func testStatusCodeFromValidRawValue() {
5473
for (expected, rawValue) in Self.statusCodeRawValue {
5574
XCTAssertEqual(

0 commit comments

Comments
 (0)