@@ -28,79 +28,25 @@ private import Synchronization
28
28
///
29
29
/// However, in most cases you should prefer wrapping the ``GRPCClient`` with a generated stub.
30
30
///
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
34
32
///
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.
38
37
///
39
38
/// ```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
+ /// }
75
43
/// ```
76
44
///
77
- /// ## Starting and stopping the client
45
+ /// ## Creating a client manually
78
46
///
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.
104
50
///
105
51
/// The ``run()`` method won't return until the client has finished handling all requests. You can
106
52
/// signal to the client that it should stop creating new request streams by calling ``beginGracefulShutdown()``.
@@ -425,3 +371,62 @@ public final class GRPCClient: Sendable {
425
371
)
426
372
}
427
373
}
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