Skip to content

Commit

Permalink
Codegen for client interceptors (#1022)
Browse files Browse the repository at this point in the history
Motivation:

We have client interceptors wired all the way through but no convnenient
way for them to be used.

Modifications:

- Generate an client interceptor factory which has a
  `make<Method>Interceptors()` function for each method on the service
- The interceptor factory is an optional requirement on the generated
  client protocol, RPC invocations will pull interceptors from the
  factory, if present.

Result:

Users can provide interceptor factory implementations to their generated
clients to use interceptors.
  • Loading branch information
glbrntt committed Nov 6, 2020
1 parent f4fb86a commit 9f10ed9
Show file tree
Hide file tree
Showing 24 changed files with 1,216 additions and 128 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ let package = Package(
"EchoImplementation",
"GRPCSampleData",
"GRPCInteroperabilityTestsImplementation",
"HelloWorldModel",
]
),

Expand Down
72 changes: 67 additions & 5 deletions Sources/Examples/Echo/Model/echo.grpc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import SwiftProtobuf

/// Usage: instantiate Echo_EchoClient, then call methods of this protocol to make API calls.
public protocol Echo_EchoClientProtocol: GRPCClient {
var interceptors: Echo_EchoClientInterceptorFactoryProtocol? { get }

func get(
_ request: Echo_EchoRequest,
callOptions: CallOptions?
Expand All @@ -46,7 +48,6 @@ public protocol Echo_EchoClientProtocol: GRPCClient {
callOptions: CallOptions?,
handler: @escaping (Echo_EchoResponse) -> Void
) -> BidirectionalStreamingCall<Echo_EchoRequest, Echo_EchoResponse>

}

extension Echo_EchoClientProtocol {
Expand All @@ -64,7 +65,8 @@ extension Echo_EchoClientProtocol {
return self.makeUnaryCall(
path: "/echo.Echo/Get",
request: request,
callOptions: callOptions ?? self.defaultCallOptions
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeGetInterceptors() ?? []
)
}

Expand All @@ -84,6 +86,7 @@ extension Echo_EchoClientProtocol {
path: "/echo.Echo/Expand",
request: request,
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeExpandInterceptors() ?? [],
handler: handler
)
}
Expand All @@ -101,7 +104,8 @@ extension Echo_EchoClientProtocol {
) -> ClientStreamingCall<Echo_EchoRequest, Echo_EchoResponse> {
return self.makeClientStreamingCall(
path: "/echo.Echo/Collect",
callOptions: callOptions ?? self.defaultCallOptions
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeCollectInterceptors() ?? []
)
}

Expand All @@ -121,40 +125,98 @@ extension Echo_EchoClientProtocol {
return self.makeBidirectionalStreamingCall(
path: "/echo.Echo/Update",
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeUpdateInterceptors() ?? [],
handler: handler
)
}
}

public protocol Echo_EchoClientInterceptorFactoryProtocol {
/// Makes an array of generic interceptors. The per-method interceptor
/// factories default to calling this function and it therefore provides a
/// convenient way of setting interceptors for all methods on a client.
/// - Returns: An array of interceptors generic over `Request` and `Response`.
/// Defaults to an empty array.
func makeInterceptors<Request: SwiftProtobuf.Message, Response: SwiftProtobuf.Message>() -> [ClientInterceptor<Request, Response>]

/// - Returns: Interceptors to use when invoking 'get'.
/// Defaults to calling `self.makeInterceptors()`.
func makeGetInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>]

/// - Returns: Interceptors to use when invoking 'expand'.
/// Defaults to calling `self.makeInterceptors()`.
func makeExpandInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>]

/// - Returns: Interceptors to use when invoking 'collect'.
/// Defaults to calling `self.makeInterceptors()`.
func makeCollectInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>]

/// - Returns: Interceptors to use when invoking 'update'.
/// Defaults to calling `self.makeInterceptors()`.
func makeUpdateInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>]
}

extension Echo_EchoClientInterceptorFactoryProtocol {
public func makeInterceptors<Request: SwiftProtobuf.Message, Response: SwiftProtobuf.Message>() -> [ClientInterceptor<Request, Response>] {
return []
}

public func makeGetInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>] {
return self.makeInterceptors()
}

public func makeExpandInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>] {
return self.makeInterceptors()
}

public func makeCollectInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>] {
return self.makeInterceptors()
}

public func makeUpdateInterceptors() -> [ClientInterceptor<Echo_EchoRequest, Echo_EchoResponse>] {
return self.makeInterceptors()
}
}

public final class Echo_EchoClient: Echo_EchoClientProtocol {
public let channel: GRPCChannel
public var defaultCallOptions: CallOptions
public var interceptors: Echo_EchoClientInterceptorFactoryProtocol?

/// Creates a client for the echo.Echo service.
///
/// - Parameters:
/// - channel: `GRPCChannel` to the service host.
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
public init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) {
/// - interceptors: A factory providing interceptors for each RPC.
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Echo_EchoClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self.defaultCallOptions = defaultCallOptions
self.interceptors = interceptors
}
}

