Browse Source

Generate sugared client APIs (#2009)

Motivation:

The generated client API uses the full expressive request/response types
which are used by the interceptors. This gives clients full control but
at the cost of usability.

In many cases we can simplify this for them:

- For single requests we can push the message and metadata into the stub
  signature and construct the request on behalf of the user
- For streaming requests we can push the metadata and request writing
  closure into the signature of the stub and construct the request for
  the user
- For single responses we can default the response handler to returning
  the message

Modifications:

- Add sugared API
- This, required that the struct swift representation and renderer be
  updated to support dictionary literls
- Regenrate the code
- Update the echo example to use the convenience API

Result:

Easier to use client APIs
George Barnett 1 year ago
parent
commit
89f8f7432a
1 changed files with 52 additions and 2 deletions
  1. 52 2
      Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift

+ 52 - 2
Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift

@@ -119,6 +119,29 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
             }
         }
 
+        @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+        extension Hello_World_Greeter.ClientProtocol {
+            /// Sends a greeting.
+            internal func sayHello<Result>(
+                _ message: Hello_World_HelloRequest,
+                metadata: GRPCCore.Metadata = [:],
+                options: GRPCCore.CallOptions = .defaults,
+                onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Hello_World_HelloReply>) async throws -> Result = {
+                    try $0.message
+                }
+            ) async throws -> Result where Result: Sendable {
+                let request = GRPCCore.ClientRequest.Single<Hello_World_HelloRequest>(
+                    message: message,
+                    metadata: metadata
+                )
+                return try await self.sayHello(
+                    request: request,
+                    options: options,
+                    handleResponse
+                )
+            }
+        }
+
         /// The greeting service definition.
         @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
         internal struct Hello_World_GreeterClient: Hello_World_Greeter.ClientProtocol {
@@ -392,6 +415,29 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
           }
         }
 
+        @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+        extension Greeter.ClientProtocol {
+          /// Sends a greeting.
+          package func sayHello<Result>(
+            _ message: HelloRequest,
+            metadata: GRPCCore.Metadata = [:],
+            options: GRPCCore.CallOptions = .defaults,
+            onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<HelloReply>) async throws -> Result = {
+              try $0.message
+            }
+          ) async throws -> Result where Result: Sendable {
+            let request = GRPCCore.ClientRequest.Single<HelloRequest>(
+              message: message,
+              metadata: metadata
+            )
+            return try await self.sayHello(
+              request: request,
+              options: options,
+              handleResponse
+            )
+          }
+        }
+
         /// The greeting service definition.
         @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
         package struct GreeterClient: Greeter.ClientProtocol {
@@ -431,7 +477,9 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
     visibility: SourceGenerator.Configuration.AccessLevel,
     client: Bool,
     server: Bool,
-    expectedCode: String
+    expectedCode: String,
+    file: StaticString = #filePath,
+    line: UInt = #line
   ) throws {
     let configs = SourceGenerator.Configuration(
       accessLevel: visibility,
@@ -471,7 +519,9 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
         protoFileModuleMappings: ProtoFileToModuleMappings(moduleMappingsProto: moduleMappings),
         extraModuleImports: ["ExtraModule"]
       ),
-      expectedCode
+      expectedCode,
+      file: file,
+      line: line
     )
   }
 }