Browse Source

Move the server-side generated code into runtime support classes as well.

Daniel Alm 7 years ago
parent
commit
489832fe10

+ 3 - 2
Examples/Echo/EchoProvider.swift

@@ -15,6 +15,7 @@
  */
 import Dispatch
 import Foundation
+import gRPC
 
 class EchoProvider: Echo_EchoProvider {
   // get returns requests as they were received.
@@ -46,7 +47,7 @@ class EchoProvider: Echo_EchoProvider {
       do {
         let request = try session.receive()
         parts.append(request.text)
-      } catch Echo_EchoServerError.endOfStream {
+      } catch ServerError.endOfStream {
         break
       } catch (let error) {
         print("\(error)")
@@ -69,7 +70,7 @@ class EchoProvider: Echo_EchoProvider {
         let sem = DispatchSemaphore(value: 0)
         try session.send(response) { _ in sem.signal() }
         _ = sem.wait(timeout: DispatchTime.distantFuture)
-      } catch Echo_EchoServerError.endOfStream {
+      } catch ServerError.endOfStream {
         break
       } catch (let error) {
         print("\(error)")

+ 30 - 270
Examples/Echo/Generated/echo.grpc.swift

@@ -268,12 +268,6 @@ class Echo_EchoServiceTestStub: Echo_EchoService {
 }
 
 
-
-/// Type for errors thrown from generated server code.
-internal enum Echo_EchoServerError : Error {
-  case endOfStream
-}
-
 /// To build a server, implement a class that conforms to this protocol.
 internal protocol Echo_EchoProvider {
   func get(request : Echo_EchoRequest, session : Echo_EchoGetSession) throws -> Echo_EchoResponse
@@ -282,129 +276,22 @@ internal protocol Echo_EchoProvider {
   func update(session : Echo_EchoUpdateSession) throws
 }
 
-/// Common properties available in each service session.
-internal protocol Echo_EchoSession {
-  var requestMetadata : Metadata { get }
-
-  var statusCode : StatusCode { get }
-  var statusMessage : String { get }
-  var initialMetadata : Metadata { get }
-  var trailingMetadata : Metadata { get }
-}
-
-fileprivate class Echo_EchoSessionImpl: Echo_EchoSession {
-  var handler : Handler
-  var requestMetadata : Metadata { return handler.requestMetadata }
-
-  var statusCode : StatusCode = .ok
-  var statusMessage : String = "OK"
-  var initialMetadata : Metadata = Metadata()
-  var trailingMetadata : Metadata = Metadata()
-
-  init(handler:Handler) {
-    self.handler = handler
-  }
-}
-
-class Echo_EchoSessionTestStub: Echo_EchoSession {
-  var requestMetadata = Metadata()
-
-  var statusCode = StatusCode.ok
-  var statusMessage = "OK"
-  var initialMetadata = Metadata()
-  var trailingMetadata = Metadata()
-}
-
-// Get (Unary Streaming)
-internal protocol Echo_EchoGetSession : Echo_EchoSession { }
+internal protocol Echo_EchoGetSession: ServerSessionUnary { }
 
-fileprivate final class Echo_EchoGetSessionImpl : Echo_EchoSessionImpl, Echo_EchoGetSession {
-  private var provider : Echo_EchoProvider
+fileprivate final class Echo_EchoGetSessionImpl: ServerSessionUnaryImpl<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoGetSession { }
 
-  /// Create a session.
-  init(handler:Handler, provider: Echo_EchoProvider) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
-
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try handler.receiveMessage(initialMetadata:initialMetadata) {(requestData) in
-      if let requestData = requestData {
-        let requestMessage = try Echo_EchoRequest(serializedData:requestData)
-        let replyMessage = try self.provider.get(request:requestMessage, session: self)
-        try self.handler.sendResponse(message:replyMessage.serializedData(),
-                                      statusCode:self.statusCode,
-                                      statusMessage:self.statusMessage,
-                                      trailingMetadata:self.trailingMetadata)
-      }
-    }
-  }
-}
+class Echo_EchoGetSessionTestStub: ServerSessionUnaryTestStub, Echo_EchoGetSession { }
 
-/// Trivial fake implementation of Echo_EchoGetSession.
-class Echo_EchoGetSessionTestStub : Echo_EchoSessionTestStub, Echo_EchoGetSession { }
-
-// Expand (Server Streaming)
-internal protocol Echo_EchoExpandSession : Echo_EchoSession {
+internal protocol Echo_EchoExpandSession: ServerSessionServerStreaming {
   /// Send a message. Nonblocking.
-  func send(_ response: Echo_EchoResponse, completion: ((Bool)->())?) throws
+  func send(_ response: Echo_EchoResponse, completion: ((Bool) -> Void)?) throws
 }
 
-fileprivate final class Echo_EchoExpandSessionImpl : Echo_EchoSessionImpl, Echo_EchoExpandSession {
-  private var provider : Echo_EchoProvider
-
-  /// Create a session.
-  init(handler:Handler, provider: Echo_EchoProvider) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
+fileprivate final class Echo_EchoExpandSessionImpl: ServerSessionServerStreamingImpl<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoExpandSession { }
 
-  func send(_ response: Echo_EchoResponse, completion: ((Bool)->())?) throws {
-    try handler.sendResponse(message:response.serializedData(), completion: completion)
-  }
+class Echo_EchoExpandSessionTestStub: ServerSessionServerStreamingTestStub<Echo_EchoResponse>, Echo_EchoExpandSession { }
 
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try self.handler.receiveMessage(initialMetadata:initialMetadata) {(requestData) in
-      if let requestData = requestData {
-        do {
-          let requestMessage = try Echo_EchoRequest(serializedData:requestData)
-          // to keep providers from blocking the server thread,
-          // we dispatch them to another queue.
-          queue.async {
-            do {
-              try self.provider.expand(request:requestMessage, session: self)
-              try self.handler.sendStatus(statusCode:self.statusCode,
-                                          statusMessage:self.statusMessage,
-                                          trailingMetadata:self.trailingMetadata,
-                                          completion:nil)
-            } catch (let error) {
-              print("error: \(error)")
-            }
-          }
-        } catch (let error) {
-          print("error: \(error)")
-        }
-      }
-    }
-  }
-}
-
-/// Simple fake implementation of Echo_EchoExpandSession that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class Echo_EchoExpandSessionTestStub : Echo_EchoSessionTestStub, Echo_EchoExpandSession {
-  var outputs: [Echo_EchoResponse] = []
-
-  func send(_ response: Echo_EchoResponse, completion: ((Bool)->())?) throws {
-    outputs.append(response)
-  }
-
-  func close() throws { }
-}
-
-// Collect (Client Streaming)
-internal protocol Echo_EchoCollectSession : Echo_EchoSession {
+internal protocol Echo_EchoCollectSession: ServerSessionClientStreaming {
   /// Receive a message. Blocks until a message is received or the client closes the connection.
   func receive() throws -> Echo_EchoRequest
 
@@ -412,163 +299,24 @@ internal protocol Echo_EchoCollectSession : Echo_EchoSession {
   func sendAndClose(_ response: Echo_EchoResponse) throws
 }
 
-fileprivate final class Echo_EchoCollectSessionImpl : Echo_EchoSessionImpl, Echo_EchoCollectSession {
-  private var provider : Echo_EchoProvider
-
-  /// Create a session.
-  init(handler:Handler, provider: Echo_EchoProvider) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
-
-  func receive() throws -> Echo_EchoRequest {
-    let sem = DispatchSemaphore(value: 0)
-    var requestMessage : Echo_EchoRequest?
-    try self.handler.receiveMessage() {(requestData) in
-      if let requestData = requestData {
-        requestMessage = try? Echo_EchoRequest(serializedData:requestData)
-      }
-      sem.signal()
-    }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-    if requestMessage == nil {
-      throw Echo_EchoServerError.endOfStream
-    }
-    return requestMessage!
-  }
-
-  func sendAndClose(_ response: Echo_EchoResponse) throws {
-    try self.handler.sendResponse(message:response.serializedData(),
-                                  statusCode:self.statusCode,
-                                  statusMessage:self.statusMessage,
-                                  trailingMetadata:self.trailingMetadata)
-  }
-
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try self.handler.sendMetadata(initialMetadata:initialMetadata) { _ in
-      queue.async {
-        do {
-          try self.provider.collect(session:self)
-        } catch (let error) {
-          print("error \(error)")
-        }
-      }
-    }
-  }
-}
-
-/// Simple fake implementation of Echo_EchoCollectSession that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class Echo_EchoCollectSessionTestStub: Echo_EchoSessionTestStub, Echo_EchoCollectSession {
-  var inputs: [Echo_EchoRequest] = []
-  var output: Echo_EchoResponse?
-
-  func receive() throws -> Echo_EchoRequest {
-    if let input = inputs.first {
-      inputs.removeFirst()
-      return input
-    } else {
-      throw Echo_EchoClientError.endOfStream
-    }
-  }
+fileprivate final class Echo_EchoCollectSessionImpl: ServerSessionClientStreamingImpl<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoCollectSession { }
 
-  func sendAndClose(_ response: Echo_EchoResponse) throws {
-    output = response
-  }
+class Echo_EchoCollectSessionTestStub: ServerSessionClientStreamingTestStub<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoCollectSession { }
 
-  func close() throws { }
-}
-
-// Update (Bidirectional Streaming)
-internal protocol Echo_EchoUpdateSession : Echo_EchoSession {
+internal protocol Echo_EchoUpdateSession: ServerSessionBidirectionalStreaming {
   /// Receive a message. Blocks until a message is received or the client closes the connection.
   func receive() throws -> Echo_EchoRequest
 
   /// Send a message. Nonblocking.
-  func send(_ response: Echo_EchoResponse, completion: ((Bool)->())?) throws
+  func send(_ response: Echo_EchoResponse, completion: ((Bool) -> Void)?) throws
   
   /// Close a connection. Blocks until the connection is closed.
   func close() throws
 }
 
-fileprivate final class Echo_EchoUpdateSessionImpl : Echo_EchoSessionImpl, Echo_EchoUpdateSession {
-  private var provider : Echo_EchoProvider
+fileprivate final class Echo_EchoUpdateSessionImpl: ServerSessionBidirectionalStreamingImpl<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoUpdateSession { }
 
-  /// Create a session.
-  init(handler:Handler, provider: Echo_EchoProvider) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
-
-  func receive() throws -> Echo_EchoRequest {
-    let sem = DispatchSemaphore(value: 0)
-    var requestMessage : Echo_EchoRequest?
-    try self.handler.receiveMessage() {(requestData) in
-      if let requestData = requestData {
-        do {
-          requestMessage = try Echo_EchoRequest(serializedData:requestData)
-        } catch (let error) {
-          print("error \(error)")
-        }
-      }
-      sem.signal()
-    }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-    if let requestMessage = requestMessage {
-      return requestMessage
-    } else {
-      throw Echo_EchoServerError.endOfStream
-    }
-  }
-
-  func send(_ response: Echo_EchoResponse, completion: ((Bool)->())?) throws {
-    try handler.sendResponse(message:response.serializedData(), completion: completion)
-  }
-
-  func close() throws {
-    let sem = DispatchSemaphore(value: 0)
-    try self.handler.sendStatus(statusCode:self.statusCode,
-                                statusMessage:self.statusMessage,
-                                trailingMetadata:self.trailingMetadata) { _ in sem.signal() }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-  }
-
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try self.handler.sendMetadata(initialMetadata:initialMetadata) { _ in
-      queue.async {
-        do {
-          try self.provider.update(session:self)
-        } catch (let error) {
-          print("error \(error)")
-        }
-      }
-    }
-  }
-}
-
-/// Simple fake implementation of Echo_EchoUpdateSession that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class Echo_EchoUpdateSessionTestStub : Echo_EchoSessionTestStub, Echo_EchoUpdateSession {
-  var inputs: [Echo_EchoRequest] = []
-  var outputs: [Echo_EchoResponse] = []
-
-  func receive() throws -> Echo_EchoRequest {
-    if let input = inputs.first {
-      inputs.removeFirst()
-      return input
-    } else {
-      throw Echo_EchoClientError.endOfStream
-    }
-  }
-
-  func send(_ response: Echo_EchoResponse, completion: ((Bool)->())?) throws {
-    outputs.append(response)
-  }
-
-  func close() throws { }
-}
+class Echo_EchoUpdateSessionTestStub: ServerSessionBidirectionalStreamingTestStub<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoUpdateSession { }
 
 
 /// Main server for generated service
@@ -620,13 +368,25 @@ internal final class Echo_EchoServer {
       do {
         switch unwrappedMethod {
         case "/echo.Echo/Get":
-          try Echo_EchoGetSessionImpl(handler:handler, provider:provider).run(queue:queue)
+          try Echo_EchoGetSessionImpl(
+            handler: handler,
+            providerBlock: { try provider.get(request: $0, session: $1 as! Echo_EchoGetSessionImpl) })
+              .run(queue:queue)
         case "/echo.Echo/Expand":
-          try Echo_EchoExpandSessionImpl(handler:handler, provider:provider).run(queue:queue)
+          try Echo_EchoExpandSessionImpl(
+            handler: handler,
+            providerBlock: { try provider.expand(request: $0, session: $1 as! Echo_EchoExpandSessionImpl) })
+              .run(queue:queue)
         case "/echo.Echo/Collect":
-          try Echo_EchoCollectSessionImpl(handler:handler, provider:provider).run(queue:queue)
+          try Echo_EchoCollectSessionImpl(
+            handler: handler,
+            providerBlock: { try provider.collect(session: $0 as! Echo_EchoCollectSessionImpl) })
+              .run(queue:queue)
         case "/echo.Echo/Update":
-          try Echo_EchoUpdateSessionImpl(handler:handler, provider:provider).run(queue:queue)
+          try Echo_EchoUpdateSessionImpl(
+            handler: handler,
+            providerBlock: { try provider.update(session: $0 as! Echo_EchoUpdateSessionImpl) })
+              .run(queue:queue)
         default:
           // handle unknown requests
           try handler.receiveMessage(initialMetadata:Metadata()) {(requestData) in

+ 4 - 79
Plugin/Templates/server-session-bidistreaming.swift

@@ -1,91 +1,16 @@
-// {{ method|methodDescriptorName }} (Bidirectional Streaming)
-{{ access }} protocol {{ .|session:file,service,method }} : {{ .|service:file,service }}Session {
+{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionBidirectionalStreaming {
   /// Receive a message. Blocks until a message is received or the client closes the connection.
   func receive() throws -> {{ method|input }}
 
   /// Send a message. Nonblocking.
-  func send(_ response: {{ method|output }}, completion: ((Bool)->())?) throws
+  func send(_ response: {{ method|output }}, completion: ((Bool) -> Void)?) throws
   
   /// Close a connection. Blocks until the connection is closed.
   func close() throws
 }
 
-fileprivate final class {{ .|session:file,service,method }}Impl : {{ .|service:file,service }}SessionImpl, {{ .|session:file,service,method }} {
-  private var provider : {{ .|provider:file,service }}
-
-  /// Create a session.
-  init(handler:Handler, provider: {{ .|provider:file,service }}) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
-
-  func receive() throws -> {{ method|input }} {
-    let sem = DispatchSemaphore(value: 0)
-    var requestMessage : {{ method|input }}?
-    try self.handler.receiveMessage() {(requestData) in
-      if let requestData = requestData {
-        do {
-          requestMessage = try {{ method|input }}(serializedData:requestData)
-        } catch (let error) {
-          print("error \(error)")
-        }
-      }
-      sem.signal()
-    }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-    if let requestMessage = requestMessage {
-      return requestMessage
-    } else {
-      throw {{ .|servererror:file,service }}.endOfStream
-    }
-  }
-
-  func send(_ response: {{ method|output }}, completion: ((Bool)->())?) throws {
-    try handler.sendResponse(message:response.serializedData(), completion: completion)
-  }
-
-  func close() throws {
-    let sem = DispatchSemaphore(value: 0)
-    try self.handler.sendStatus(statusCode:self.statusCode,
-                                statusMessage:self.statusMessage,
-                                trailingMetadata:self.trailingMetadata) { _ in sem.signal() }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-  }
-
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try self.handler.sendMetadata(initialMetadata:initialMetadata) { _ in
-      queue.async {
-        do {
-          try self.provider.{{ method|methodDescriptorName|lowercase }}(session:self)
-        } catch (let error) {
-          print("error \(error)")
-        }
-      }
-    }
-  }
-}
+fileprivate final class {{ .|session:file,service,method }}Impl: ServerSessionBidirectionalStreamingImpl<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} { }
 
 //-{% if generateTestStubs %}
-/// Simple fake implementation of {{ .|session:file,service,method }} that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class {{ .|session:file,service,method }}TestStub : {{ .|service:file,service }}SessionTestStub, {{ .|session:file,service,method }} {
-  var inputs: [{{ method|input }}] = []
-  var outputs: [{{ method|output }}] = []
-
-  func receive() throws -> {{ method|input }} {
-    if let input = inputs.first {
-      inputs.removeFirst()
-      return input
-    } else {
-      throw {{ .|clienterror:file,service }}.endOfStream
-    }
-  }
-
-  func send(_ response: {{ method|output }}, completion: ((Bool)->())?) throws {
-    outputs.append(response)
-  }
-
-  func close() throws { }
-}
+class {{ .|session:file,service,method }}TestStub: ServerSessionBidirectionalStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} { }
 //-{% endif %}

+ 3 - 68
Plugin/Templates/server-session-clientstreaming.swift

@@ -1,5 +1,4 @@
-// {{ method|methodDescriptorName }} (Client Streaming)
-{{ access }} protocol {{ .|session:file,service,method }} : {{ .|service:file,service }}Session {
+{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionClientStreaming {
   /// Receive a message. Blocks until a message is received or the client closes the connection.
   func receive() throws -> {{ method|input }}
 
@@ -7,72 +6,8 @@
   func sendAndClose(_ response: {{ method|output }}) throws
 }
 
-fileprivate final class {{ .|session:file,service,method }}Impl : {{ .|service:file,service }}SessionImpl, {{ .|session:file,service,method }} {
-  private var provider : {{ .|provider:file,service }}
-
-  /// Create a session.
-  init(handler:Handler, provider: {{ .|provider:file,service }}) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
-
-  func receive() throws -> {{ method|input }} {
-    let sem = DispatchSemaphore(value: 0)
-    var requestMessage : {{ method|input }}?
-    try self.handler.receiveMessage() {(requestData) in
-      if let requestData = requestData {
-        requestMessage = try? {{ method|input }}(serializedData:requestData)
-      }
-      sem.signal()
-    }
-    _ = sem.wait(timeout: DispatchTime.distantFuture)
-    if requestMessage == nil {
-      throw {{ .|servererror:file,service }}.endOfStream
-    }
-    return requestMessage!
-  }
-
-  func sendAndClose(_ response: {{ method|output }}) throws {
-    try self.handler.sendResponse(message:response.serializedData(),
-                                  statusCode:self.statusCode,
-                                  statusMessage:self.statusMessage,
-                                  trailingMetadata:self.trailingMetadata)
-  }
-
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try self.handler.sendMetadata(initialMetadata:initialMetadata) { _ in
-      queue.async {
-        do {
-          try self.provider.{{ method|methodDescriptorName|lowercase }}(session:self)
-        } catch (let error) {
-          print("error \(error)")
-        }
-      }
-    }
-  }
-}
+fileprivate final class {{ .|session:file,service,method }}Impl: ServerSessionClientStreamingImpl<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} { }
 
 //-{% if generateTestStubs %}
-/// Simple fake implementation of {{ .|session:file,service,method }} that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class {{ .|session:file,service,method }}TestStub: {{ .|service:file,service }}SessionTestStub, {{ .|session:file,service,method }} {
-  var inputs: [{{ method|input }}] = []
-  var output: {{ method|output }}?
-
-  func receive() throws -> {{ method|input }} {
-    if let input = inputs.first {
-      inputs.removeFirst()
-      return input
-    } else {
-      throw {{ .|clienterror:file,service }}.endOfStream
-    }
-  }
-
-  func sendAndClose(_ response: {{ method|output }}) throws {
-    output = response
-  }
-
-  func close() throws { }
-}
+class {{ .|session:file,service,method }}TestStub: ServerSessionClientStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} { }
 //-{% endif %}

+ 4 - 53
Plugin/Templates/server-session-serverstreaming.swift

@@ -1,59 +1,10 @@
-// {{ method|methodDescriptorName }} (Server Streaming)
-{{ access }} protocol {{ .|session:file,service,method }} : {{ .|service:file,service }}Session {
+{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionServerStreaming {
   /// Send a message. Nonblocking.
-  func send(_ response: {{ method|output }}, completion: ((Bool)->())?) throws
+  func send(_ response: {{ method|output }}, completion: ((Bool) -> Void)?) throws
 }
 
-fileprivate final class {{ .|session:file,service,method }}Impl : {{ .|service:file,service }}SessionImpl, {{ .|session:file,service,method }} {
-  private var provider : {{ .|provider:file,service }}
-
-  /// Create a session.
-  init(handler:Handler, provider: {{ .|provider:file,service }}) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
-
-  func send(_ response: {{ method|output }}, completion: ((Bool)->())?) throws {
-    try handler.sendResponse(message:response.serializedData(), completion: completion)
-  }
-
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try self.handler.receiveMessage(initialMetadata:initialMetadata) {(requestData) in
-      if let requestData = requestData {
-        do {
-          let requestMessage = try {{ method|input }}(serializedData:requestData)
-          // to keep providers from blocking the server thread,
-          // we dispatch them to another queue.
-          queue.async {
-            do {
-              try self.provider.{{ method|methodDescriptorName|lowercase }}(request:requestMessage, session: self)
-              try self.handler.sendStatus(statusCode:self.statusCode,
-                                          statusMessage:self.statusMessage,
-                                          trailingMetadata:self.trailingMetadata,
-                                          completion:nil)
-            } catch (let error) {
-              print("error: \(error)")
-            }
-          }
-        } catch (let error) {
-          print("error: \(error)")
-        }
-      }
-    }
-  }
-}
+fileprivate final class {{ .|session:file,service,method }}Impl: ServerSessionServerStreamingImpl<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} { }
 
 //-{% if generateTestStubs %}
-/// Simple fake implementation of {{ .|session:file,service,method }} that returns a previously-defined set of results
-/// and stores sent values for later verification.
-class {{ .|session:file,service,method }}TestStub : {{ .|service:file,service }}SessionTestStub, {{ .|session:file,service,method }} {
-  var outputs: [{{ method|output }}] = []
-
-  func send(_ response: {{ method|output }}, completion: ((Bool)->())?) throws {
-    outputs.append(response)
-  }
-
-  func close() throws { }
-}
+class {{ .|session:file,service,method }}TestStub: ServerSessionServerStreamingTestStub<{{ method|output }}>, {{ .|session:file,service,method }} { }
 //-{% endif %}

+ 3 - 27
Plugin/Templates/server-session-unary.swift

@@ -1,31 +1,7 @@
-// {{ method|methodDescriptorName }} (Unary Streaming)
-{{ access }} protocol {{ .|session:file,service,method }} : {{ .|service:file,service }}Session { }
+{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionUnary { }
 
-fileprivate final class {{ .|session:file,service,method }}Impl : {{ .|service:file,service }}SessionImpl, {{ .|session:file,service,method }} {
-  private var provider : {{ .|provider:file,service }}
-
-  /// Create a session.
-  init(handler:Handler, provider: {{ .|provider:file,service }}) {
-    self.provider = provider
-    super.init(handler:handler)
-  }
-
-  /// Run the session. Internal.
-  func run(queue:DispatchQueue) throws {
-    try handler.receiveMessage(initialMetadata:initialMetadata) {(requestData) in
-      if let requestData = requestData {
-        let requestMessage = try {{ method|input }}(serializedData:requestData)
-        let replyMessage = try self.provider.{{ method|methodDescriptorName|lowercase }}(request:requestMessage, session: self)
-        try self.handler.sendResponse(message:replyMessage.serializedData(),
-                                      statusCode:self.statusCode,
-                                      statusMessage:self.statusMessage,
-                                      trailingMetadata:self.trailingMetadata)
-      }
-    }
-  }
-}
+fileprivate final class {{ .|session:file,service,method }}Impl: ServerSessionUnaryImpl<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} { }
 
 //-{% if generateTestStubs %}
-/// Trivial fake implementation of {{ .|session:file,service,method }}.
-class {{ .|session:file,service,method }}TestStub : {{ .|service:file,service }}SessionTestStub, {{ .|session:file,service,method }} { }
+class {{ .|session:file,service,method }}TestStub: ServerSessionUnaryTestStub, {{ .|session:file,service,method }} { }
 //-{% endif %}

+ 11 - 42
Plugin/Templates/server.swift

@@ -1,10 +1,4 @@
 //-{% for service in file.services %}
-
-/// Type for errors thrown from generated server code.
-{{ access }} enum {{ .|servererror:file,service }} : Error {
-  case endOfStream
-}
-
 /// To build a server, implement a class that conforms to this protocol.
 {{ access }} protocol {{ .|provider:file,service }} {
   //-{% for method in service.methods %}
@@ -23,41 +17,6 @@
   //-{% endfor %}
 }
 
-/// Common properties available in each service session.
-{{ access }} protocol {{ .|service:file,service }}Session {
-  var requestMetadata : Metadata { get }
-
-  var statusCode : StatusCode { get }
-  var statusMessage : String { get }
-  var initialMetadata : Metadata { get }
-  var trailingMetadata : Metadata { get }
-}
-
-fileprivate class {{ .|service:file,service }}SessionImpl: {{ .|service:file,service }}Session {
-  var handler : Handler
-  var requestMetadata : Metadata { return handler.requestMetadata }
-
-  var statusCode : StatusCode = .ok
-  var statusMessage : String = "OK"
-  var initialMetadata : Metadata = Metadata()
-  var trailingMetadata : Metadata = Metadata()
-
-  init(handler:Handler) {
-    self.handler = handler
-  }
-}
-
-//-{% if generateTestStubs %}
-class {{ .|service:file,service }}SessionTestStub: {{ .|service:file,service }}Session {
-  var requestMetadata = Metadata()
-
-  var statusCode = StatusCode.ok
-  var statusMessage = "OK"
-  var initialMetadata = Metadata()
-  var trailingMetadata = Metadata()
-}
-//-{% endif %}
-
 //-{% for method in service.methods %}
 //-{% if method|methodIsUnary %}
 //-{% include "server-session-unary.swift" %}
@@ -123,7 +82,17 @@ class {{ .|service:file,service }}SessionTestStub: {{ .|service:file,service }}S
         switch unwrappedMethod {
         //-{% for method in service.methods %}
         case "{{ .|path:file,service,method }}":
-          try {{ .|session:file,service,method }}Impl(handler:handler, provider:provider).run(queue:queue)
+          //-{% if method|methodIsUnary or method|methodIsServerStreaming %}
+          try {{ .|session:file,service,method }}Impl(
+            handler: handler,
+            providerBlock: { try provider.{{ method|methodDescriptorName|lowercase }}(request: $0, session: $1 as! {{ .|session:file,service,method }}Impl) })
+              .run(queue:queue)
+          //-{% else %}
+          try {{ .|session:file,service,method }}Impl(
+            handler: handler,
+            providerBlock: { try provider.{{ method|methodDescriptorName|lowercase }}(session: $0 as! {{ .|session:file,service,method }}Impl) })
+              .run(queue:queue)
+          //-{% endif %}
         //-{% endfor %}
         default:
           // handle unknown requests

+ 24 - 0
Sources/gRPC/GenCodeSupport/ServerError.swift

@@ -0,0 +1,24 @@
+/*
+ * 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
+
+/// Type for errors thrown from generated server code.
+public enum ServerError: Error {
+  case endOfStream
+}

+ 51 - 0
Sources/gRPC/GenCodeSupport/ServerSession.swift

@@ -0,0 +1,51 @@
+/*
+ * 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 ServerSession: class {
+  var requestMetadata : Metadata { get }
+  
+  var statusCode : StatusCode { get }
+  var statusMessage : String { get }
+  var initialMetadata : Metadata { get }
+  var trailingMetadata : Metadata { get }
+}
+
+open class ServerSessionImpl: ServerSession {
+  public var handler : Handler
+  public var requestMetadata : Metadata { return handler.requestMetadata }
+  
+  public var statusCode : StatusCode = .ok
+  public var statusMessage : String = "OK"
+  public var initialMetadata : Metadata = Metadata()
+  public var trailingMetadata : Metadata = Metadata()
+  
+  public init(handler:Handler) {
+    self.handler = handler
+  }
+}
+
+open class ServerSessionTestStub: ServerSession {
+  open var requestMetadata = Metadata()
+  
+  open var statusCode = StatusCode.ok
+  open var statusMessage = "OK"
+  open var initialMetadata = Metadata()
+  open var trailingMetadata = Metadata()
+}

+ 98 - 0
Sources/gRPC/GenCodeSupport/ServerSessionBidirectionalStreaming.swift

@@ -0,0 +1,98 @@
+/*
+ * 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 ServerSessionBidirectionalStreaming: ServerSession { }
+
+open class ServerSessionBidirectionalStreamingImpl<InputType: Message, OutputType: Message>: ServerSessionImpl, ServerSessionBidirectionalStreaming {
+  public typealias ProviderBlock = (ServerSessionBidirectionalStreamingImpl) throws -> Void
+  private var providerBlock: ProviderBlock
+  
+  public init(handler:Handler, providerBlock: @escaping ProviderBlock) {
+    self.providerBlock = providerBlock
+    super.init(handler:handler)
+  }
+  
+  public func receive() throws -> InputType {
+    let sem = DispatchSemaphore(value: 0)
+    var requestMessage : InputType?
+    try self.handler.receiveMessage() {(requestData) in
+      if let requestData = requestData {
+        do {
+          requestMessage = try InputType(serializedData:requestData)
+        } catch (let error) {
+          print("error \(error)")
+        }
+      }
+      sem.signal()
+    }
+    _ = sem.wait(timeout: DispatchTime.distantFuture)
+    if let requestMessage = requestMessage {
+      return requestMessage
+    } else {
+      throw ServerError.endOfStream
+    }
+  }
+  
+  public func send(_ response: OutputType, completion: ((Bool)->())?) throws {
+    try handler.sendResponse(message:response.serializedData(), completion: completion)
+  }
+  
+  public func close() throws {
+    let sem = DispatchSemaphore(value: 0)
+    try self.handler.sendStatus(statusCode:self.statusCode,
+                                statusMessage:self.statusMessage,
+                                trailingMetadata:self.trailingMetadata) { _ in sem.signal() }
+    _ = sem.wait(timeout: DispatchTime.distantFuture)
+  }
+  
+  public func run(queue:DispatchQueue) throws {
+    try self.handler.sendMetadata(initialMetadata:initialMetadata) { _ in
+      queue.async {
+        do {
+          try self.providerBlock(self)
+        } catch (let error) {
+          print("error \(error)")
+        }
+      }
+    }
+  }
+}
+
+/// Simple fake implementation of ServerSessionBidirectionalStreaming that returns a previously-defined set of results
+/// and stores sent values for later verification.
+open class ServerSessionBidirectionalStreamingTestStub<InputType: Message, OutputType: Message>: ServerSessionTestStub, ServerSessionBidirectionalStreaming {
+  open var inputs: [InputType] = []
+  open var outputs: [OutputType] = []
+  
+  open func receive() throws -> InputType {
+    if let input = inputs.first {
+      inputs.removeFirst()
+      return input
+    } else {
+      throw ServerError.endOfStream
+    }
+  }
+  
+  open func send(_ response: OutputType, completion: ((Bool)->())?) throws {
+    outputs.append(response)
+  }
+  
+  open func close() throws { }
+}

+ 88 - 0
Sources/gRPC/GenCodeSupport/ServerSessionClientStreaming.swift

@@ -0,0 +1,88 @@
+/*
+ * 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 ServerSessionClientStreaming: ServerSession { }
+
+open class ServerSessionClientStreamingImpl<InputType: Message, OutputType: Message>: ServerSessionImpl, ServerSessionClientStreaming {
+  public typealias ProviderBlock = (ServerSessionClientStreamingImpl) throws -> Void
+  private var providerBlock: ProviderBlock
+  
+  public init(handler:Handler, providerBlock: @escaping ProviderBlock) {
+    self.providerBlock = providerBlock
+    super.init(handler:handler)
+  }
+  
+  public func receive() throws -> InputType {
+    let sem = DispatchSemaphore(value: 0)
+    var requestMessage : InputType?
+    try self.handler.receiveMessage() {(requestData) in
+      if let requestData = requestData {
+        requestMessage = try? InputType(serializedData:requestData)
+      }
+      sem.signal()
+    }
+    _ = sem.wait(timeout: DispatchTime.distantFuture)
+    if requestMessage == nil {
+      throw ServerError.endOfStream
+    }
+    return requestMessage!
+  }
+  
+  public func sendAndClose(_ response: OutputType) throws {
+    try self.handler.sendResponse(message:response.serializedData(),
+                                  statusCode:self.statusCode,
+                                  statusMessage:self.statusMessage,
+                                  trailingMetadata:self.trailingMetadata)
+  }
+  
+  public func run(queue:DispatchQueue) throws {
+    try self.handler.sendMetadata(initialMetadata:initialMetadata) { _ in
+      queue.async {
+        do {
+          try self.providerBlock(self)
+        } catch (let error) {
+          print("error \(error)")
+        }
+      }
+    }
+  }
+}
+
+/// Simple fake implementation of ServerSessionClientStreaming that returns a previously-defined result
+/// and stores sent values for later verification.
+open class ServerSessionClientStreamingTestStub<InputType: Message, OutputType: Message>: ServerSessionTestStub, ServerSessionClientStreaming {
+  open var inputs: [InputType] = []
+  open var output: OutputType?
+  
+  open func receive() throws -> InputType {
+    if let input = inputs.first {
+      inputs.removeFirst()
+      return input
+    } else {
+      throw ServerError.endOfStream
+    }
+  }
+  
+  open func sendAndClose(_ response: OutputType) throws {
+    output = response
+  }
+  
+  open func close() throws { }
+}

+ 72 - 0
Sources/gRPC/GenCodeSupport/ServerSessionServerStreaming.swift

@@ -0,0 +1,72 @@
+/*
+ * 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 ServerSessionServerStreaming: ServerSession { }
+
+open class ServerSessionServerStreamingImpl<InputType: Message, OutputType: Message>: ServerSessionImpl, ServerSessionServerStreaming {
+  public typealias ProviderBlock = (InputType, ServerSessionServerStreamingImpl) throws -> Void
+  private var providerBlock: ProviderBlock
+  
+  public init(handler:Handler, providerBlock: @escaping ProviderBlock) {
+    self.providerBlock = providerBlock
+    super.init(handler:handler)
+  }
+  
+  public func send(_ response: OutputType, completion: ((Bool)->())?) throws {
+    try handler.sendResponse(message:response.serializedData(), completion: completion)
+  }
+  
+  public func run(queue:DispatchQueue) throws {
+    try self.handler.receiveMessage(initialMetadata:initialMetadata) {(requestData) in
+      if let requestData = requestData {
+        do {
+          let requestMessage = try InputType(serializedData:requestData)
+          // to keep providers from blocking the server thread,
+          // we dispatch them to another queue.
+          queue.async {
+            do {
+              try self.providerBlock(requestMessage, self)
+              try self.handler.sendStatus(statusCode:self.statusCode,
+                                          statusMessage:self.statusMessage,
+                                          trailingMetadata:self.trailingMetadata,
+                                          completion:nil)
+            } catch (let error) {
+              print("error: \(error)")
+            }
+          }
+        } catch (let error) {
+          print("error: \(error)")
+        }
+      }
+    }
+  }
+}
+
+/// Simple fake implementation of ServerSessionServerStreaming that returns a previously-defined set of results
+/// and stores sent values for later verification.
+open class ServerSessionServerStreamingTestStub<OutputType: Message>: ServerSessionTestStub, ServerSessionServerStreaming {
+  open var outputs: [OutputType] = []
+  
+  open func send(_ response: OutputType, completion: ((Bool)->())?) throws {
+    outputs.append(response)
+  }
+  
+  open func close() throws { }
+}

+ 47 - 0
Sources/gRPC/GenCodeSupport/ServerSessionUnary.swift

@@ -0,0 +1,47 @@
+/*
+ * 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 ServerSessionUnary: ServerSession { }
+
+open class ServerSessionUnaryImpl<InputType: Message, OutputType: Message>: ServerSessionImpl, ServerSessionUnary {
+  public typealias ProviderBlock = (InputType, ServerSessionUnaryImpl) throws -> OutputType
+  private var providerBlock: ProviderBlock
+  
+  public init(handler:Handler, providerBlock: @escaping ProviderBlock) {
+    self.providerBlock = providerBlock
+    super.init(handler:handler)
+  }
+  
+  public func run(queue:DispatchQueue) throws {
+    try handler.receiveMessage(initialMetadata:initialMetadata) {(requestData) in
+      if let requestData = requestData {
+        let requestMessage = try InputType(serializedData:requestData)
+        let replyMessage = try self.providerBlock(requestMessage, self)
+        try self.handler.sendResponse(message:replyMessage.serializedData(),
+                                      statusCode:self.statusCode,
+                                      statusMessage:self.statusMessage,
+                                      trailingMetadata:self.trailingMetadata)
+      }
+    }
+  }
+}
+
+/// Trivial fake implementation of ServerSessionUnary.
+open class ServerSessionUnaryTestStub: ServerSessionTestStub, ServerSessionUnary { }