Browse Source

Generate default response handlers (#2007)

Motivation:

The client stub for all RPCs accepts a response handler and can return
the result of that response. For unary and client-streaming RPCs, the
most common handler will just return the response message. This handler
should be generated as the default for those RPC types.

Modifications:

- Default the response handler parameter (`body`) to `{ try $0.message
}`.

Result:

As an example, part of the generated code for the Echo service which
looks like:

```swift
extension Echo_Echo.ClientProtocol {
    internal func get<R>(
        request: ClientRequest.Single<Echo_EchoRequest>,
        options: CallOptions = .defaults,
        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R
    ) async throws -> R where R: Sendable {
        try await self.get(...)
    }
```

Becomes:

```swift
extension Echo_Echo.ClientProtocol {
    internal func get<R>(
        request: ClientRequest.Single<Echo_EchoRequest>,
        options: CallOptions = .defaults,
        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R = {
	    try $0.message
        }
    ) async throws -> R where R: Sendable {
        try await self.get(...)
    }
```

---------

Co-authored-by: George Barnett <gbarnett@apple.com>
Clinton Nkwocha 1 year ago
parent
commit
edaa545c21

+ 12 - 4
Sources/Examples/v2/Echo/Generated/echo.grpc.swift

@@ -216,7 +216,9 @@ extension Echo_Echo.ClientProtocol {
     internal func get<R>(
         request: ClientRequest.Single<Echo_EchoRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.get(
             request: request,
@@ -244,7 +246,9 @@ extension Echo_Echo.ClientProtocol {
     internal func collect<R>(
         request: ClientRequest.Stream<Echo_EchoRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.collect(
             request: request,
@@ -284,7 +288,9 @@ internal struct Echo_EchoClient: Echo_Echo.ClientProtocol {
         serializer: some MessageSerializer<Echo_EchoRequest>,
         deserializer: some MessageDeserializer<Echo_EchoResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -320,7 +326,9 @@ internal struct Echo_EchoClient: Echo_Echo.ClientProtocol {
         serializer: some MessageSerializer<Echo_EchoRequest>,
         deserializer: some MessageDeserializer<Echo_EchoResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Echo_EchoResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.clientStreaming(
             request: request,

+ 37 - 8
Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift

@@ -37,7 +37,9 @@
 ///   public func baz<R>(
 ///     request: ClientRequest.Single<Foo_Bar_Input>,
 ///     options: CallOptions = .defaults,
-///     _ body: @Sendable @escaping (ClientResponse.Single<Foo_Bar_Output>) async throws -> R
+///     _ body: @Sendable @escaping (ClientResponse.Single<Foo_Bar_Output>) async throws -> R = {
+///       try $0.message
+///     }
 ///   ) async throws -> R where R: Sendable {
 ///     try await self.baz(
 ///       request: request,
@@ -58,7 +60,9 @@
 ///     serializer: some MessageSerializer<Foo_Bar_Input>,
 ///     deserializer: some MessageDeserializer<Foo_Bar_Output>,
 ///     options: CallOptions = .defaults,
-///     _ body: @Sendable @escaping (ClientResponse.Single<Foo_Bar_Output>) async throws -> R
+///     _ body: @Sendable @escaping (ClientResponse.Single<Foo_Bar_Output>) async throws -> R = {
+///       try $0.message
+///     }
 ///   ) async throws -> R where R: Sendable {
 ///     try await self.client.unary(
 ///       request: request,
@@ -173,7 +177,8 @@ extension ClientCodeTranslator {
       from: codeGenerationRequest,
       // The serializer/deserializer for the protocol extension method will be auto-generated.
       includeSerializationParameters: !isProtocolExtension,
-      includeDefaultCallOptions: includeDefaultCallOptions
+      includeDefaultCallOptions: includeDefaultCallOptions,
+      includeDefaultResponseHandler: isProtocolExtension && !method.isOutputStreaming
     )
     let functionSignature = FunctionSignatureDescription(
       accessModifier: accessModifier,
@@ -242,7 +247,8 @@ extension ClientCodeTranslator {
     in service: CodeGenerationRequest.ServiceDescriptor,
     from codeGenerationRequest: CodeGenerationRequest,
     includeSerializationParameters: Bool,
-    includeDefaultCallOptions: Bool
+    includeDefaultCallOptions: Bool,
+    includeDefaultResponseHandler: Bool
   ) -> [ParameterDescription] {
     var parameters = [ParameterDescription]()
 
@@ -259,7 +265,13 @@ extension ClientCodeTranslator {
           ? .memberAccess(MemberAccessDescription(right: "defaults")) : nil
       )
     )
-    parameters.append(self.bodyParameter(for: method, in: service))
+    parameters.append(
+      self.bodyParameter(
+        for: method,
+        in: service,
+        includeDefaultResponseHandler: includeDefaultResponseHandler
+      )
+    )
     return parameters
   }
   private func clientRequestParameter(
@@ -309,7 +321,8 @@ extension ClientCodeTranslator {
 
   private func bodyParameter(
     for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor,
-    in service: CodeGenerationRequest.ServiceDescriptor
+    in service: CodeGenerationRequest.ServiceDescriptor,
+    includeDefaultResponseHandler: Bool
   ) -> ParameterDescription {
     let clientStreaming = method.isOutputStreaming ? "Stream" : "Single"
     let closureParameterType = ExistingTypeDescription.generic(
@@ -324,7 +337,22 @@ extension ClientCodeTranslator {
       sendable: true,
       escaping: true
     )
-    return ParameterDescription(name: "body", type: .closure(bodyClosure))
+
+    var defaultResponseHandler: Expression? = nil
+
+    if includeDefaultResponseHandler {
+      defaultResponseHandler = .closureInvocation(
+        ClosureInvocationDescription(
+          body: [.expression(.try(.identifierPattern("$0").dot("message")))]
+        )
+      )
+    }
+
+    return ParameterDescription(
+      name: "body",
+      type: .closure(bodyClosure),
+      defaultValue: defaultResponseHandler
+    )
   }
 
   private func makeClientStruct(
@@ -403,7 +431,8 @@ extension ClientCodeTranslator {
       in: service,
       from: codeGenerationRequest,
       includeSerializationParameters: true,
-      includeDefaultCallOptions: true
+      includeDefaultCallOptions: true,
+      includeDefaultResponseHandler: !method.isOutputStreaming
     )
     let grpcMethodName = self.clientMethod(
       isInputStreaming: method.isInputStreaming,

+ 48 - 16
Sources/InteroperabilityTests/Generated/test.grpc.swift

@@ -566,7 +566,9 @@ extension Grpc_Testing_TestService.ClientProtocol {
     public func emptyCall<R>(
         request: ClientRequest.Single<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.emptyCall(
             request: request,
@@ -580,7 +582,9 @@ extension Grpc_Testing_TestService.ClientProtocol {
     public func unaryCall<R>(
         request: ClientRequest.Single<Grpc_Testing_SimpleRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.unaryCall(
             request: request,
@@ -594,7 +598,9 @@ extension Grpc_Testing_TestService.ClientProtocol {
     public func cacheableUnaryCall<R>(
         request: ClientRequest.Single<Grpc_Testing_SimpleRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.cacheableUnaryCall(
             request: request,
@@ -622,7 +628,9 @@ extension Grpc_Testing_TestService.ClientProtocol {
     public func streamingInputCall<R>(
         request: ClientRequest.Stream<Grpc_Testing_StreamingInputCallRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_StreamingInputCallResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_StreamingInputCallResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.streamingInputCall(
             request: request,
@@ -664,7 +672,9 @@ extension Grpc_Testing_TestService.ClientProtocol {
     public func unimplementedCall<R>(
         request: ClientRequest.Single<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.unimplementedCall(
             request: request,
@@ -692,7 +702,9 @@ public struct Grpc_Testing_TestServiceClient: Grpc_Testing_TestService.ClientPro
         serializer: some MessageSerializer<Grpc_Testing_Empty>,
         deserializer: some MessageDeserializer<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -710,7 +722,9 @@ public struct Grpc_Testing_TestServiceClient: Grpc_Testing_TestService.ClientPro
         serializer: some MessageSerializer<Grpc_Testing_SimpleRequest>,
         deserializer: some MessageDeserializer<Grpc_Testing_SimpleResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -730,7 +744,9 @@ public struct Grpc_Testing_TestServiceClient: Grpc_Testing_TestService.ClientPro
         serializer: some MessageSerializer<Grpc_Testing_SimpleRequest>,
         deserializer: some MessageDeserializer<Grpc_Testing_SimpleResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -768,7 +784,9 @@ public struct Grpc_Testing_TestServiceClient: Grpc_Testing_TestService.ClientPro
         serializer: some MessageSerializer<Grpc_Testing_StreamingInputCallRequest>,
         deserializer: some MessageDeserializer<Grpc_Testing_StreamingInputCallResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_StreamingInputCallResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_StreamingInputCallResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.clientStreaming(
             request: request,
@@ -828,7 +846,9 @@ public struct Grpc_Testing_TestServiceClient: Grpc_Testing_TestService.ClientPro
         serializer: some MessageSerializer<Grpc_Testing_Empty>,
         deserializer: some MessageDeserializer<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -860,7 +880,9 @@ extension Grpc_Testing_UnimplementedService.ClientProtocol {
     public func unimplementedCall<R>(
         request: ClientRequest.Single<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.unimplementedCall(
             request: request,
@@ -888,7 +910,9 @@ public struct Grpc_Testing_UnimplementedServiceClient: Grpc_Testing_Unimplemente
         serializer: some MessageSerializer<Grpc_Testing_Empty>,
         deserializer: some MessageDeserializer<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -926,7 +950,9 @@ extension Grpc_Testing_ReconnectService.ClientProtocol {
     public func start<R>(
         request: ClientRequest.Single<Grpc_Testing_ReconnectParams>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.start(
             request: request,
@@ -940,7 +966,9 @@ extension Grpc_Testing_ReconnectService.ClientProtocol {
     public func stop<R>(
         request: ClientRequest.Single<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_ReconnectInfo>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_ReconnectInfo>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.stop(
             request: request,
@@ -966,7 +994,9 @@ public struct Grpc_Testing_ReconnectServiceClient: Grpc_Testing_ReconnectService
         serializer: some MessageSerializer<Grpc_Testing_ReconnectParams>,
         deserializer: some MessageDeserializer<Grpc_Testing_Empty>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_Empty>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -983,7 +1013,9 @@ public struct Grpc_Testing_ReconnectServiceClient: Grpc_Testing_ReconnectService
         serializer: some MessageSerializer<Grpc_Testing_Empty>,
         deserializer: some MessageDeserializer<Grpc_Testing_ReconnectInfo>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_ReconnectInfo>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_ReconnectInfo>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,

+ 6 - 2
Sources/Services/Health/Generated/health.grpc.swift

@@ -225,7 +225,9 @@ extension Grpc_Health_V1_Health.ClientProtocol {
     package func check<R>(
         request: ClientRequest.Single<Grpc_Health_V1_HealthCheckRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Health_V1_HealthCheckResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Health_V1_HealthCheckResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.check(
             request: request,
@@ -276,7 +278,9 @@ package struct Grpc_Health_V1_HealthClient: Grpc_Health_V1_Health.ClientProtocol
         serializer: some MessageSerializer<Grpc_Health_V1_HealthCheckRequest>,
         deserializer: some MessageDeserializer<Grpc_Health_V1_HealthCheckResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Health_V1_HealthCheckResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Health_V1_HealthCheckResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,

+ 12 - 4
Sources/performance-worker/Generated/grpc_testing_benchmark_service.grpc.swift

@@ -269,7 +269,9 @@ extension Grpc_Testing_BenchmarkService.ClientProtocol {
     internal func unaryCall<R>(
         request: ClientRequest.Single<Grpc_Testing_SimpleRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.unaryCall(
             request: request,
@@ -297,7 +299,9 @@ extension Grpc_Testing_BenchmarkService.ClientProtocol {
     internal func streamingFromClient<R>(
         request: ClientRequest.Stream<Grpc_Testing_SimpleRequest>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.streamingFromClient(
             request: request,
@@ -352,7 +356,9 @@ internal struct Grpc_Testing_BenchmarkServiceClient: Grpc_Testing_BenchmarkServi
         serializer: some MessageSerializer<Grpc_Testing_SimpleRequest>,
         deserializer: some MessageDeserializer<Grpc_Testing_SimpleResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -391,7 +397,9 @@ internal struct Grpc_Testing_BenchmarkServiceClient: Grpc_Testing_BenchmarkServi
         serializer: some MessageSerializer<Grpc_Testing_SimpleRequest>,
         deserializer: some MessageDeserializer<Grpc_Testing_SimpleResponse>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.clientStreaming(
             request: request,

+ 24 - 8
Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift

@@ -59,7 +59,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
           public func methodA<R>(
               request: ClientRequest.Single<NamespaceA_ServiceARequest>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.methodA(
                   request: request,
@@ -85,7 +87,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
               serializer: some MessageSerializer<NamespaceA_ServiceARequest>,
               deserializer: some MessageDeserializer<NamespaceA_ServiceAResponse>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.client.unary(
                   request: request,
@@ -140,7 +144,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
           public func methodA<R>(
               request: ClientRequest.Stream<NamespaceA_ServiceARequest>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.methodA(
                   request: request,
@@ -166,7 +172,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
               serializer: some MessageSerializer<NamespaceA_ServiceARequest>,
               deserializer: some MessageDeserializer<NamespaceA_ServiceAResponse>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.client.clientStreaming(
                   request: request,
@@ -400,7 +408,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
           package func methodA<R>(
               request: ClientRequest.Stream<NamespaceA_ServiceARequest>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.methodA(
                   request: request,
@@ -440,7 +450,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
               serializer: some MessageSerializer<NamespaceA_ServiceARequest>,
               deserializer: some MessageDeserializer<NamespaceA_ServiceAResponse>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<NamespaceA_ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.client.clientStreaming(
                   request: request,
@@ -513,7 +525,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
           internal func methodA<R>(
               request: ClientRequest.Single<ServiceARequest>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.methodA(
                   request: request,
@@ -539,7 +553,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase {
               serializer: some MessageSerializer<ServiceARequest>,
               deserializer: some MessageDeserializer<ServiceAResponse>,
               options: CallOptions = .defaults,
-              _ body: @Sendable @escaping (ClientResponse.Single<ServiceAResponse>) async throws -> R
+              _ body: @Sendable @escaping (ClientResponse.Single<ServiceAResponse>) async throws -> R = {
+                  try $0.message
+              }
           ) async throws -> R where R: Sendable {
               try await self.client.unary(
                   request: request,

+ 12 - 4
Tests/GRPCHTTP2TransportTests/Generated/control.grpc.swift

@@ -217,7 +217,9 @@ extension Control.ClientProtocol {
     internal func unary<R>(
         request: ClientRequest.Single<ControlInput>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.unary(
             request: request,
@@ -245,7 +247,9 @@ extension Control.ClientProtocol {
     internal func clientStream<R>(
         request: ClientRequest.Stream<ControlInput>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.clientStream(
             request: request,
@@ -288,7 +292,9 @@ internal struct ControlClient: Control.ClientProtocol {
         serializer: some MessageSerializer<ControlInput>,
         deserializer: some MessageDeserializer<ControlOutput>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.unary(
             request: request,
@@ -322,7 +328,9 @@ internal struct ControlClient: Control.ClientProtocol {
         serializer: some MessageSerializer<ControlInput>,
         deserializer: some MessageDeserializer<ControlOutput>,
         options: CallOptions = .defaults,
-        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R
+        _ body: @Sendable @escaping (ClientResponse.Single<ControlOutput>) async throws -> R = {
+            try $0.message
+        }
     ) async throws -> R where R: Sendable {
         try await self.client.clientStreaming(
             request: request,

+ 12 - 4
Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift

@@ -105,7 +105,9 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
             internal func sayHello<R>(
                 request: ClientRequest.Single<Hello_World_HelloRequest>,
                 options: CallOptions = .defaults,
-                _ body: @Sendable @escaping (ClientResponse.Single<Hello_World_HelloReply>) async throws -> R
+                _ body: @Sendable @escaping (ClientResponse.Single<Hello_World_HelloReply>) async throws -> R = {
+                    try $0.message
+                }
             ) async throws -> R where R: Sendable {
                 try await self.sayHello(
                     request: request,
@@ -132,7 +134,9 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
                 serializer: some MessageSerializer<Hello_World_HelloRequest>,
                 deserializer: some MessageDeserializer<Hello_World_HelloReply>,
                 options: CallOptions = .defaults,
-                _ body: @Sendable @escaping (ClientResponse.Single<Hello_World_HelloReply>) async throws -> R
+                _ body: @Sendable @escaping (ClientResponse.Single<Hello_World_HelloReply>) async throws -> R = {
+                    try $0.message
+                }
             ) async throws -> R where R: Sendable {
                 try await self.client.unary(
                     request: request,
@@ -374,7 +378,9 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
           package func sayHello<R>(
             request: ClientRequest.Single<HelloRequest>,
             options: CallOptions = .defaults,
-            _ body: @Sendable @escaping (ClientResponse.Single<HelloReply>) async throws -> R
+            _ body: @Sendable @escaping (ClientResponse.Single<HelloReply>) async throws -> R = {
+              try $0.message
+            }
           ) async throws -> R where R: Sendable {
             try await self.sayHello(
               request: request,
@@ -401,7 +407,9 @@ final class ProtobufCodeGeneratorTests: XCTestCase {
             serializer: some MessageSerializer<HelloRequest>,
             deserializer: some MessageDeserializer<HelloReply>,
             options: CallOptions = .defaults,
-            _ body: @Sendable @escaping (ClientResponse.Single<HelloReply>) async throws -> R
+            _ body: @Sendable @escaping (ClientResponse.Single<HelloReply>) async throws -> R = {
+              try $0.message
+            }
           ) async throws -> R where R: Sendable {
             try await self.client.unary(
               request: request,