Skip to content

Commit 7986535

Browse files
authored
Simplify client and server init (#1735)
Motivation: When configuring a client and server must users will specify only the transport and services (for a server) and potentially interceptors too. This should be the "easy" path. Moreover these are all reference types so it makes sense to separate them from "raw" configuration. Creating a client also diverged somewhat from the server. Having both follow a similar pattern simplifies things for users. Modifications: - Move interceptors from the client config to the init. - Modify the server to follow a similar pattern to the server. Results: Server and client are configured similarly.
1 parent 39c567a commit 7986535

File tree

6 files changed

+186
-389
lines changed

6 files changed

+186
-389
lines changed

Sources/GRPCCore/ClientError.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ extension ClientError {
109109
public struct Code: Hashable, Sendable {
110110
private enum Value {
111111
case clientIsAlreadyRunning
112-
case clientIsNotRunning
113112
case clientIsStopped
114113
case transportError
115114
}
@@ -124,11 +123,6 @@ extension ClientError {
124123
Self(.clientIsAlreadyRunning)
125124
}
126125

127-
/// An attempt to start an RPC was made but the client is not running.
128-
public static var clientIsNotRunning: Self {
129-
Self(.clientIsNotRunning)
130-
}
131-
132126
/// At attempt to start the client was made but it has already stopped.
133127
public static var clientIsStopped: Self {
134128
Self(.clientIsStopped)

Sources/GRPCCore/GRPCClient.swift

Lines changed: 34 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ import Atomics
4040
/// // Create a configuration object for the client.
4141
/// var configuration = GRPCClient.Configuration()
4242
///
43-
/// // Create and add an interceptor.
44-
/// configuration.interceptors.add(StatsRecordingClientInterceptor())
45-
///
4643
/// // Override the timeout for the 'Get' method on the 'echo.Echo' service. This configuration
4744
/// // takes precedence over any set by the transport.
4845
/// let echoGet = MethodDescriptor(service: "echo.Echo", method: "Get")
@@ -56,10 +53,15 @@ import Atomics
5653
/// let defaultMethodConfiguration = MethodConfiguration(executionPolicy: nil, timeout: seconds(10))
5754
/// configuration.method.defaults.setDefaultConfiguration(defaultMethodConfiguration)
5855
///
59-
/// // Finally create a transport and instantiate the client.
56+
/// // Finally create a transport and instantiate the client, adding an interceptor.
6057
/// let inProcessServerTransport = InProcessServerTransport()
6158
/// let inProcessClientTransport = InProcessClientTransport(serverTransport: inProcessServerTransport)
62-
/// let client = GRPCClient(transport: inProcessClientTransport, configuration: configuration)
59+
///
60+
/// let client = GRPCClient(
61+
/// transport: inProcessClientTransport,
62+
/// interceptors: [StatsRecordingClientInterceptor()],
63+
/// configuration: configuration
64+
/// )
6365
/// ```
6466
///
6567
/// ## Starting and stopping the client
@@ -101,6 +103,14 @@ public struct GRPCClient: Sendable {
101103
/// The transport which provides a bidirectional communication channel with the server.
102104
private let transport: any ClientTransport
103105

106+
/// A collection of interceptors providing cross-cutting functionality to each accepted RPC.
107+
///
108+
/// The order in which interceptors are added reflects the order in which they are called. The
109+
/// first interceptor added will be the first interceptor to intercept each request. The last
110+
/// interceptor added will be the final interceptor to intercept each request before calling
111+
/// the appropriate handler.
112+
private let interceptors: [any ClientInterceptor]
113+
104114
/// The configuration used by the client.
105115
public let configuration: Configuration
106116

@@ -121,13 +131,23 @@ public struct GRPCClient: Sendable {
121131
case stopped
122132
}
123133

124-
/// Creates a new client with the given transport and configuration.
134+
/// Creates a new client with the given transport, interceptors and configuration.
125135
///
126136
/// - Parameters:
127137
/// - transport: The transport used to establish a communication channel with a server.
138+
/// - interceptors: A collection of interceptors providing cross-cutting functionality to each
139+
/// accepted RPC. The order in which interceptors are added reflects the order in which they
140+
/// are called. The first interceptor added will be the first interceptor to intercept each
141+
/// request. The last interceptor added will be the final interceptor to intercept each
142+
/// request before calling the appropriate handler.
128143
/// - configuration: Configuration for the client.
129-
public init(transport: some ClientTransport, configuration: Configuration = Configuration()) {
144+
public init(
145+
transport: some ClientTransport,
146+
interceptors: [any ClientInterceptor] = [],
147+
configuration: Configuration = Configuration()
148+
) {
130149
self.transport = transport
150+
self.interceptors = interceptors
131151
self.configuration = configuration
132152
self.state = ManagedAtomic(.notStarted)
133153
}
@@ -250,7 +270,7 @@ public struct GRPCClient: Sendable {
250270
deserializer: some MessageDeserializer<Response>,
251271
handler: @Sendable @escaping (ClientResponse.Single<Response>) async throws -> ReturnValue
252272
) async throws -> ReturnValue {
253-
try await bidirectionalStreaming(
273+
try await self.bidirectionalStreaming(
254274
request: ClientRequest.Stream(single: request),
255275
descriptor: descriptor,
256276
serializer: serializer,
@@ -278,7 +298,7 @@ public struct GRPCClient: Sendable {
278298
deserializer: some MessageDeserializer<Response>,
279299
handler: @Sendable @escaping (ClientResponse.Single<Response>) async throws -> ReturnValue
280300
) async throws -> ReturnValue {
281-
try await bidirectionalStreaming(
301+
try await self.bidirectionalStreaming(
282302
request: request,
283303
descriptor: descriptor,
284304
serializer: serializer,
@@ -306,7 +326,7 @@ public struct GRPCClient: Sendable {
306326
deserializer: some MessageDeserializer<Response>,
307327
handler: @Sendable @escaping (ClientResponse.Stream<Response>) async throws -> ReturnValue
308328
) async throws -> ReturnValue {
309-
try await bidirectionalStreaming(
329+
try await self.bidirectionalStreaming(
310330
request: ClientRequest.Stream(single: request),
311331
descriptor: descriptor,
312332
serializer: serializer,
@@ -336,13 +356,10 @@ public struct GRPCClient: Sendable {
336356
handler: @Sendable @escaping (ClientResponse.Stream<Response>) async throws -> ReturnValue
337357
) async throws -> ReturnValue {
338358
switch self.state.load(ordering: .sequentiallyConsistent) {
339-
case .running:
359+
case .notStarted, .running:
360+
// Allow .notStarted as making a request can race with 'run()'. Transports should tolerate
361+
// queuing the request if not yet started.
340362
()
341-
case .notStarted:
342-
throw ClientError(
343-
code: .clientIsNotRunning,
344-
message: "Client must be running to make an RPC: call run() first."
345-
)
346363
case .stopping, .stopped:
347364
throw ClientError(
348365
code: .clientIsStopped,
@@ -357,7 +374,7 @@ public struct GRPCClient: Sendable {
357374
serializer: serializer,
358375
deserializer: deserializer,
359376
transport: self.transport,
360-
interceptors: self.configuration.interceptors.values,
377+
interceptors: self.interceptors,
361378
handler: handler
362379
)
363380
}
@@ -383,14 +400,6 @@ public struct GRPCClient: Sendable {
383400
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
384401
extension GRPCClient {
385402
public struct Configuration: Sendable {
386-
/// A collection of interceptors providing cross-cutting functionality to each accepted RPC.
387-
///
388-
/// The order in which interceptors are added reflects the order in which they are called. The
389-
/// first interceptor added will be the first interceptor to intercept each request. The last
390-
/// interceptor added will be the final interceptor to intercept each request before calling
391-
/// the appropriate handler.
392-
public var interceptors: Interceptors
393-
394403
/// Configuration for how methods are executed.
395404
///
396405
/// Method configuration determines how each RPC is executed by the client. Some services and
@@ -401,48 +410,13 @@ extension GRPCClient {
401410

402411
/// Creates a new default configuration.
403412
public init() {
404-
self.interceptors = Interceptors()
405413
self.method = Method()
406414
}
407415
}
408416
}
409417

410418
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
411419
extension GRPCClient.Configuration {
412-
/// A collection of ``ClientInterceptor`` implementations which are applied to all accepted
413-
/// RPCs.
414-
///
415-
/// RPCs are intercepted in the order that interceptors are added. That is, a request sent from the client to
416-
/// the server will first be intercepted by the first added interceptor followed by the second, and so on.
417-
/// For responses from the server, they'll be applied in the opposite order.
418-
public struct Interceptors: Sendable {
419-
private(set) var values: [any ClientInterceptor] = []
420-
421-
/// Add an interceptor to the client.
422-
///
423-
/// The order in which interceptors are added reflects the order in which they are called. The
424-
/// first interceptor added will be the first interceptor to intercept each request. The last
425-
/// interceptor added will be the final interceptor to intercept each request before calling
426-
/// the appropriate handler.
427-
///
428-
/// - Parameter interceptor: The interceptor to add.
429-
public mutating func add(_ interceptor: some ClientInterceptor) {
430-
self.values.append(interceptor)
431-
}
432-
433-
/// Adds a sequence of interceptor to the client.
434-
///
435-
/// The order in which interceptors are added reflects the order in which they are called. The
436-
/// first interceptor added will be the first interceptor to intercept each request. The last
437-
/// interceptor added will be the final interceptor to intercept each request before calling
438-
/// the appropriate handler.
439-
///
440-
/// - Parameter interceptors: The interceptors to add.
441-
public mutating func add(contentsOf interceptors: some Sequence<ClientInterceptor>) {
442-
self.values.append(contentsOf: interceptors)
443-
}
444-
}
445-
446420
/// Configuration for how methods should be executed.
447421
///
448422
/// In most cases the client should defer to the configuration provided by the transport as this
@@ -464,10 +438,3 @@ extension GRPCClient.Configuration {
464438
}
465439
}
466440
}
467-
468-
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
469-
extension GRPCClient.Configuration.Interceptors: CustomStringConvertible {
470-
public var description: String {
471-
return String(describing: self.values.map { String(describing: type(of: $0)) })
472-
}
473-
}

0 commit comments

Comments
 (0)