Skip to content

Commit ee99455

Browse files
committed
Ensure that errors in client calls are always provided to the user.
The expected behavior is now for the client call's completion handler to be called when the whole call is completed. You can check that completion handler's `statusCode` for the call's result. The intermediate send/receive call completion handlers will also get called with an error, but that error's status code is most likely to be `.unknown` (because send/receive calls don't receive the final status from the server). In addition, `CallResult` now also includes the operation group's success flag, but that flag is much less important now. This includes: - Make most completion handlers non-throwing (their errors were for the most part discarded, anyway) - Extract Receive-Streaming into a common protocol - Changing many completion handler interfaces to be called with a `CallResult` instead of `Data?` and `ResultOrRPCError<ReceivedType?>` instead of `(ReceivedType, Error)` - Not throwing on end of stream, but passing `nil` to the completion handler instead - Making `ServerSessionUnary` always return an error when one of its calls fail - Adding test to ensure the proper error behavior.
1 parent 263e66b commit ee99455

27 files changed

+560
-434
lines changed

.gitignore

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ xcuserdata
55
protoc-gen-swift
66
protoc-gen-swiftgrpc
77
third_party/**
8-
Plugin/Packages/**
9-
Plugin/Sources/protoc-gen-swiftgrpc/templates.swift
10-
Plugin/protoc-*
11-
Plugin/swiftgrpc.log
12-
Plugin/echo.*.swift
138
Echo
149
test.out
1510
echo.pid

Sources/Examples/Echo/EchoProvider.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ class EchoProvider: Echo_EchoProvider {
4545
var parts: [String] = []
4646
while true {
4747
do {
48-
let request = try session.receive()
48+
guard let request = try session.receive()
49+
else { break } // End of stream
4950
parts.append(request.text)
50-
} catch ServerError.endOfStream {
51-
break
5251
} catch (let error) {
5352
print("\(error)")
5453
}
@@ -63,7 +62,8 @@ class EchoProvider: Echo_EchoProvider {
6362
var count = 0
6463
while true {
6564
do {
66-
let request = try session.receive()
65+
guard let request = try session.receive()
66+
else { break } // End of stream
6767
var response = Echo_EchoResponse()
6868
response.text = "Swift echo update (\(count)): \(request.text)"
6969
count += 1
@@ -72,8 +72,6 @@ class EchoProvider: Echo_EchoProvider {
7272
print("update error: \(error)")
7373
}
7474
}
75-
} catch ServerError.endOfStream {
76-
break
7775
} catch (let error) {
7876
print("\(error)")
7977
break

Sources/Examples/Echo/Generated/echo.grpc.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ fileprivate final class Echo_EchoGetCallBase: ClientCallUnaryBase<Echo_EchoReque
3333

3434
internal protocol Echo_EchoExpandCall: ClientCallServerStreaming {
3535
/// Call this to wait for a result. Blocking.
36-
func receive() throws -> Echo_EchoResponse
36+
func receive() throws -> Echo_EchoResponse?
3737
/// Call this to wait for a result. Nonblocking.
38-
func receive(completion: @escaping (Echo_EchoResponse?, ClientError?) -> Void) throws
38+
func receive(completion: @escaping (ResultOrRPCError<Echo_EchoResponse?>) -> Void) throws
3939
}
4040

4141
fileprivate final class Echo_EchoExpandCallBase: ClientCallServerStreamingBase<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoExpandCall {
@@ -53,7 +53,7 @@ internal protocol Echo_EchoCollectCall: ClientCallClientStreaming {
5353
/// Call this to close the connection and wait for a response. Blocking.
5454
func closeAndReceive() throws -> Echo_EchoResponse
5555
/// Call this to close the connection and wait for a response. Nonblocking.
56-
func closeAndReceive(completion: @escaping (Echo_EchoResponse?, ClientError?) -> Void) throws
56+
func closeAndReceive(completion: @escaping (ResultOrRPCError<Echo_EchoResponse>) -> Void) throws
5757
}
5858

5959
fileprivate final class Echo_EchoCollectCallBase: ClientCallClientStreamingBase<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoCollectCall {
@@ -68,9 +68,9 @@ class Echo_EchoCollectCallTestStub: ClientCallClientStreamingTestStub<Echo_EchoR
6868

6969
internal protocol Echo_EchoUpdateCall: ClientCallBidirectionalStreaming {
7070
/// Call this to wait for a result. Blocking.
71-
func receive() throws -> Echo_EchoResponse
71+
func receive() throws -> Echo_EchoResponse?
7272
/// Call this to wait for a result. Nonblocking.
73-
func receive(completion: @escaping (Echo_EchoResponse?, ClientError?) -> Void) throws
73+
func receive(completion: @escaping (ResultOrRPCError<Echo_EchoResponse?>) -> Void) throws
7474

7575
/// Call this to send each message in the request stream.
7676
func send(_ message: Echo_EchoRequest, completion: @escaping (Error?) -> Void) throws
@@ -210,8 +210,10 @@ fileprivate final class Echo_EchoExpandSessionBase: ServerSessionServerStreaming
210210
class Echo_EchoExpandSessionTestStub: ServerSessionServerStreamingTestStub<Echo_EchoResponse>, Echo_EchoExpandSession {}
211211

212212
internal protocol Echo_EchoCollectSession: ServerSessionClientStreaming {
213-
/// Receive a message. Blocks until a message is received or the client closes the connection.
214-
func receive() throws -> Echo_EchoRequest
213+
/// Call this to wait for a result. Blocking.
214+
func receive() throws -> Echo_EchoRequest?
215+
/// Call this to wait for a result. Nonblocking.
216+
func receive(completion: @escaping (ResultOrRPCError<Echo_EchoRequest?>) -> Void) throws
215217

216218
/// Send a response and close the connection.
217219
func sendAndClose(_ response: Echo_EchoResponse) throws
@@ -222,8 +224,10 @@ fileprivate final class Echo_EchoCollectSessionBase: ServerSessionClientStreamin
222224
class Echo_EchoCollectSessionTestStub: ServerSessionClientStreamingTestStub<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoCollectSession {}
223225

224226
internal protocol Echo_EchoUpdateSession: ServerSessionBidirectionalStreaming {
225-
/// Receive a message. Blocks until a message is received or the client closes the connection.
226-
func receive() throws -> Echo_EchoRequest
227+
/// Call this to wait for a result. Blocking.
228+
func receive() throws -> Echo_EchoRequest?
229+
/// Call this to wait for a result. Nonblocking.
230+
func receive(completion: @escaping (ResultOrRPCError<Echo_EchoRequest?>) -> Void) throws
227231

228232
/// Send a message. Nonblocking.
229233
func send(_ response: Echo_EchoResponse, completion: ((Error?) -> Void)?) throws

Sources/Examples/Echo/main.swift

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,13 @@ Group {
109109
callResult = result
110110
sem.signal()
111111
}
112-
var running = true
113-
while running {
114-
do {
115-
let responseMessage = try expandCall.receive()
116-
print("expand received: \(responseMessage.text)")
117-
} catch ClientError.endOfStream {
118-
running = false
119-
}
112+
while true {
113+
guard let responseMessage = try expandCall.receive()
114+
else { break } // End of stream
115+
print("expand received: \(responseMessage.text)")
120116
}
121117
_ = sem.wait()
118+
122119
if let statusCode = callResult?.statusCode {
123120
print("expand completed with code \(statusCode)")
124121
}
@@ -150,6 +147,7 @@ Group {
150147
let responseMessage = try collectCall.closeAndReceive()
151148
print("collect received: \(responseMessage.text)")
152149
_ = sem.wait()
150+
153151
if let statusCode = callResult?.statusCode {
154152
print("collect completed with code \(statusCode)")
155153
}
@@ -182,17 +180,10 @@ Group {
182180
try updateCall.closeSend()
183181

184182
while true {
185-
do {
186-
let responseMessage = try updateCall.receive()
187-
print("update received: \(responseMessage.text)")
188-
} catch ClientError.endOfStream {
189-
break
190-
} catch (let error) {
191-
print("update receive error: \(error)")
192-
break
193-
}
183+
guard let responseMessage = try updateCall.receive()
184+
else { break } // End of stream
185+
print("update received: \(responseMessage.text)")
194186
}
195-
196187
_ = sem.wait()
197188

198189
if let statusCode = callResult?.statusCode {

Sources/SwiftGRPC/Core/Call.swift

Lines changed: 16 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -30,113 +30,6 @@ public enum CallWarning: Error {
3030
case blocked
3131
}
3232

33-
public enum CallError: Error {
34-
case ok
35-
case unknown
36-
case notOnServer
37-
case notOnClient
38-
case alreadyAccepted
39-
case alreadyInvoked
40-
case notInvoked
41-
case alreadyFinished
42-
case tooManyOperations
43-
case invalidFlags
44-
case invalidMetadata
45-
case invalidMessage
46-
case notServerCompletionQueue
47-
case batchTooBig
48-
case payloadTypeMismatch
49-
50-
static func callError(grpcCallError error: grpc_call_error) -> CallError {
51-
switch error {
52-
case GRPC_CALL_OK:
53-
return .ok
54-
case GRPC_CALL_ERROR:
55-
return .unknown
56-
case GRPC_CALL_ERROR_NOT_ON_SERVER:
57-
return .notOnServer
58-
case GRPC_CALL_ERROR_NOT_ON_CLIENT:
59-
return .notOnClient
60-
case GRPC_CALL_ERROR_ALREADY_ACCEPTED:
61-
return .alreadyAccepted
62-
case GRPC_CALL_ERROR_ALREADY_INVOKED:
63-
return .alreadyInvoked
64-
case GRPC_CALL_ERROR_NOT_INVOKED:
65-
return .notInvoked
66-
case GRPC_CALL_ERROR_ALREADY_FINISHED:
67-
return .alreadyFinished
68-
case GRPC_CALL_ERROR_TOO_MANY_OPERATIONS:
69-
return .tooManyOperations
70-
case GRPC_CALL_ERROR_INVALID_FLAGS:
71-
return .invalidFlags
72-
case GRPC_CALL_ERROR_INVALID_METADATA:
73-
return .invalidMetadata
74-
case GRPC_CALL_ERROR_INVALID_MESSAGE:
75-
return .invalidMessage
76-
case GRPC_CALL_ERROR_NOT_SERVER_COMPLETION_QUEUE:
77-
return .notServerCompletionQueue
78-
case GRPC_CALL_ERROR_BATCH_TOO_BIG:
79-
return .batchTooBig
80-
case GRPC_CALL_ERROR_PAYLOAD_TYPE_MISMATCH:
81-
return .payloadTypeMismatch
82-
default:
83-
return .unknown
84-
}
85-
}
86-
}
87-
88-
public struct CallResult: CustomStringConvertible {
89-
public let statusCode: StatusCode
90-
public let statusMessage: String?
91-
public let resultData: Data?
92-
public let initialMetadata: Metadata?
93-
public let trailingMetadata: Metadata?
94-
95-
fileprivate init(_ op: OperationGroup) {
96-
if op.success {
97-
if let statusCodeRawValue = op.receivedStatusCode() {
98-
if let statusCode = StatusCode(rawValue: statusCodeRawValue) {
99-
self.statusCode = statusCode
100-
} else {
101-
statusCode = .unknown
102-
}
103-
} else {
104-
statusCode = .ok
105-
}
106-
statusMessage = op.receivedStatusMessage()
107-
resultData = op.receivedMessage()?.data()
108-
initialMetadata = op.receivedInitialMetadata()
109-
trailingMetadata = op.receivedTrailingMetadata()
110-
} else {
111-
statusCode = .unknown
112-
statusMessage = nil
113-
resultData = nil
114-
initialMetadata = nil
115-
trailingMetadata = nil
116-
}
117-
}
118-
119-
public var description: String {
120-
var result = "status \(statusCode)"
121-
if let statusMessage = self.statusMessage {
122-
result += ": " + statusMessage
123-
}
124-
if let resultData = self.resultData {
125-
result += "\n"
126-
result += resultData.description
127-
}
128-
if let initialMetadata = self.initialMetadata {
129-
result += "\n"
130-
result += initialMetadata.description
131-
}
132-
if let trailingMetadata = self.trailingMetadata {
133-
result += "\n"
134-
result += trailingMetadata.description
135-
}
136-
return result
137-
}
138-
}
139-
14033
/// A gRPC API call
14134
public class Call {
14235
/// Shared mutex for synchronizing calls to cgrpc_call_perform()
@@ -240,11 +133,18 @@ public class Call {
240133
.receiveStatusOnClient,
241134
]
242135
case .clientStreaming, .bidiStreaming:
243-
operations = [
244-
.sendInitialMetadata(metadata.copy()),
245-
.receiveInitialMetadata,
246-
.receiveStatusOnClient,
247-
]
136+
try perform(OperationGroup(call: self,
137+
operations: [
138+
.sendInitialMetadata(metadata.copy()),
139+
.receiveInitialMetadata
140+
],
141+
completion: nil))
142+
try perform(OperationGroup(call: self,
143+
operations: [.receiveStatusOnClient],
144+
completion: completion != nil
145+
? { op in completion?(CallResult(op)) }
146+
: nil))
147+
return
248148
}
249149
try perform(OperationGroup(call: self,
250150
operations: operations,
@@ -304,27 +204,17 @@ public class Call {
304204

305205
// Receive a message over a streaming connection.
306206
/// - Throws: `CallError` if fails to call.
307-
public func closeAndReceiveMessage(completion: @escaping (Data?) throws -> Void) throws {
207+
public func closeAndReceiveMessage(completion: @escaping (CallResult) -> Void) throws {
308208
try perform(OperationGroup(call: self, operations: [.sendCloseFromClient, .receiveMessage]) { operationGroup in
309-
if operationGroup.success {
310-
if let messageBuffer = operationGroup.receivedMessage() {
311-
try completion(messageBuffer.data())
312-
} else {
313-
try completion(nil) // an empty response signals the end of a connection
314-
}
315-
}
209+
completion(CallResult(operationGroup))
316210
})
317211
}
318212

319213
// Receive a message over a streaming connection.
320214
/// - Throws: `CallError` if fails to call.
321-
public func receiveMessage(completion: @escaping (Data?) throws -> Void) throws {
215+
public func receiveMessage(completion: @escaping (CallResult) -> Void) throws {
322216
try perform(OperationGroup(call: self, operations: [.receiveMessage]) { operationGroup in
323-
if operationGroup.success {
324-
try completion(operationGroup.receivedMessage()?.data())
325-
} else {
326-
try completion(nil)
327-
}
217+
completion(CallResult(operationGroup))
328218
})
329219
}
330220

0 commit comments

Comments
 (0)