@@ -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:isolation:handleClient:)``
34+ /// or ``withGRPCClient(transport:interceptorPipeline:isolation: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,62 @@ 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+ /// - isolation: A reference to the actor to which the enclosing code is isolated, or nil if the
385+ /// code is nonisolated.
386+ /// - handleClient: A closure which is called with the client. When the closure returns, the
387+ /// client is shutdown gracefully.
388+ public func withGRPCClient< Result: Sendable > (
389+ transport: some ClientTransport ,
390+ interceptors: [ any ClientInterceptor ] = [ ] ,
391+ isolation: isolated ( any Actor ) ? = #isolation,
392+ handleClient: ( GRPCClient ) async throws -> Result
393+ ) async throws -> Result {
394+ try await withGRPCClient (
395+ transport: transport,
396+ interceptorPipeline: interceptors. map { . apply( $0, to: . all) } ,
397+ isolation: isolation,
398+ handleClient: handleClient
399+ )
400+ }
401+
402+ /// Creates and runs a new client with the given transport and interceptors.
403+ ///
404+ /// - Parameters:
405+ /// - transport: The transport used to establish a communication channel with a server.
406+ /// - interceptorPipeline: A collection of ``ClientInterceptorPipelineOperation`` providing cross-cutting
407+ /// functionality to each accepted RPC. Only applicable interceptors from the pipeline will be applied to each RPC.
408+ /// The order in which interceptors are added reflects the order in which they are called.
409+ /// The first interceptor added will be the first interceptor to intercept each request.
410+ /// The last interceptor added will be the final interceptor to intercept each request before calling the appropriate handler.
411+ /// - isolation: A reference to the actor to which the enclosing code is isolated, or nil if the
412+ /// code is nonisolated.
413+ /// - handleClient: A closure which is called with the client. When the closure returns, the
414+ /// client is shutdown gracefully.
415+ /// - Returns: The result of the `handleClient` closure.
416+ public func withGRPCClient< Result: Sendable > (
417+ transport: some ClientTransport ,
418+ interceptorPipeline: [ ClientInterceptorPipelineOperation ] ,
419+ isolation: isolated ( any Actor ) ? = #isolation,
420+ handleClient: ( GRPCClient ) async throws -> Result
421+ ) async throws -> Result {
422+ try await withThrowingDiscardingTaskGroup { group in
423+ let client = GRPCClient ( transport: transport, interceptorPipeline: interceptorPipeline)
424+ group. addTask {
425+ try await client. run ( )
426+ }
427+
428+ let result = try await handleClient ( client)
429+ client. beginGracefulShutdown ( )
430+ return result
431+ }
432+ }
0 commit comments