Browse Source

Add a runtime support class for client-streaming client calls.

Daniel Alm 8 years ago
parent
commit
a1174b5b94

+ 8 - 82
Examples/Echo/Generated/echo.grpc.swift

@@ -43,110 +43,36 @@ internal protocol Echo_EchoExpandCall: ClientCallServerStreamingBase {
   /// Call this to wait for a result. Blocking.
   func receive() throws -> Echo_EchoResponse
   /// Call this to wait for a result. Nonblocking.
-  func receive(completion:@escaping (Echo_EchoResponse?, ClientError?)->()) throws
+  func receive(completion: @escaping (Echo_EchoResponse?, ClientError?) -> Void) throws
 }
 
 fileprivate final class Echo_EchoExpandCallImpl: ClientCallServerStreamingImpl<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoExpandCall {
   override class var method: String { return "/echo.Echo/Expand" }
 }
 
-/// Simple fake implementation of Echo_EchoExpandCall that returns a previously-defined set of results.
 class Echo_EchoExpandCallTestStub: ClientCallServerStreamingTestStub<Echo_EchoResponse>, Echo_EchoExpandCall {
   override class var method: String { return "/echo.Echo/Expand" }
 }
 
 /// Collect (Client Streaming)
-internal protocol Echo_EchoCollectCall {
+internal protocol Echo_EchoCollectCall: ClientCallClientStreamingBase {
   /// Call this to send each message in the request stream. Nonblocking.
-  func send(_ message:Echo_EchoRequest, errorHandler:@escaping (Error)->()) throws
+  func send(_ message: Echo_EchoRequest, errorHandler: @escaping (Error) -> Void) throws
   
   /// Call this to close the connection and wait for a response. Blocking.
   func closeAndReceive() throws -> Echo_EchoResponse
   /// Call this to close the connection and wait for a response. Nonblocking.
-  func closeAndReceive(completion:@escaping (Echo_EchoResponse?, Echo_EchoClientError?)->()) throws
-  
-  /// Cancel the call.
-  func cancel()
+  func closeAndReceive(completion: @escaping (Echo_EchoResponse?, ClientError?) -> Void) throws
 }
 
-internal extension Echo_EchoCollectCall {
-  func closeAndReceive() throws -> Echo_EchoResponse {
-    var returnError : Echo_EchoClientError?
-    var returnResponse : Echo_EchoResponse!
-    let sem = DispatchSemaphore(value: 0)
-    do {
-      try closeAndReceive() {response, error in
-        returnResponse = response
-        returnError = error
-        sem.signal()
-      }
-      _ = sem.wait(timeout: DispatchTime.distantFuture)
-    } catch (let error) {
-      throw error
-    }
-    if let returnError = returnError {
-      throw returnError
-    }
-    return returnResponse
-  }
-}
-
-fileprivate final class Echo_EchoCollectCallImpl: Echo_EchoCollectCall {
-  private var call : Call
-
-  /// Create a call.
-  init(_ channel: Channel) {
-    self.call = channel.makeCall("/echo.Echo/Collect")
-  }
-
-  /// Call this to start a call. Nonblocking.
-  func start(metadata:Metadata, completion: ((CallResult)->())?)
-    throws -> Echo_EchoCollectCall {
-      try self.call.start(.clientStreaming, metadata:metadata, completion:completion)
-      return self
-  }
-
-  func send(_ message:Echo_EchoRequest, errorHandler:@escaping (Error)->()) throws {
-    let messageData = try message.serializedData()
-    try call.sendMessage(data:messageData, errorHandler:errorHandler)
-  }
-
-  func closeAndReceive(completion:@escaping (Echo_EchoResponse?, Echo_EchoClientError?)->()) throws {
-    do {
-      try call.receiveMessage() {(responseData) in
-        if let responseData = responseData,
-          let response = try? Echo_EchoResponse(serializedData:responseData) {
-          completion(response, nil)
-        } else {
-          completion(nil, Echo_EchoClientError.invalidMessageReceived)
-        }
-      }
-      try call.close(completion:{})
-    } catch (let error) {
-      throw error
-    }
-  }
-
-  func cancel() {
-    call.cancel()
-  }
+fileprivate final class Echo_EchoCollectCallImpl: ClientCallClientStreamingImpl<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoCollectCall {
+  override class var method: String { return "/echo.Echo/Collect" }
 }
 
 /// Simple fake implementation of Echo_EchoCollectCall
 /// stores sent values for later verification and finall returns a previously-defined result.
-class Echo_EchoCollectCallTestStub: Echo_EchoCollectCall {
-  var inputs: [Echo_EchoRequest] = []
-  var output: Echo_EchoResponse?
-
-  func send(_ message:Echo_EchoRequest, errorHandler:@escaping (Error)->()) throws {
-    inputs.append(message)
-  }
-  
-  func closeAndReceive(completion:@escaping (Echo_EchoResponse?, Echo_EchoClientError?)->()) throws {
-    completion(output!, nil)
-  }
-
-  func cancel() { }
+class Echo_EchoCollectCallTestStub: ClientCallClientStreamingTestStub<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoCollectCall {
+  override class var method: String { return "/echo.Echo/Collect" }
 }
 
 /// Update (Bidirectional Streaming)

+ 7 - 80
Plugin/Templates/client-call-clientstreaming.swift

@@ -1,95 +1,22 @@
 /// {{ method|methodDescriptorName }} (Client Streaming)
-{{ access }} protocol {{ .|call:file,service,method }} {
+{{ access }} protocol {{ .|call:file,service,method }}: ClientCallClientStreamingBase {
   /// Call this to send each message in the request stream. Nonblocking.
-  func send(_ message:{{ method|input }}, errorHandler:@escaping (Error)->()) throws
+  func send(_ message: {{ method|input }}, errorHandler: @escaping (Error) -> Void) throws
   
   /// Call this to close the connection and wait for a response. Blocking.
   func closeAndReceive() throws -> {{ method|output }}
   /// Call this to close the connection and wait for a response. Nonblocking.
-  func closeAndReceive(completion:@escaping ({{ method|output }}?, {{ .|clienterror:file,service }}?)->()) throws
-  
-  /// Cancel the call.
-  func cancel()
+  func closeAndReceive(completion: @escaping ({{ method|output }}?, ClientError?) -> Void) throws
 }
 
-{{ access }} extension {{ .|call:file,service,method }} {
-  func closeAndReceive() throws -> {{ method|output }} {
-    var returnError : {{ .|clienterror:file,service }}?
-    var returnResponse : {{ method|output }}!
-    let sem = DispatchSemaphore(value: 0)
-    do {
-      try closeAndReceive() {response, error in
-        returnResponse = response
-        returnError = error
-        sem.signal()
-      }
-      _ = sem.wait(timeout: DispatchTime.distantFuture)
-    } catch (let error) {
-      throw error
-    }
-    if let returnError = returnError {
-      throw returnError
-    }
-    return returnResponse
-  }
-}
-
-fileprivate final class {{ .|call:file,service,method }}Impl: {{ .|call:file,service,method }} {
-  private var call : Call
-
-  /// Create a call.
-  init(_ channel: Channel) {
-    self.call = channel.makeCall("{{ .|path:file,service,method }}")
-  }
-
-  /// Call this to start a call. Nonblocking.
-  func start(metadata:Metadata, completion: ((CallResult)->())?)
-    throws -> {{ .|call:file,service,method }} {
-      try self.call.start(.clientStreaming, metadata:metadata, completion:completion)
-      return self
-  }
-
-  func send(_ message:{{ method|input }}, errorHandler:@escaping (Error)->()) throws {
-    let messageData = try message.serializedData()
-    try call.sendMessage(data:messageData, errorHandler:errorHandler)
-  }
-
-  func closeAndReceive(completion:@escaping ({{ method|output }}?, {{ .|clienterror:file,service }}?)->()) throws {
-    do {
-      try call.receiveMessage() {(responseData) in
-        if let responseData = responseData,
-          let response = try? {{ method|output }}(serializedData:responseData) {
-          completion(response, nil)
-        } else {
-          completion(nil, {{ .|clienterror:file,service }}.invalidMessageReceived)
-        }
-      }
-      try call.close(completion:{})
-    } catch (let error) {
-      throw error
-    }
-  }
-
-  func cancel() {
-    call.cancel()
-  }
+fileprivate final class {{ .|call:file,service,method }}Impl: ClientCallClientStreamingImpl<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} {
+  override class var method: String { return "{{ .|path:file,service,method }}" }
 }
 
 //-{% if generateTestStubs %}
 /// Simple fake implementation of {{ .|call:file,service,method }}
 /// stores sent values for later verification and finall returns a previously-defined result.
-class {{ .|call:file,service,method }}TestStub: {{ .|call:file,service,method }} {
-  var inputs: [{{ method|input }}] = []
-  var output: {{ method|output }}?
-
-  func send(_ message:{{ method|input }}, errorHandler:@escaping (Error)->()) throws {
-    inputs.append(message)
-  }
-  
-  func closeAndReceive(completion:@escaping ({{ method|output }}?, {{ .|clienterror:file,service }}?)->()) throws {
-    completion(output!, nil)
-  }
-
-  func cancel() { }
+class {{ .|call:file,service,method }}TestStub: ClientCallClientStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} {
+  override class var method: String { return "{{ .|path:file,service,method }}" }
 }
 //-{% endif %}

+ 114 - 0
Sources/gRPC/GenCodeSupport/ClientCallClientStreaming.swift

@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * 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
+import Dispatch
+import SwiftProtobuf
+
+public protocol ClientCallClientStreamingBase: class {
+  static var method: String { get }
+  
+  /// Cancel the call.
+  func cancel()
+  
+  // TODO: Move the other, message type-dependent, methods into this protocol. At the moment, this is not possible,
+  // as the protocol would then have an associated type requirement (and become pretty much unusable in the process).
+}
+
+open class ClientCallClientStreamingImpl<InputType: Message, OutputType: Message>: ClientCallClientStreamingBase {
+  open class var method: String { fatalError("needs to be overridden") }
+  
+  private var call: Call
+  
+  /// Create a call.
+  public init(_ channel: Channel) {
+    self.call = channel.makeCall(type(of: self).method)
+  }
+  
+  /// Call this to start a call. Nonblocking.
+  public func start(metadata:Metadata, completion: ((CallResult)->())?) throws -> Self {
+    try self.call.start(.clientStreaming, metadata:metadata, completion:completion)
+    return self
+  }
+  
+  public func send(_ message: InputType, errorHandler:@escaping (Error)->()) throws {
+    let messageData = try message.serializedData()
+    try call.sendMessage(data:messageData, errorHandler:errorHandler)
+  }
+  
+  public func closeAndReceive(completion:@escaping (OutputType?, ClientError?)->()) throws {
+    do {
+      try call.receiveMessage() {(responseData) in
+        if let responseData = responseData,
+          let response = try? OutputType(serializedData:responseData) {
+          completion(response, nil)
+        } else {
+          completion(nil, .invalidMessageReceived)
+        }
+      }
+      try call.close(completion:{})
+    } catch (let error) {
+      throw error
+    }
+  }
+  
+  public func closeAndReceive() throws -> OutputType {
+    var returnError : ClientError?
+    var returnResponse : OutputType!
+    let sem = DispatchSemaphore(value: 0)
+    do {
+      try closeAndReceive() {response, error in
+        returnResponse = response
+        returnError = error
+        sem.signal()
+      }
+      _ = sem.wait(timeout: DispatchTime.distantFuture)
+    } catch (let error) {
+      throw error
+    }
+    if let returnError = returnError {
+      throw returnError
+    }
+    return returnResponse
+  }
+  
+  public func cancel() {
+    call.cancel()
+  }
+}
+
+/// Simple fake implementation of ClientCallClientStreamingBase that
+/// stores sent values for later verification and finally returns a previously-defined result.
+open class ClientCallClientStreamingTestStub<InputType: Message, OutputType: Message>: ClientCallClientStreamingBase {
+  open class var method: String { fatalError("needs to be overridden") }
+  
+  var inputs: [InputType] = []
+  var output: OutputType?
+  
+  public func send(_ message: InputType, errorHandler:@escaping (Error)->()) throws {
+    inputs.append(message)
+  }
+  
+  public func closeAndReceive(completion:@escaping (OutputType?, ClientError?)->()) throws {
+    completion(output!, nil)
+  }
+  
+  public func closeAndReceive() throws -> OutputType {
+    return output!
+  }
+  
+  public func cancel() { }
+}