public final class Echo_EchoTestClient: Echo_EchoClientProtocol {
private let fakeChannel: FakeChannel
public var defaultCallOptions: CallOptions
public var interceptors: Echo_EchoClientInterceptorFactoryProtocol?

public var channel: GRPCChannel {
return self.fakeChannel
}

public init(
fakeChannel: FakeChannel = FakeChannel(),
defaultCallOptions callOptions: CallOptions = CallOptions()
defaultCallOptions callOptions: CallOptions = CallOptions(),
interceptors: Echo_EchoClientInterceptorFactoryProtocol? = nil
) {
self.fakeChannel = fakeChannel
self.defaultCallOptions = callOptions
self.interceptors = interceptors
}

/// Make a unary response for the Get RPC. This must be called
Expand Down
10 changes: 8 additions & 2 deletions Sources/Examples/Echo/Model/echo.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ extension Echo_EchoRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.text)
case 1: try { try decoder.decodeSingularStringField(value: &self.text) }()
default: break
}
}
Expand Down Expand Up @@ -101,8 +104,11 @@ extension Echo_EchoResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.text)
case 1: try { try decoder.decodeSingularStringField(value: &self.text) }()
default: break
}
}
Expand Down
38 changes: 35 additions & 3 deletions Sources/Examples/HelloWorld/Model/helloworld.grpc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ import SwiftProtobuf

/// Usage: instantiate Helloworld_GreeterClient, then call methods of this protocol to make API calls.
public protocol Helloworld_GreeterClientProtocol: GRPCClient {
var interceptors: Helloworld_GreeterClientInterceptorFactoryProtocol? { get }

func sayHello(
_ request: Helloworld_HelloRequest,
callOptions: CallOptions?
) -> UnaryCall<Helloworld_HelloRequest, Helloworld_HelloReply>

}

extension Helloworld_GreeterClientProtocol {
Expand All @@ -49,23 +50,54 @@ extension Helloworld_GreeterClientProtocol {
return self.makeUnaryCall(
path: "/helloworld.Greeter/SayHello",
request: request,
callOptions: callOptions ?? self.defaultCallOptions
callOptions: callOptions ?? self.defaultCallOptions,
interceptors: self.interceptors?.makeSayHelloInterceptors() ?? []
)
}
}

public protocol Helloworld_GreeterClientInterceptorFactoryProtocol {
/// Makes an array of generic interceptors. The per-method interceptor
/// factories default to calling this function and it therefore provides a
/// convenient way of setting interceptors for all methods on a client.
/// - Returns: An array of interceptors generic over `Request` and `Response`.
/// Defaults to an empty array.
func makeInterceptors<Request: SwiftProtobuf.Message, Response: SwiftProtobuf.Message>() -> [ClientInterceptor<Request, Response>]

/// - Returns: Interceptors to use when invoking 'sayHello'.
/// Defaults to calling `self.makeInterceptors()`.
func makeSayHelloInterceptors() -> [ClientInterceptor<Helloworld_HelloRequest, Helloworld_HelloReply>]
}

extension Helloworld_GreeterClientInterceptorFactoryProtocol {
public func makeInterceptors<Request: SwiftProtobuf.Message, Response: SwiftProtobuf.Message>() -> [ClientInterceptor<Request, Response>] {
return []
}

public func makeSayHelloInterceptors() -> [ClientInterceptor<Helloworld_HelloRequest, Helloworld_HelloReply>] {
return self.makeInterceptors()
}
}

public final class Helloworld_GreeterClient: Helloworld_GreeterClientProtocol {
public let channel: GRPCChannel
public var defaultCallOptions: CallOptions
public var interceptors: Helloworld_GreeterClientInterceptorFactoryProtocol?

/// Creates a client for the helloworld.Greeter service.
///
/// - Parameters:
/// - channel: `GRPCChannel` to the service host.
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
public init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) {
/// - interceptors: A factory providing interceptors for each RPC.
public init(
channel: GRPCChannel,
defaultCallOptions: CallOptions = CallOptions(),
interceptors: Helloworld_GreeterClientInterceptorFactoryProtocol? = nil
) {
self.channel = channel
self.defaultCallOptions = defaultCallOptions
self.interceptors = interceptors
}
}

Expand Down
10 changes: 8 additions & 2 deletions Sources/Examples/HelloWorld/Model/helloworld.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ extension Helloworld_HelloRequest: SwiftProtobuf.Message, SwiftProtobuf._Message

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.name)
case 1: try { try decoder.decodeSingularStringField(value: &self.name) }()
default: break
}
}
Expand Down Expand Up @@ -101,8 +104,11 @@ extension Helloworld_HelloReply: SwiftProtobuf.Message, SwiftProtobuf._MessageIm

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try decoder.decodeSingularStringField(value: &self.message)
case 1: try { try decoder.decodeSingularStringField(value: &self.message) }()
default: break
}
}
Expand Down
Loading

0 comments on commit 9f10ed9

Please sign in to comment.