Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/GRPCCore/Call/Client/ClientInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
/// received from the transport. They are typically used for cross-cutting concerns like injecting
/// metadata, validating messages, logging additional data, and tracing.
///
/// Interceptors are registered with the server via ``ClientInterceptorPipelineOperation``s.
/// Interceptors are registered with the server via ``ConditionalInterceptor``s.
/// You may register them for all services registered with a server, for RPCs directed to specific services, or
/// for RPCs directed to specific methods. If you need to modify the behavior of an interceptor on a
/// per-RPC basis in more detail, then you can use the ``ClientContext/descriptor`` to determine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,16 @@
* limitations under the License.
*/

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

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

@usableFromInline
internal func applies(to descriptor: MethodDescriptor) -> Bool {
package func applies(to descriptor: MethodDescriptor) -> Bool {
switch self.wrapped {
case .all:
return true
Expand All @@ -69,24 +65,15 @@ public struct ClientInterceptorPipelineOperation: Sendable {
}
}

/// The interceptor specified for this operation.
public let interceptor: any ClientInterceptor
/// The interceptor.
public let interceptor: Interceptor

@usableFromInline
internal let subject: Subject

private init(interceptor: any ClientInterceptor, appliesTo: Subject) {
fileprivate init(interceptor: Interceptor, subject: Subject) {
self.interceptor = interceptor
self.subject = appliesTo
}

/// Create an operation, specifying which ``ClientInterceptor`` to apply and to which ``Subject``.
/// - Parameters:
/// - interceptor: The ``ClientInterceptor`` to register with the client.
/// - subject: The ``Subject`` to which the `interceptor` applies.
/// - Returns: A ``ClientInterceptorPipelineOperation``.
public static func apply(_ interceptor: any ClientInterceptor, to subject: Subject) -> Self {
Self(interceptor: interceptor, appliesTo: subject)
self.subject = subject
}

/// Returns whether this ``ClientInterceptorPipelineOperation`` applies to the given `descriptor`.
Expand All @@ -97,3 +84,29 @@ public struct ClientInterceptorPipelineOperation: Sendable {
self.subject.applies(to: descriptor)
}
}

extension ConditionalInterceptor where Interceptor == any ClientInterceptor {
/// Create an operation, specifying which ``ClientInterceptor`` to apply and to which ``Subject``.
/// - Parameters:
/// - interceptor: The ``ClientInterceptor`` to register with the client.
/// - subject: The ``Subject`` to which the `interceptor` applies.
public static func apply(
_ interceptor: any ClientInterceptor,
to subject: Subject
) -> Self {
Self(interceptor: interceptor, subject: subject)
}
}

extension ConditionalInterceptor where Interceptor == any ServerInterceptor {
/// Create an operation, specifying which ``ServerInterceptor`` to apply and to which ``Subject``.
/// - Parameters:
/// - interceptor: The ``ServerInterceptor`` to register with the client.
/// - subject: The ``Subject`` to which the `interceptor` applies.
public static func apply(
_ interceptor: any ServerInterceptor,
to subject: Subject
) -> Self {
Self(interceptor: interceptor, subject: subject)
}
}
5 changes: 3 additions & 2 deletions Sources/GRPCCore/Call/Server/RPCRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,10 @@ public struct RPCRouter: Sendable {
/// only call this method _after_ you have registered all handlers.
/// - Parameter pipeline: The interceptor pipeline operations to register to all currently-registered handlers. The order of the
/// interceptors matters.
/// - SeeAlso: ``ServerInterceptorPipelineOperation``.
@inlinable
public mutating func registerInterceptors(pipeline: [ServerInterceptorPipelineOperation]) {
public mutating func registerInterceptors(
pipeline: [ConditionalInterceptor<any ServerInterceptor>]
) {
for descriptor in self.handlers.keys {
let applicableOperations = pipeline.filter { $0.applies(to: descriptor) }
if !applicableOperations.isEmpty {
Expand Down
2 changes: 1 addition & 1 deletion Sources/GRPCCore/Call/Server/ServerInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
/// been returned from a service. They are typically used for cross-cutting concerns like filtering
/// requests, validating messages, logging additional data, and tracing.
///
/// Interceptors can be registered with the server either directly or via ``ServerInterceptorPipelineOperation``s.
/// Interceptors can be registered with the server either directly or via ``ConditionalInterceptor``s.
/// You may register them for all services registered with a server, for RPCs directed to specific services, or
/// for RPCs directed to specific methods. If you need to modify the behavior of an interceptor on a
/// per-RPC basis in more detail, then you can use the ``ServerContext/descriptor`` to determine
Expand Down

This file was deleted.

12 changes: 6 additions & 6 deletions Sources/GRPCCore/GRPCClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public final class GRPCClient: Sendable {
private struct StateMachine {
var state: State

private let interceptorPipeline: [ClientInterceptorPipelineOperation]
private let interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>]

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

init(interceptorPipeline: [ClientInterceptorPipelineOperation]) {
init(interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>]) {
self.state = .notStarted
self.interceptorPipeline = interceptorPipeline
self.interceptorsPerMethod = [:]
Expand Down Expand Up @@ -188,14 +188,14 @@ public final class GRPCClient: Sendable {
///
/// - Parameters:
/// - transport: The transport used to establish a communication channel with a server.
/// - interceptorPipeline: A collection of ``ClientInterceptorPipelineOperation`` providing cross-cutting
/// - interceptorPipeline: A collection of ``ConditionalInterceptor``s providing cross-cutting
/// functionality to each accepted RPC. Only applicable interceptors from the pipeline will be applied to each RPC.
/// The order in which interceptors are added reflects the order in which they are called.
/// The first interceptor added will be the first interceptor to intercept each request.
/// The last interceptor added will be the final interceptor to intercept each request before calling the appropriate handler.
public init(
transport: some ClientTransport,
interceptorPipeline: [ClientInterceptorPipelineOperation]
interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>]
) {
self.transport = transport
self.stateMachine = Mutex(StateMachine(interceptorPipeline: interceptorPipeline))
Expand Down Expand Up @@ -411,7 +411,7 @@ public func withGRPCClient<Result: Sendable>(
///
/// - Parameters:
/// - transport: The transport used to establish a communication channel with a server.
/// - interceptorPipeline: A collection of ``ClientInterceptorPipelineOperation`` providing cross-cutting
/// - interceptorPipeline: A collection of ``ConditionalInterceptor``s providing cross-cutting
/// functionality to each accepted RPC. Only applicable interceptors from the pipeline will be applied to each RPC.
/// The order in which interceptors are added reflects the order in which they are called.
/// The first interceptor added will be the first interceptor to intercept each request.
Expand All @@ -423,7 +423,7 @@ public func withGRPCClient<Result: Sendable>(
/// - Returns: The result of the `handleClient` closure.
public func withGRPCClient<Result: Sendable>(
transport: some ClientTransport,
interceptorPipeline: [ClientInterceptorPipelineOperation],
interceptorPipeline: [ConditionalInterceptor<any ClientInterceptor>],
isolation: isolated (any Actor)? = #isolation,
handleClient: (GRPCClient) async throws -> Result
) async throws -> Result {
Expand Down
4 changes: 2 additions & 2 deletions Sources/GRPCCore/GRPCServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public final class GRPCServer: Sendable {
public convenience init(
transport: any ServerTransport,
services: [any RegistrableRPCService],
interceptorPipeline: [ServerInterceptorPipelineOperation]
interceptorPipeline: [ConditionalInterceptor<any ServerInterceptor>]
) {
var router = RPCRouter()
for service in services {
Expand Down Expand Up @@ -290,7 +290,7 @@ public func withGRPCServer<Result: Sendable>(
public func withGRPCServer<Result: Sendable>(
transport: any ServerTransport,
services: [any RegistrableRPCService],
interceptorPipeline: [ServerInterceptorPipelineOperation],
interceptorPipeline: [ConditionalInterceptor<any ServerInterceptor>],
isolation: isolated (any Actor)? = #isolation,
handleServer: (GRPCServer) async throws -> Result
) async throws -> Result {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@
* limitations under the License.
*/

import GRPCCore
import Testing

@testable import GRPCCore

@Suite("ClientInterceptorPipelineOperation")
struct ClientInterceptorPipelineOperationTests {
@Suite("ConditionalInterceptor")
struct ConditionalInterceptorTests {
@Test(
"Applies to",
arguments: [
Expand All @@ -38,24 +37,19 @@ struct ClientInterceptorPipelineOperationTests {
[.barFoo],
[.fooBar, .fooBaz, .barBaz]
),
] as [(ClientInterceptorPipelineOperation.Subject, [MethodDescriptor], [MethodDescriptor])]
] as [(ConditionalInterceptor<any Sendable>.Subject, [MethodDescriptor], [MethodDescriptor])]
)
func appliesTo(
operationSubject: ClientInterceptorPipelineOperation.Subject,
target: ConditionalInterceptor<any Sendable>.Subject,
applicableMethods: [MethodDescriptor],
notApplicableMethods: [MethodDescriptor]
) {
let operation = ClientInterceptorPipelineOperation.apply(
.requestCounter(.init()),
to: operationSubject
)

for applicableMethod in applicableMethods {
#expect(operation.applies(to: applicableMethod))
#expect(target.applies(to: applicableMethod))
}

for notApplicableMethod in notApplicableMethods {
#expect(!operation.applies(to: notApplicableMethod))
#expect(!target.applies(to: notApplicableMethod))
}
}
}
Expand Down
Loading
Loading