Skip to content

Commit 7feca6a

Browse files
authored
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
1 parent c59bcc3 commit 7feca6a

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed

Sources/InteroperabilityTests/Generated/empty_service.grpc.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ public protocol Grpc_Testing_EmptyServiceClientProtocol: Sendable {}
7777
extension Grpc_Testing_EmptyService.ClientProtocol {
7878
}
7979

80+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
81+
extension Grpc_Testing_EmptyService.ClientProtocol {
82+
}
83+
8084
/// A service that has zero methods.
8185
/// See https://github.com/grpc/grpc/issues/15574
8286
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)

Sources/InteroperabilityTests/Generated/test.grpc.swift

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,173 @@ extension Grpc_Testing_TestService.ClientProtocol {
686686
}
687687
}
688688

689+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
690+
extension Grpc_Testing_TestService.ClientProtocol {
691+
/// One empty request followed by one empty response.
692+
public func emptyCall<Result>(
693+
_ message: Grpc_Testing_Empty,
694+
metadata: GRPCCore.Metadata = [:],
695+
options: GRPCCore.CallOptions = .defaults,
696+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_Empty>) async throws -> Result = {
697+
try $0.message
698+
}
699+
) async throws -> Result where Result: Sendable {
700+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_Empty>(
701+
message: message,
702+
metadata: metadata
703+
)
704+
return try await self.emptyCall(
705+
request: request,
706+
options: options,
707+
handleResponse
708+
)
709+
}
710+
711+
/// One request followed by one response.
712+
public func unaryCall<Result>(
713+
_ message: Grpc_Testing_SimpleRequest,
714+
metadata: GRPCCore.Metadata = [:],
715+
options: GRPCCore.CallOptions = .defaults,
716+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> Result = {
717+
try $0.message
718+
}
719+
) async throws -> Result where Result: Sendable {
720+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_SimpleRequest>(
721+
message: message,
722+
metadata: metadata
723+
)
724+
return try await self.unaryCall(
725+
request: request,
726+
options: options,
727+
handleResponse
728+
)
729+
}
730+
731+
/// One request followed by one response. Response has cache control
732+
/// headers set such that a caching HTTP proxy (such as GFE) can
733+
/// satisfy subsequent requests.
734+
public func cacheableUnaryCall<Result>(
735+
_ message: Grpc_Testing_SimpleRequest,
736+
metadata: GRPCCore.Metadata = [:],
737+
options: GRPCCore.CallOptions = .defaults,
738+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_SimpleResponse>) async throws -> Result = {
739+
try $0.message
740+
}
741+
) async throws -> Result where Result: Sendable {
742+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_SimpleRequest>(
743+
message: message,
744+
metadata: metadata
745+
)
746+
return try await self.cacheableUnaryCall(
747+
request: request,
748+
options: options,
749+
handleResponse
750+
)
751+
}
752+
753+
/// One request followed by a sequence of responses (streamed download).
754+
/// The server returns the payload with client desired type and sizes.
755+
public func streamingOutputCall<Result>(
756+
_ message: Grpc_Testing_StreamingOutputCallRequest,
757+
metadata: GRPCCore.Metadata = [:],
758+
options: GRPCCore.CallOptions = .defaults,
759+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream<Grpc_Testing_StreamingOutputCallResponse>) async throws -> Result
760+
) async throws -> Result where Result: Sendable {
761+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_StreamingOutputCallRequest>(
762+
message: message,
763+
metadata: metadata
764+
)
765+
return try await self.streamingOutputCall(
766+
request: request,
767+
options: options,
768+
handleResponse
769+
)
770+
}
771+
772+
/// A sequence of requests followed by one response (streamed upload).
773+
/// The server returns the aggregated size of client payload as the result.
774+
public func streamingInputCall<Result>(
775+
metadata: GRPCCore.Metadata = [:],
776+
options: GRPCCore.CallOptions = .defaults,
777+
requestProducer: @Sendable @escaping (GRPCCore.RPCWriter<Grpc_Testing_StreamingInputCallRequest>) async throws -> Void,
778+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_StreamingInputCallResponse>) async throws -> Result = {
779+
try $0.message
780+
}
781+
) async throws -> Result where Result: Sendable {
782+
let request = GRPCCore.ClientRequest.Stream<Grpc_Testing_StreamingInputCallRequest>(
783+
metadata: metadata,
784+
producer: requestProducer
785+
)
786+
return try await self.streamingInputCall(
787+
request: request,
788+
options: options,
789+
handleResponse
790+
)
791+
}
792+
793+
/// A sequence of requests with each request served by the server immediately.
794+
/// As one request could lead to multiple responses, this interface
795+
/// demonstrates the idea of full duplexing.
796+
public func fullDuplexCall<Result>(
797+
metadata: GRPCCore.Metadata = [:],
798+
options: GRPCCore.CallOptions = .defaults,
799+
requestProducer: @Sendable @escaping (GRPCCore.RPCWriter<Grpc_Testing_StreamingOutputCallRequest>) async throws -> Void,
800+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream<Grpc_Testing_StreamingOutputCallResponse>) async throws -> Result
801+
) async throws -> Result where Result: Sendable {
802+
let request = GRPCCore.ClientRequest.Stream<Grpc_Testing_StreamingOutputCallRequest>(
803+
metadata: metadata,
804+
producer: requestProducer
805+
)
806+
return try await self.fullDuplexCall(
807+
request: request,
808+
options: options,
809+
handleResponse
810+
)
811+
}
812+
813+
/// A sequence of requests followed by a sequence of responses.
814+
/// The server buffers all the client requests and then serves them in order. A
815+
/// stream of responses are returned to the client when the server starts with
816+
/// first request.
817+
public func halfDuplexCall<Result>(
818+
metadata: GRPCCore.Metadata = [:],
819+
options: GRPCCore.CallOptions = .defaults,
820+
requestProducer: @Sendable @escaping (GRPCCore.RPCWriter<Grpc_Testing_StreamingOutputCallRequest>) async throws -> Void,
821+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream<Grpc_Testing_StreamingOutputCallResponse>) async throws -> Result
822+
) async throws -> Result where Result: Sendable {
823+
let request = GRPCCore.ClientRequest.Stream<Grpc_Testing_StreamingOutputCallRequest>(
824+
metadata: metadata,
825+
producer: requestProducer
826+
)
827+
return try await self.halfDuplexCall(
828+
request: request,
829+
options: options,
830+
handleResponse
831+
)
832+
}
833+
834+
/// The test server will not implement this method. It will be used
835+
/// to test the behavior when clients call unimplemented methods.
836+
public func unimplementedCall<Result>(
837+
_ message: Grpc_Testing_Empty,
838+
metadata: GRPCCore.Metadata = [:],
839+
options: GRPCCore.CallOptions = .defaults,
840+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_Empty>) async throws -> Result = {
841+
try $0.message
842+
}
843+
) async throws -> Result where Result: Sendable {
844+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_Empty>(
845+
message: message,
846+
metadata: metadata
847+
)
848+
return try await self.unimplementedCall(
849+
request: request,
850+
options: options,
851+
handleResponse
852+
)
853+
}
854+
}
855+
689856
/// A simple service to test the various types of RPCs and experiment with
690857
/// performance with various types of payload.
691858
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@@ -894,6 +1061,29 @@ extension Grpc_Testing_UnimplementedService.ClientProtocol {
8941061
}
8951062
}
8961063

