Browse Source

Add a runtime support class for bidirectional streaming client calls.

Daniel Alm 8 years ago
parent
commit
fcfc5b8f03

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

@@ -54,7 +54,6 @@ class Echo_EchoExpandCallTestStub: ClientCallServerStreamingTestStub<Echo_EchoRe
   override class var method: String { return "/echo.Echo/Expand" }
 }
 
-/// Collect (Client Streaming)
 internal protocol Echo_EchoCollectCall: ClientCallClientStreamingBase {
   /// Call this to send each message in the request stream. Nonblocking.
   func send(_ message: Echo_EchoRequest, errorHandler: @escaping (Error) -> Void) throws
@@ -75,120 +74,27 @@ class Echo_EchoCollectCallTestStub: ClientCallClientStreamingTestStub<Echo_EchoR
   override class var method: String { return "/echo.Echo/Collect" }
 }
 
-/// Update (Bidirectional Streaming)
-internal protocol Echo_EchoUpdateCall {
+internal protocol Echo_EchoUpdateCall: ClientCallBidirectionalStreamingBase {
   /// 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?, Echo_EchoClientError?)->()) throws
+  func receive(completion: @escaping (Echo_EchoResponse?, ClientError?) -> Void) throws
   
   /// Call this to send each message in the request stream.
-  func send(_ message:Echo_EchoRequest, errorHandler:@escaping (Error)->()) throws
+  func send(_ message: Echo_EchoRequest, errorHandler: @escaping (Error) -> Void) throws
   
   /// Call this to close the sending connection. Blocking.
   func closeSend() throws
   /// Call this to close the sending connection. Nonblocking.
-  func closeSend(completion: (()->())?) throws
-  
-  /// Cancel the call.
-  func cancel()
+  func closeSend(completion: (() -> Void)?) throws
 }
 
-internal extension Echo_EchoUpdateCall {
-  func receive() throws -> Echo_EchoResponse {
-    var returnError : Echo_EchoClientError?
-    var returnMessage : Echo_EchoResponse!
-    let sem = DispatchSemaphore(value: 0)
-    do {
-      try receive() {response, error in
-        returnMessage = response
-        returnError = error
-        sem.signal()
-      }
-      _ = sem.wait(timeout: DispatchTime.distantFuture)
-    }
-    if let returnError = returnError {
-      throw returnError
-    }
-    return returnMessage
-  }
-
-  func closeSend() throws {
-    let sem = DispatchSemaphore(value: 0)
-    try closeSend() {
-      sem.signal()
-    }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-  }
-}
-
-fileprivate final class Echo_EchoUpdateCallImpl: Echo_EchoUpdateCall {
-  private var call : Call
-
-  /// Create a call.
-  init(_ channel: Channel) {
-    self.call = channel.makeCall("/echo.Echo/Update")
-  }
-
-  /// Call this to start a call. Nonblocking.
-  func start(metadata:Metadata, completion: ((CallResult)->())?)
-    throws -> Echo_EchoUpdateCall {
-      try self.call.start(.bidiStreaming, metadata:metadata, completion:completion)
-      return self
-  }
-
-  func receive(completion:@escaping (Echo_EchoResponse?, Echo_EchoClientError?)->()) throws {
-    do {
-      try call.receiveMessage() {(data) in
-        if let data = data {
-          if let returnMessage = try? Echo_EchoResponse(serializedData:data) {
-            completion(returnMessage, nil)
-          } else {
-            completion(nil, Echo_EchoClientError.invalidMessageReceived)
-          }
-        } else {
-          completion(nil, Echo_EchoClientError.endOfStream)
-        }
-      }
-    }
-  }
-
-  func send(_ message:Echo_EchoRequest, errorHandler:@escaping (Error)->()) throws {
-    let messageData = try message.serializedData()
-    try call.sendMessage(data:messageData, errorHandler:errorHandler)
-  }
-
-  func closeSend(completion: (()->())?) throws {
-  	try call.close(completion: completion)
-  }
-
-  func cancel() {
-    call.cancel()
-  }
+fileprivate final class Echo_EchoUpdateCallImpl: ClientCallBidirectionalStreamingImpl<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoUpdateCall {
+  override class var method: String { return "/echo.Echo/Update" }
 }
 
-/// Simple fake implementation of Echo_EchoUpdateCall that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class Echo_EchoUpdateCallTestStub: Echo_EchoUpdateCall {
-  var inputs: [Echo_EchoRequest] = []
-  var outputs: [Echo_EchoResponse] = []
-  
-  func receive(completion:@escaping (Echo_EchoResponse?, Echo_EchoClientError?)->()) throws {
-    if let output = outputs.first {
-      outputs.removeFirst()
-      completion(output, nil)
-    } else {
-      completion(nil, Echo_EchoClientError.endOfStream)
-    }
-  }
-
-  func send(_ message:Echo_EchoRequest, errorHandler:@escaping (Error)->()) throws {
-    inputs.append(message)
-  }
-
-  func closeSend(completion: (()->())?) throws { completion?() }
-
-  func cancel() { }
+class Echo_EchoUpdateCallTestStub: ClientCallBidirectionalStreamingTestStub<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoUpdateCall {
+  override class var method: String { return "/echo.Echo/Update" }
 }
 
 

+ 8 - 101
Plugin/Templates/client-call-bidistreaming.swift

@@ -1,117 +1,24 @@
-/// {{ method|methodDescriptorName }} (Bidirectional Streaming)
-{{ access }} protocol {{ .|call:file,service,method }} {
+{{ access }} protocol {{ .|call:file,service,method }}: ClientCallBidirectionalStreamingBase {
   /// Call this to wait for a result. Blocking.
   func receive() throws -> {{ method|output }}
   /// Call this to wait for a result. Nonblocking.
-  func receive(completion:@escaping ({{ method|output }}?, {{ .|clienterror:file,service }}?)->()) throws
+  func receive(completion: @escaping ({{ method|output }}?, ClientError?) -> Void) throws
   
   /// Call this to send each message in the request stream.
-  func send(_ message:{{ method|input }}, errorHandler:@escaping (Error)->()) throws
+  func send(_ message: {{ method|input }}, errorHandler: @escaping (Error) -> Void) throws
   
   /// Call this to close the sending connection. Blocking.
   func closeSend() throws
   /// Call this to close the sending connection. Nonblocking.
-  func closeSend(completion: (()->())?) throws
-  
-  /// Cancel the call.
-  func cancel()
-}
-
-{{ access }} extension {{ .|call:file,service,method }} {
-  func receive() throws -> {{ method|output }} {
-    var returnError : {{ .|clienterror:file,service }}?
-    var returnMessage : {{ method|output }}!
-    let sem = DispatchSemaphore(value: 0)
-    do {
-      try receive() {response, error in
-        returnMessage = response
-        returnError = error
-        sem.signal()
-      }
-      _ = sem.wait(timeout: DispatchTime.distantFuture)
-    }
-    if let returnError = returnError {
-      throw returnError
-    }
-    return returnMessage
-  }
-
-  func closeSend() throws {
-    let sem = DispatchSemaphore(value: 0)
-    try closeSend() {
-      sem.signal()
-    }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-  }
+  func closeSend(completion: (() -> Void)?) throws
 }
 
-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(.bidiStreaming, metadata:metadata, completion:completion)
-      return self
-  }
-
-  func receive(completion:@escaping ({{ method|output }}?, {{ .|clienterror:file,service }}?)->()) throws {
-    do {
-      try call.receiveMessage() {(data) in
-        if let data = data {
-          if let returnMessage = try? {{ method|output }}(serializedData:data) {
-            completion(returnMessage, nil)
-          } else {
-            completion(nil, {{ .|clienterror:file,service }}.invalidMessageReceived)
-          }
-        } else {
-          completion(nil, {{ .|clienterror:file,service }}.endOfStream)
-        }
-      }
-    }
-  }
-
-  func send(_ message:{{ method|input }}, errorHandler:@escaping (Error)->()) throws {
-    let messageData = try message.serializedData()
-    try call.sendMessage(data:messageData, errorHandler:errorHandler)
-  }
-
-  func closeSend(completion: (()->())?) throws {
-  	try call.close(completion: completion)
-  }
-
-  func cancel() {
-    call.cancel()
-  }
+fileprivate final class {{ .|call:file,service,method }}Impl: ClientCallBidirectionalStreamingImpl<{{ 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 }} that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class {{ .|call:file,service,method }}TestStub: {{ .|call:file,service,method }} {
-  var inputs: [{{ method|input }}] = []
-  var outputs: [{{ method|output }}] = []
-  
-  func receive(completion:@escaping ({{ method|output }}?, {{ .|clienterror:file,service }}?)->()) throws {
-    if let output = outputs.first {
-      outputs.removeFirst()
-      completion(output, nil)
-    } else {
-      completion(nil, {{ .|clienterror:file,service }}.endOfStream)
-    }
-  }
-
-  func send(_ message:{{ method|input }}, errorHandler:@escaping (Error)->()) throws {
-    inputs.append(message)
-  }
-
-  func closeSend(completion: (()->())?) throws { completion?() }
-
-  func cancel() { }
+class {{ .|call:file,service,method }}TestStub: ClientCallBidirectionalStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} {
+  override class var method: String { return "{{ .|path:file,service,method }}" }
 }
 //-{% endif %}

+ 0 - 1
Plugin/Templates/client-call-clientstreaming.swift

@@ -1,4 +1,3 @@
-/// {{ method|methodDescriptorName }} (Client Streaming)
 {{ 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) -> Void) throws

+ 136 - 0
Sources/gRPC/GenCodeSupport/ClientCallBidirectionalStreaming.swift

@@ -0,0 +1,136 @@
+/*
+ * 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 ClientCallBidirectionalStreamingBase: class {
+  static var method: String { get }
+  
+  // 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 ClientCallBidirectionalStreamingImpl<InputType: Message, OutputType: Message>: ClientCallBidirectionalStreamingBase {
+  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(.bidiStreaming, metadata:metadata, completion:completion)
+      return self
+  }
+  
+  public func receive(completion:@escaping (OutputType?, ClientError?)->()) throws {
+    do {
+      try call.receiveMessage() {(data) in
+        if let data = data {
+          if let returnMessage = try? OutputType(serializedData:data) {
+            completion(returnMessage, nil)
+          } else {
+            completion(nil, .invalidMessageReceived)
+          }
+        } else {
+          completion(nil, .endOfStream)
+        }
+      }
+    }
+  }
+  
+  public func receive() throws -> OutputType {
+    var returnError : ClientError?
+    var returnMessage : OutputType!
+    let sem = DispatchSemaphore(value: 0)
+    do {
+      try receive() {response, error in
+        returnMessage = response
+        returnError = error
+        sem.signal()
+      }
+      _ = sem.wait(timeout: DispatchTime.distantFuture)
+    }
+    if let returnError = returnError {
+      throw returnError
+    }
+    return returnMessage
+  }
+  
+  public func send(_ message:InputType, errorHandler:@escaping (Error)->()) throws {
+    let messageData = try message.serializedData()
+    try call.sendMessage(data:messageData, errorHandler:errorHandler)
+  }
+  
+  public func closeSend(completion: (()->())?) throws {
+    try call.close(completion: completion)
+  }
+  
+  public func closeSend() throws {
+    let sem = DispatchSemaphore(value: 0)
+    try closeSend() {
+      sem.signal()
+    }
+    _ = sem.wait(timeout: DispatchTime.distantFuture)
+  }
+  
+  public func cancel() {
+    call.cancel()
+  }
+}
+
+/// Simple fake implementation of ClientCallBidirectionalStreamingBase that returns a previously-defined set of results
+/// and stores sent values for later verification.
+open class ClientCallBidirectionalStreamingTestStub<InputType: Message, OutputType: Message>: ClientCallBidirectionalStreamingBase {
+  open class var method: String { fatalError("needs to be overridden") }
+  
+  open var inputs: [InputType] = []
+  open var outputs: [OutputType] = []
+  
+  open func receive(completion:@escaping (OutputType?, ClientError?)->()) throws {
+    if let output = outputs.first {
+      outputs.removeFirst()
+      completion(output, nil)
+    } else {
+      completion(nil, .endOfStream)
+    }
+  }
+  
+  open func receive() throws -> OutputType {
+    if let output = outputs.first {
+      outputs.removeFirst()
+      return output
+    } else {
+      throw ClientError.endOfStream
+    }
+  }
+  
+  open func send(_ message: InputType, errorHandler:@escaping (Error)->()) throws {
+    inputs.append(message)
+  }
+  
+  open func closeSend(completion: (()->())?) throws { completion?() }
+  
+  open func closeSend() throws { }
+  
+  open func cancel() { }
+}