Browse Source

Add AnyServiceClient (#481)

Motivation:
- Sometimes it's useful to call a service when you don't have a client
  generated for it.
- We had `GRPCClient` which contains all the boilerplate for creating
  calls, but no concrete implementation.

Modifications:
- Add `AnyServiceClient`, a minimal `GRPCClient` with no generated
  stubs.
- Added tests.
George Barnett 6 years ago
parent
commit
b19820ca8a

+ 45 - 8
Sources/GRPC/GRPCClient.swift

@@ -29,39 +29,59 @@ extension GRPCClient {
   public func makeUnaryCall<Request: Message, Response: Message>(
     path: String,
     request: Request,
-    callOptions: CallOptions,
+    callOptions: CallOptions? = nil,
     responseType: Response.Type = Response.self
   ) -> UnaryClientCall<Request, Response> {
-    return UnaryClientCall(connection: self.connection, path: path, request: request, callOptions: callOptions, errorDelegate: self.connection.configuration.errorDelegate)
+    return UnaryClientCall(
+      connection: self.connection,
+      path: path,
+      request: request,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      errorDelegate: self.connection.configuration.errorDelegate)
   }
 
   public func makeServerStreamingCall<Request: Message, Response: Message>(
     path: String,
     request: Request,
-    callOptions: CallOptions,
+    callOptions: CallOptions? = nil,
     responseType: Response.Type = Response.self,
     handler: @escaping (Response) -> Void
   ) -> ServerStreamingClientCall<Request, Response> {
-    return ServerStreamingClientCall(connection: self.connection, path: path, request: request, callOptions: callOptions, errorDelegate: self.connection.configuration.errorDelegate, handler: handler)
+    return ServerStreamingClientCall(
+      connection: self.connection,
+      path: path,
+      request: request,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      errorDelegate: self.connection.configuration.errorDelegate,
+      handler: handler)
   }
 
   public func makeClientStreamingCall<Request: Message, Response: Message>(
     path: String,
-    callOptions: CallOptions,
+    callOptions: CallOptions? = nil,
     requestType: Request.Type = Request.self,
     responseType: Response.Type = Response.self
   ) -> ClientStreamingClientCall<Request, Response> {
-    return ClientStreamingClientCall(connection: self.connection, path: path, callOptions: callOptions, errorDelegate: self.connection.configuration.errorDelegate)
+    return ClientStreamingClientCall(
+      connection: self.connection,
+      path: path,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      errorDelegate: self.connection.configuration.errorDelegate)
   }
 
   public func makeBidirectionalStreamingCall<Request: Message, Response: Message>(
     path: String,
-    callOptions: CallOptions,
+    callOptions: CallOptions? = nil,
     requestType: Request.Type = Request.self,
     responseType: Response.Type = Response.self,
     handler: @escaping (Response) -> Void
   ) -> BidirectionalStreamingClientCall<Request, Response> {
-    return BidirectionalStreamingClientCall(connection: self.connection, path: path, callOptions: callOptions, errorDelegate: self.connection.configuration.errorDelegate, handler: handler)
+    return BidirectionalStreamingClientCall(
+      connection: self.connection,
+      path: path,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      errorDelegate: self.connection.configuration.errorDelegate,
+      handler: handler)
   }
 }
 
@@ -85,3 +105,20 @@ extension GRPCServiceClient {
     return "/\(self.serviceName)/\(method)"
   }
 }
+
+/// A client which has no generated stubs and may be used to create gRPC calls manually.
+/// See `GRPCClient` for details.
+public final class AnyServiceClient: GRPCClient {
+  public let connection: GRPCClientConnection
+  public var defaultCallOptions: CallOptions
+
+  /// Creates a client which may be used to call any service.
+  ///
+  /// - Parameters:
+  ///   - connection: `GRPCClientConnection` to the service host.
+  ///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
+  public init(connection: GRPCClientConnection, defaultCallOptions: CallOptions = CallOptions()) {
+    self.connection = connection
+    self.defaultCallOptions = defaultCallOptions
+  }
+}

+ 67 - 0
Tests/GRPCTests/AnyServiceClientTests.swift

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019, 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 GRPC
+import XCTest
+
+class AnyServiceClientTests: EchoTestCaseBase {
+
+  var anyServiceClient: AnyServiceClient {
+    return AnyServiceClient(connection: self.client.connection)
+  }
+
+  func testUnary() throws {
+    let get = self.anyServiceClient.makeUnaryCall(
+      path: "/echo.Echo/Get",
+      request: Echo_EchoRequest.with { $0.text = "foo" },
+      responseType: Echo_EchoResponse.self)
+
+    XCTAssertEqual(try get.status.map { $0.code }.wait(), .ok)
+  }
+
+  func testClientStreaming() throws {
+    let collect = self.anyServiceClient.makeClientStreamingCall(
+      path: "/echo.Echo/Collect",
+      requestType: Echo_EchoRequest.self,
+      responseType: Echo_EchoResponse.self)
+
+    collect.sendEnd(promise: nil)
+
+    XCTAssertEqual(try collect.status.map { $0.code }.wait(), .ok)
+  }
+
+  func testServerStreaming() throws {
+    let expand = self.anyServiceClient.makeServerStreamingCall(
+      path: "/echo.Echo/Expand",
+      request: Echo_EchoRequest.with { $0.text = "foo" },
+      responseType: Echo_EchoResponse.self,
+      handler: { _ in })
+
+    XCTAssertEqual(try expand.status.map { $0.code }.wait(), .ok)
+  }
+
+  func testBidirectionalStreaming() throws {
+    let update = self.anyServiceClient.makeBidirectionalStreamingCall(
+      path: "/echo.Echo/Update",
+      requestType: Echo_EchoRequest.self,
+      responseType: Echo_EchoResponse.self,
+      handler: { _ in })
+
+    update.sendEnd(promise: nil)
+
+    XCTAssertEqual(try update.status.map { $0.code }.wait(), .ok)
+  }
+}

+ 13 - 0
Tests/GRPCTests/XCTestManifests.swift

@@ -1,6 +1,18 @@
 #if !canImport(ObjectiveC)
 import XCTest
 
+extension AnyServiceClientTests {
+    // DO NOT MODIFY: This is autogenerated, use:
+    //   `swift test --generate-linuxmain`
+    // to regenerate.
+    static let __allTests__AnyServiceClientTests = [
+        ("testBidirectionalStreaming", testBidirectionalStreaming),
+        ("testClientStreaming", testClientStreaming),
+        ("testServerStreaming", testServerStreaming),
+        ("testUnary", testUnary),
+    ]
+}
+
 extension ClientCancellingTests {
     // DO NOT MODIFY: This is autogenerated, use:
     //   `swift test --generate-linuxmain`
@@ -307,6 +319,7 @@ extension ServerWebTests {
 
 public func __allTests() -> [XCTestCaseEntry] {
     return [
+        testCase(AnyServiceClientTests.__allTests__AnyServiceClientTests),
         testCase(ClientCancellingTests.__allTests__ClientCancellingTests),
         testCase(ClientClosedChannelTests.__allTests__ClientClosedChannelTests),
         testCase(ClientTLSFailureTests.__allTests__ClientTLSFailureTests),