@@ -28,79 +28,25 @@ private import Synchronization
2828///
2929/// However, in most cases you should prefer wrapping the ``GRPCClient`` with a generated stub.
3030///
31- /// You can set ``ServiceConfig``s on this client to override whatever configurations have been
32- /// set on the given transport. You can also use ``ClientInterceptor``s to implement cross-cutting
33- /// logic which apply to all RPCs. Example uses of interceptors include authentication and logging.
31+ /// ## Creating a client
3432///
35- /// ## Creating and configuring a client
36- ///
37- /// The following example demonstrates how to create and configure a client.
33+ /// You can create and run a client using ``withGRPCClient(transport:interceptors:handleClient:)``
34+ /// or ``withGRPCClient(transport:interceptorPipeline:handleClient:)`` which create, configure and
35+ /// run the client providing scoped access to it via the `handleClient` closure. The client will
36+ /// begin gracefully shutting down when the closure returns.
3837///
3938/// ```swift
40- /// // Create a configuration object for the client and override the timeout for the 'Get' method on
41- /// // the 'echo.Echo' service. This configuration takes precedence over any set by the transport.
42- /// var configuration = GRPCClient.Configuration()
43- /// configuration.service.override = ServiceConfig(
44- /// methodConfig: [
45- /// MethodConfig(
46- /// names: [
47- /// MethodConfig.Name(service: "echo.Echo", method: "Get")
48- /// ],
49- /// timeout: .seconds(5)
50- /// )
51- /// ]
52- /// )
53- ///
54- /// // Configure a fallback timeout for all RPCs (indicated by an empty service and method name) if
55- /// // no configuration is provided in the overrides or by the transport.
56- /// configuration.service.defaults = ServiceConfig(
57- /// methodConfig: [
58- /// MethodConfig(
59- /// names: [
60- /// MethodConfig.Name(service: "", method: "")
61- /// ],
62- /// timeout: .seconds(10)
63- /// )
64- /// ]
65- /// )
66- ///
67- /// // Finally create a transport and instantiate the client, adding an interceptor.
68- /// let inProcessTransport = InProcessTransport()
69- ///
70- /// let client = GRPCClient(
71- /// transport: inProcessTransport.client,
72- /// interceptors: [StatsRecordingClientInterceptor()],
73- /// configuration: configuration
74- /// )
39+ /// let transport: any ClientTransport = ...
40+ /// try await withGRPCClient(transport: transport) { client in
41+ /// // ...
42+ /// }
7543/// ```
7644///
77- /// ## Starting and stopping the client
45+ /// ## Creating a client manually
7846///
79- /// Once you have configured the client, call ``run()`` to start it. Calling ``run()`` instructs the
80- /// transport to start connecting to the server.
81- ///
82- /// ```swift
83- /// // Start running the client. 'run()' must be running while RPCs are execute so it's executed in
84- /// // a task group.
85- /// try await withThrowingTaskGroup(of: Void.self) { group in
86- /// group.addTask {
87- /// try await client.run()
88- /// }
89- ///
90- /// // Execute a request against the "echo.Echo" service.
91- /// try await client.unary(
92- /// request: ClientRequest<[UInt8]>(message: [72, 101, 108, 108, 111, 33]),
93- /// descriptor: MethodDescriptor(service: "echo.Echo", method: "Get"),
94- /// serializer: IdentitySerializer(),
95- /// deserializer: IdentityDeserializer(),
96- /// ) { response in
97- /// print(response.message)
98- /// }
99- ///
100- /// // The RPC has completed, close the client.
101- /// client.beginGracefulShutdown()
102- /// }
103- /// ```
47+ /// If the `with`-style methods for creating clients isn't suitable for your application then you
48+ /// can create and run a client manually. This requires you to call the ``run()`` method in a task
49+ /// which instructs the client to start connecting to the server.
10450///
10551/// The ``run()`` method won't return until the client has finished handling all requests. You can
10652/// signal to the client that it should stop creating new request streams by calling ``beginGracefulShutdown()``.
@@ -425,3 +371,55 @@ public final class GRPCClient: Sendable {
425371 )
426372 }
427373}
374+
375+ /// Creates and runs a new client with the given transport and interceptors.
376+ ///
377+ /// - Parameters:
378+ /// - transport: The transport used to establish a communication channel with a server.
379+ /// - interceptors: A collection of ``ClientInterceptor``s providing cross-cutting functionality to each
380+ /// accepted RPC. The order in which interceptors are added reflects the order in which they
381+ /// are called. The first interceptor added will be the first interceptor to intercept each
382+ /// request. The last interceptor added will be the final interceptor to intercept each
383+ /// request before calling the appropriate handler.
384+ /// - handleClient: A closure which is called with the client. When the closure returns, the
385+ /// client is shutdown gracefully.
386+ public func withGRPCClient< Result: Sendable > (
387+ transport: some ClientTransport ,
388+ interceptors: [ any ClientInterceptor ] = [ ] ,
389+ handleClient: ( GRPCClient ) async throws -> Result
390+ ) async throws -> Result {
391+ try await withGRPCClient (
392+ transport: transport,
393+ interceptorPipeline: interceptors. map { . apply( $0, to: . all) } ,
394+ handleClient: handleClient
395+ )
396+ }
397+
398+ /// Creates and runs a new client with the given transport and interceptors.
399+ ///
400+ /// - Parameters:
401+ /// - transport: The transport used to establish a communication channel with a server.
402+ /// - interceptorPipeline: A collection of ``ClientInterceptorPipelineOperation`` providing cross-cutting
403+ /// functionality to each accepted RPC. Only applicable interceptors from the pipeline will be applied to each RPC.
404+ /// The order in which interceptors are added reflects the order in which they are called.
405+ /// The first interceptor added will be the first interceptor to intercept each request.
406+ /// The last interceptor added will be the final interceptor to intercept each request before calling the appropriate handler.
407+ /// - handleClient: A closure which is called with the client. When the closure returns, the
408+ /// client is shutdown gracefully.
409+ /// - Returns: The result of the `handleClient` closure.
410+ public func withGRPCClient< Result: Sendable > (
411+ transport: some ClientTransport ,
412+ interceptorPipeline: [ ClientInterceptorPipelineOperation ] ,
413+ handleClient: ( GRPCClient ) async throws -> Result
414+ ) async throws -> Result {
415+ try await withThrowingDiscardingTaskGroup { group in
416+ let client = GRPCClient ( transport: transport, interceptorPipeline: interceptorPipeline)
417+ group. addTask {
418+ try await client. run ( )
419+ }
420+
421+ let result = try await handleClient ( client)
422+ client. beginGracefulShutdown ( )
423+ return result
424+ }
425+ }
0 commit comments