1064+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
1065+
extension Grpc_Testing_UnimplementedService.ClientProtocol {
1066+
/// A call that no server should implement
1067+
public func unimplementedCall<Result>(
1068+
_ message: Grpc_Testing_Empty,
1069+
metadata: GRPCCore.Metadata = [:],
1070+
options: GRPCCore.CallOptions = .defaults,
1071+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_Empty>) async throws -> Result = {
1072+
try $0.message
1073+
}
1074+
) async throws -> Result where Result: Sendable {
1075+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_Empty>(
1076+
message: message,
1077+
metadata: metadata
1078+
)
1079+
return try await self.unimplementedCall(
1080+
request: request,
1081+
options: options,
1082+
handleResponse
1083+
)
1084+
}
1085+
}
1086+
8971087
/// A simple service NOT implemented at servers so clients can test for
8981088
/// that case.
8991089
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
@@ -980,6 +1170,47 @@ extension Grpc_Testing_ReconnectService.ClientProtocol {
9801170
}
9811171
}
9821172

1173+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
1174+
extension Grpc_Testing_ReconnectService.ClientProtocol {
1175+
public func start<Result>(
1176+
_ message: Grpc_Testing_ReconnectParams,
1177+
metadata: GRPCCore.Metadata = [:],
1178+
options: GRPCCore.CallOptions = .defaults,
1179+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_Empty>) async throws -> Result = {
1180+
try $0.message
1181+
}
1182+
) async throws -> Result where Result: Sendable {
1183+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_ReconnectParams>(
1184+
message: message,
1185+
metadata: metadata
1186+
)
1187+
return try await self.start(
1188+
request: request,
1189+
options: options,
1190+
handleResponse
1191+
)
1192+
}
1193+
1194+
public func stop<Result>(
1195+
_ message: Grpc_Testing_Empty,
1196+
metadata: GRPCCore.Metadata = [:],
1197+
options: GRPCCore.CallOptions = .defaults,
1198+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Testing_ReconnectInfo>) async throws -> Result = {
1199+
try $0.message
1200+
}
1201+
) async throws -> Result where Result: Sendable {
1202+
let request = GRPCCore.ClientRequest.Single<Grpc_Testing_Empty>(
1203+
message: message,
1204+
metadata: metadata
1205+
)
1206+
return try await self.stop(
1207+
request: request,
1208+
options: options,
1209+
handleResponse
1210+
)
1211+
}
1212+
}
1213+
9831214
/// A service used to control reconnect server.
9841215
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
9851216
public struct Grpc_Testing_ReconnectServiceClient: Grpc_Testing_ReconnectService.ClientProtocol {

Sources/Services/Health/Generated/health.grpc.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,69 @@ extension Grpc_Health_V1_Health.ClientProtocol {
253253
}
254254
}
255255

