Skip to content

Commit eb7ed6f

Browse files
glbrnttgjcairo
andauthored
Deduplicate interceptor pipeline operation (#2157)
Motivation: ClientInterceptorPipelineOperation is the same as ServerInterceptorPipelineOperation apart from the type of interceptor being used. The duplication here is unnecessary. Modifications: - Add a `ConditionalInterceptor` which is roughly the same as `*InterceptorPipelineOperation` but is generic. - The init is private and can only be initialized via static methods when the generic type is appropriate (i.e. client/server interceptor). Result: Less duplication, less code --------- Co-authored-by: Gus Cairo <[email protected]>
1 parent eafb334 commit eb7ed6f

12 files changed

+61
-221
lines changed

Sources/GRPCCore/Call/Client/ClientInterceptor.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
/// received from the transport. They are typically used for cross-cutting concerns like injecting
2424
/// metadata, validating messages, logging additional data, and tracing.
2525
///
26-
/// Interceptors are registered with the client via ``ClientInterceptorPipelineOperation``s.
26+
/// Interceptors are registered with the client via ``ConditionalInterceptor``s.
2727
/// You may register them for all services registered with a server, for RPCs directed to specific services, or
2828
/// for RPCs directed to specific methods. If you need to modify the behavior of an interceptor on a
2929
/// per-RPC basis in more detail, then you can use the ``ClientContext/descriptor`` to determine

Sources/GRPCCore/Call/Client/ClientInterceptorPipelineOperation.swift renamed to Sources/GRPCCore/Call/ConditionalInterceptor.swift

+36-23
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,16 @@
1414
* limitations under the License.
1515
*/
1616

17-
/// A `ClientInterceptorPipelineOperation` describes to which RPCs a client interceptor should be applied.
17+
/// Describes the conditions under which an interceptor should be applied.
1818
///
19-
/// You can configure a client interceptor to be applied to:
19+
/// You can configure interceptors to be applied to:
2020
/// - all RPCs and services;
2121
/// - requests directed only to specific services; or
2222
/// - requests directed only to specific methods (of a specific service).
2323
///
24-
/// - SeeAlso: ``ClientInterceptor`` for more information on client interceptors, and
25-
/// ``ServerInterceptorPipelineOperation`` for the server-side version of this type.
26-
public struct ClientInterceptorPipelineOperation: Sendable {
27-
/// The subject of a ``ClientInterceptorPipelineOperation``.
28-
/// The subject of an interceptor can either be all services and methods, only specific services, or only specific methods.
24+
/// - SeeAlso: ``ClientInterceptor`` and ``ServerInterceptor`` for more information on client and
25+
/// server interceptors, respectively.
26+
public struct ConditionalInterceptor<Interceptor: Sendable>: Sendable {
2927
public struct Subject: Sendable {
3028
internal enum Wrapped: Sendable {
3129
case all
@@ -41,21 +39,19 @@ public struct ClientInterceptorPipelineOperation: Sendable {
4139
/// An operation subject specifying an interceptor that will be applied only to RPCs directed to the specified services.
4240
/// - Parameters:
4341
/// - services: The list of service names for which this interceptor should intercept RPCs.
44-
/// - Returns: A ``ClientInterceptorPipelineOperation``.
4542
public static func services(_ services: Set<ServiceDescriptor>) -> Self {
4643
Self(wrapped: .services(services))
4744
}
4845

4946
/// An operation subject specifying an interceptor that will be applied only to RPCs directed to the specified service methods.
5047
/// - Parameters:
5148
/// - methods: The list of method descriptors for which this interceptor should intercept RPCs.
52-
/// - Returns: A ``ClientInterceptorPipelineOperation``.
5349
public static func methods(_ methods: Set<MethodDescriptor>) -> Self {
5450
Self(wrapped: .methods(methods))
5551
}
5652

5753
@usableFromInline
58-
internal func applies(to descriptor: MethodDescriptor) -> Bool {
54+
package func applies(to descriptor: MethodDescriptor) -> Bool {
5955
switch self.wrapped {
6056
case .all:
6157
return true
@@ -69,24 +65,15 @@ public struct ClientInterceptorPipelineOperation: Sendable {
6965
}
7066
}
7167

72-
/// The interceptor specified for this operation.
73-
public let interceptor: any ClientInterceptor
68+
/// The interceptor.
69+
public let interceptor: Interceptor
7470

7571
@usableFromInline
7672
internal let subject: Subject
7773

78-
private init(interceptor: any ClientInterceptor, appliesTo: Subject) {
74+
fileprivate init(interceptor: Interceptor, subject: Subject) {
7975
self.interceptor = interceptor
80-
self.subject = appliesTo
81-
}
82-
83-
/// Create an operation, specifying which ``ClientInterceptor`` to apply and to which ``Subject``.
84-
/// - Parameters:
85-
/// - interceptor: The ``ClientInterceptor`` to register with the client.
86-
/// - subject: The ``Subject`` to which the `interceptor` applies.
87-
/// - Returns: A ``ClientInterceptorPipelineOperation``.
88-
public static func apply(_ interceptor: any ClientInterceptor, to subject: Subject) -> Self {
89-
Self(interceptor: interceptor, appliesTo: subject)
76+
self.subject = subject
9077
}
9178

9279
/// Returns whether this ``ClientInterceptorPipelineOperation`` applies to the given `descriptor`.
@@ -97,3 +84,29 @@ public struct ClientInterceptorPipelineOperation: Sendable {
9784
self.subject.applies(to: descriptor)
9885
}
9986
}
87+
88+
extension ConditionalInterceptor where Interceptor == any ClientInterceptor {
89+
/// Create an operation, specifying which ``ClientInterceptor`` to apply and to which ``Subject``.
90+
/// - Parameters:
91+
/// - interceptor: The ``ClientInterceptor`` to register with the client.
92+
/// - subject: The ``Subject`` to which the `interceptor` applies.
93+
public static func apply(
94+
_ interceptor: any ClientInterceptor,
95+
to subject: Subject
96+
) -> Self {
97+
Self(interceptor: interceptor, subject: subject)
98+
}
99+
}
100+
101+
extension ConditionalInterceptor where Interceptor == any ServerInterceptor {
102+
/// Create an operation, specifying which ``ServerInterceptor`` to apply and to which ``Subject``.
103+
/// - Parameters:
104+
/// - interceptor: The ``ServerInterceptor`` to register with the server.
105+
/// - subject: The ``Subject`` to which the `interceptor` applies.
106+
public static func apply(
107+
_ interceptor: any ServerInterceptor,
108+
to subject: Subject
109+
) -> Self {
110+
Self(interceptor: interceptor, subject: subject)
111+
}
112+
}

Sources/GRPCCore/Call/Server/RPCRouter.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,10 @@ public struct RPCRouter: Sendable {
155155
/// only call this method _after_ you have registered all handlers.
156156
/// - Parameter pipeline: The interceptor pipeline operations to register to all currently-registered handlers. The order of the
157157
/// interceptors matters.
158-
/// - SeeAlso: ``ServerInterceptorPipelineOperation``.
159158
@inlinable
160-
public mutating func registerInterceptors(pipeline: [ServerInterceptorPipelineOperation]) {
159+
public mutating func registerInterceptors(
160+
pipeline: [ConditionalInterceptor<any ServerInterceptor>]
161+
) {
161162
for descriptor in self.handlers.keys {
162163
let applicableOperations = pipeline.filter { $0.applies(to: descriptor) }
163164
if !applicableOperations.isEmpty {

Sources/GRPCCore/Call/Server/ServerInterceptor.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
/// been returned from a service. They are typically used for cross-cutting concerns like filtering
2222
/// requests, validating messages, logging additional data, and tracing.
2323
///
24-
/// Interceptors can be registered with the server either directly or via ``ServerInterceptorPipelineOperation``s.
24+
/// Interceptors can be registered with the server either directly or via ``ConditionalInterceptor``s.
2525
/// You may register them for all services registered with a server, for RPCs directed to specific services, or
2626
/// for RPCs directed to specific methods. If you need to modify the behavior of an interceptor on a
2727
/// per-RPC basis in more detail, then you can use the ``ServerContext/descriptor`` to determine

Sources/GRPCCore/Call/Server/ServerInterceptorPipelineOperation.swift

-99
This file was deleted.

Sources/GRPCCore/Documentation.docc/Documentation.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ Resources for developers working on gRPC Swift:
9696
- ``ServerInterceptor``
9797
- ``ClientContext``
9898
- ``ServerContext``
99-
- ``ClientInterceptorPipelineOperation``
100-
- ``ServerInterceptorPipelineOperation``
99+
- ``ConditionalInterceptor``
101100

102101
### RPC descriptors
103102

Sources/GRPCCore/GRPCClient.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public final class GRPCClient: Sendable {
129129
private struct StateMachine {
130130
var state: State
131131

132-
private let interceptorPipeline: [ClientInterceptorPipelineOperation]
132+
private let interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>]
133133

134134
/// A collection of interceptors providing cross-cutting functionality to each accepted RPC, keyed by the method to which they apply.
135135
///
@@ -142,7 +142,7 @@ public final class GRPCClient: Sendable {
142142
/// the appropriate handler.
143143
var interceptorsPerMethod: [MethodDescriptor: [any ClientInterceptor]]
144144

145-
init(interceptorPipeline: [ClientInterceptorPipelineOperation]) {
145+
init(interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>]) {
146146
self.state = .notStarted
147147
self.interceptorPipeline = interceptorPipeline
148148
self.interceptorsPerMethod = [:]
@@ -188,14 +188,14 @@ public final class GRPCClient: Sendable {
188188
///
189189
/// - Parameters:
190190
/// - transport: The transport used to establish a communication channel with a server.
191-
/// - interceptorPipeline: A collection of ``ClientInterceptorPipelineOperation`` providing cross-cutting
191+
/// - interceptorPipeline: A collection of ``ConditionalInterceptor``s providing cross-cutting
192192
/// functionality to each accepted RPC. Only applicable interceptors from the pipeline will be applied to each RPC.
193193
/// The order in which interceptors are added reflects the order in which they are called.
194194
/// The first interceptor added will be the first interceptor to intercept each request.
195195
/// The last interceptor added will be the final interceptor to intercept each request before calling the appropriate handler.
196196
public init(
197197
transport: some ClientTransport,
198-
interceptorPipeline: [ClientInterceptorPipelineOperation]
198+
interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>]
199199
) {
200200
self.transport = transport
201201
self.stateMachine = Mutex(StateMachine(interceptorPipeline: interceptorPipeline))
@@ -416,7 +416,7 @@ public func withGRPCClient<Result: Sendable>(
416416
///
417417
/// - Parameters:
418418
/// - transport: The transport used to establish a communication channel with a server.
419-
/// - interceptorPipeline: A collection of ``ClientInterceptorPipelineOperation`` providing cross-cutting
419+
/// - interceptorPipeline: A collection of ``ConditionalInterceptor``s providing cross-cutting
420420
/// functionality to each accepted RPC. Only applicable interceptors from the pipeline will be applied to each RPC.
421421
/// The order in which interceptors are added reflects the order in which they are called.
422422
/// The first interceptor added will be the first interceptor to intercept each request.
@@ -428,7 +428,7 @@ public func withGRPCClient<Result: Sendable>(
428428
/// - Returns: The result of the `handleClient` closure.
429429
public func withGRPCClient<Result: Sendable>(
430430
transport: some ClientTransport,
431-
interceptorPipeline: [ClientInterceptorPipelineOperation],
431+
interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>],
432432
isolation: isolated (any Actor)? = #isolation,
433433
handleClient: (GRPCClient) async throws -> Result
434434
) async throws -> Result {

Sources/GRPCCore/GRPCServer.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public final class GRPCServer: Sendable {
171171
public convenience init(
172172
transport: any ServerTransport,
173173
services: [any RegistrableRPCService],
174-
interceptorPipeline: [ServerInterceptorPipelineOperation]
174+
interceptorPipeline: [ConditionalInterceptor<any ServerInterceptor>]
175175
) {
176176
var router = RPCRouter()
177177
for service in services {
@@ -290,7 +290,7 @@ public func withGRPCServer<Result: Sendable>(
290290
public func withGRPCServer<Result: Sendable>(
291291
transport: any ServerTransport,
292292
services: [any RegistrableRPCService],
293-
interceptorPipeline: [ServerInterceptorPipelineOperation],
293+
interceptorPipeline: [ConditionalInterceptor<any ServerInterceptor>],
294294
isolation: isolated (any Actor)? = #isolation,
295295
handleServer: (GRPCServer) async throws -> Result
296296
) async throws -> Result {

Tests/GRPCCoreTests/Call/Client/ClientInterceptorPipelineOperationTests.swift renamed to Tests/GRPCCoreTests/Call/ConditionalInterceptorTests.swift

+7-13
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17+
import GRPCCore
1718
import Testing
1819

19-
@testable import GRPCCore
20-
21-
@Suite("ClientInterceptorPipelineOperation")
22-
struct ClientInterceptorPipelineOperationTests {
20+
@Suite("ConditionalInterceptor")
21+
struct ConditionalInterceptorTests {
2322
@Test(
2423
"Applies to",
2524
arguments: [
@@ -38,24 +37,19 @@ struct ClientInterceptorPipelineOperationTests {
3837
[.barFoo],
3938
[.fooBar, .fooBaz, .barBaz]
4039
),
41-
] as [(ClientInterceptorPipelineOperation.Subject, [MethodDescriptor], [MethodDescriptor])]
40+
] as [(ConditionalInterceptor<any Sendable>.Subject, [MethodDescriptor], [MethodDescriptor])]
4241
)
4342
func appliesTo(
44-
operationSubject: ClientInterceptorPipelineOperation.Subject,
43+
target: ConditionalInterceptor<any Sendable>.Subject,
4544
applicableMethods: [MethodDescriptor],
4645
notApplicableMethods: [MethodDescriptor]
4746
) {
48-
let operation = ClientInterceptorPipelineOperation.apply(
49-
.requestCounter(.init()),
50-
to: operationSubject
51-
)
52-
5347
for applicableMethod in applicableMethods {
54-
#expect(operation.applies(to: applicableMethod))
48+
#expect(target.applies(to: applicableMethod))
5549
}
5650

5751
for notApplicableMethod in notApplicableMethods {
58-
#expect(!operation.applies(to: notApplicableMethod))
52+
#expect(!target.applies(to: notApplicableMethod))
5953
}
6054
}
6155
}

0 commit comments

Comments
 (0)