256+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
257+
extension Grpc_Health_V1_Health.ClientProtocol {
258+
/// Check gets the health of the specified service. If the requested service
259+
/// is unknown, the call will fail with status NOT_FOUND. If the caller does
260+
/// not specify a service name, the server should respond with its overall
261+
/// health status.
262+
///
263+
/// Clients should set a deadline when calling Check, and can declare the
264+
/// server unhealthy if they do not receive a timely response.
265+
///
266+
/// Check implementations should be idempotent and side effect free.
267+
package func check<Result>(
268+
_ message: Grpc_Health_V1_HealthCheckRequest,
269+
metadata: GRPCCore.Metadata = [:],
270+
options: GRPCCore.CallOptions = .defaults,
271+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Grpc_Health_V1_HealthCheckResponse>) async throws -> Result = {
272+
try $0.message
273+
}
274+
) async throws -> Result where Result: Sendable {
275+
let request = GRPCCore.ClientRequest.Single<Grpc_Health_V1_HealthCheckRequest>(
276+
message: message,
277+
metadata: metadata
278+
)
279+
return try await self.check(
280+
request: request,
281+
options: options,
282+
handleResponse
283+
)
284+
}
285+
286+
/// Performs a watch for the serving status of the requested service.
287+
/// The server will immediately send back a message indicating the current
288+
/// serving status. It will then subsequently send a new message whenever
289+
/// the service's serving status changes.
290+
///
291+
/// If the requested service is unknown when the call is received, the
292+
/// server will send a message setting the serving status to
293+
/// SERVICE_UNKNOWN but will *not* terminate the call. If at some
294+
/// future point, the serving status of the service becomes known, the
295+
/// server will send a new message with the service's serving status.
296+
///
297+
/// If the call terminates with status UNIMPLEMENTED, then clients
298+
/// should assume this method is not supported and should not retry the
299+
/// call. If the call terminates with any other status (including OK),
300+
/// clients should retry the call with appropriate exponential backoff.
301+
package func watch<Result>(
302+
_ message: Grpc_Health_V1_HealthCheckRequest,
303+
metadata: GRPCCore.Metadata = [:],
304+
options: GRPCCore.CallOptions = .defaults,
305+
onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream<Grpc_Health_V1_HealthCheckResponse>) async throws -> Result
306+
) async throws -> Result where Result: Sendable {
307+
let request = GRPCCore.ClientRequest.Single<Grpc_Health_V1_HealthCheckRequest>(
308+
message: message,
309+
metadata: metadata
310+
)
311+
return try await self.watch(
312+
request: request,
313+
options: options,
314+
handleResponse
315+
)
316+
}
317+
}
318+
256319
/// Health is gRPC's mechanism for checking whether a server is able to handle
257320
/// RPCs. Its semantics are documented in
258321
/// https://github.com/grpc/grpc/blob/master/doc/health-checking.md.

0 commit comments

Comments
 (0)