Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion Examples/buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ plugins:
opt: Visibility=Internal
out: ./ElizaSharedSources/GeneratedSources
- name: connect-swift
opt: Visibility=Internal
opt: >
GenerateServiceMetadata=false,
Visibility=Internal
out: ./ElizaSharedSources/GeneratedSources
path: ../.tmp/bin/protoc-gen-connect-swift
42 changes: 42 additions & 0 deletions Libraries/Connect/Interfaces/MethodSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2022-2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Contains metadata for a specific RPC method.
public struct MethodSpec: Equatable, Codable {
/// The name of the method (1:1 with the `.proto` file). E.g., `Foo`.
public let name: String
/// The fully qualified name of the method's service. E.g., `foo.v1.FooService`.
public let service: String
/// The type of method (unary, bidirectional stream, etc.).
public let type: MethodType

/// The path of the RPC, constructed using the package, service, and method name.
/// E.g., `foo.v1.FooService/Foo`.
public var path: String {
return "\(self.service)/\(self.name)"
}

public enum MethodType: Equatable, Codable {
case unary
case clientStream
case serverStream
case bidirectionalStream
}

public init(name: String, service: String, type: MethodType) {
self.name = name
self.service = service
self.type = type
}
}
8 changes: 8 additions & 0 deletions Plugins/ConnectPluginUtilities/GeneratorOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ private enum CommandLineParameter: String {
case fileNaming = "FileNaming"
case generateAsyncMethods = "GenerateAsyncMethods"
case generateCallbackMethods = "GenerateCallbackMethods"
case generateServiceMetadata = "GenerateServiceMetadata"
case keepMethodCasing = "KeepMethodCasing"
case protoPathModuleMappings = "ProtoPathModuleMappings"
case swiftProtobufModuleName = "SwiftProtobufModuleName"
Expand Down Expand Up @@ -76,6 +77,7 @@ public struct GeneratorOptions {
public private(set) var fileNaming = FileNaming.fullPath
public private(set) var generateAsyncMethods = true
public private(set) var generateCallbackMethods = false
public private(set) var generateServiceMetadata = true
public private(set) var keepMethodCasing = false
public private(set) var protoToModuleMappings = ProtoFileToModuleMappings()
public private(set) var swiftProtobufModuleName = "SwiftProtobuf"
Expand Down Expand Up @@ -126,6 +128,12 @@ public struct GeneratorOptions {
continue
}

case .generateServiceMetadata:
if let value = Bool(rawValue) {
self.generateServiceMetadata = value
continue
}

case .keepMethodCasing:
if let value = Bool(rawValue) {
self.keepMethodCasing = value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ import SwiftProtobufPluginLibrary

extension MethodDescriptor {
public var methodPath: String {
if self.file.package.isEmpty {
return "\(self.service.name)/\(self.name)"
} else {
return "\(self.file.package).\(self.service.name)/\(self.name)"
}
return "\(self.service.servicePath)/\(self.name)"
}

public func name(using options: GeneratorOptions) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
import SwiftProtobufPluginLibrary

extension ServiceDescriptor {
public var servicePath: String {
if self.file.package.isEmpty {
return self.name
} else {
return "\(self.file.package).\(self.name)"
}
}

public func implementationName(using namer: SwiftProtobufNamer) -> String {
let upperCamelName = NamingUtils.toUpperCamelCase(self.name) + "Client"
if self.file.package.isEmpty {
Expand Down
40 changes: 40 additions & 0 deletions Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,34 @@ final class ConnectClientGenerator: Generator {
self.printAsyncAwaitMethodImplementation(for: method)
}
}

if self.options.generateServiceMetadata {
self.printSpecs(for: service)
}
}
self.printLine("}")
}

private func printSpecs(for service: ServiceDescriptor) {
self.printLine()
self.printLine("\(self.visibility) enum Metadata {")
self.indent {
self.printLine("\(self.visibility) enum Methods {")
self.indent {
for method in service.methods {
self.printLine(
"""
\(self.visibility) static let \(method.name(using: self.options)) = \
Connect.MethodSpec(\
name: "\(method.name)", \
service: "\(method.service.servicePath)", \
type: \(method.specStreamType())\
)
"""
)
}
}
self.printLine("}")
}
self.printLine("}")
}
Expand Down Expand Up @@ -146,6 +174,18 @@ final class ConnectClientGenerator: Generator {
}

private extension MethodDescriptor {
func specStreamType() -> String {
if self.clientStreaming && self.serverStreaming {
return ".bidirectionalStream"
} else if self.serverStreaming {
return ".serverStream"
} else if self.clientStreaming {
return ".clientStream"
} else {
return ".unary"
}
}

func callbackReturnValue() -> String {
if self.clientStreaming && self.serverStreaming {
return """
Expand Down
69 changes: 69 additions & 0 deletions Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2022-2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Connect
import XCTest

final class ServiceMetadataTests: XCTestCase {
func testMethodSpecsAreGeneratedCorrectlyForService() {
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.unaryCall,
MethodSpec(
name: "UnaryCall",
service: "grpc.testing.TestService",
type: .unary
)
)
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.unaryCall.path,
"grpc.testing.TestService/UnaryCall"
)
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.streamingOutputCall,
MethodSpec(
name: "StreamingOutputCall",
service: "grpc.testing.TestService",
type: .serverStream
)
)
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.streamingOutputCall.path,
"grpc.testing.TestService/StreamingOutputCall"
)
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.streamingInputCall,
MethodSpec(
name: "StreamingInputCall",
service: "grpc.testing.TestService",
type: .clientStream
)
)
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.streamingInputCall.path,
"grpc.testing.TestService/StreamingInputCall"
)
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.fullDuplexCall,
MethodSpec(
name: "FullDuplexCall",
service: "grpc.testing.TestService",
type: .bidirectionalStream
)
)
XCTAssertEqual(
Grpc_Testing_TestServiceClient.Metadata.Methods.fullDuplexCall.path,
"grpc.testing.TestService/FullDuplexCall"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,22 @@ internal final class Grpc_Testing_TestServiceClient: Grpc_Testing_TestServiceCli
internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:]) -> any Connect.ServerOnlyAsyncStreamInterface<Grpc_Testing_Empty, Grpc_Testing_Empty> {
return self.client.serverOnlyStream(path: "grpc.testing.TestService/UnimplementedStreamingOutputCall", headers: headers)
}

internal enum Metadata {
internal enum Methods {
internal static let emptyCall = Connect.MethodSpec(name: "EmptyCall", service: "grpc.testing.TestService", type: .unary)
internal static let unaryCall = Connect.MethodSpec(name: "UnaryCall", service: "grpc.testing.TestService", type: .unary)
internal static let failUnaryCall = Connect.MethodSpec(name: "FailUnaryCall", service: "grpc.testing.TestService", type: .unary)
internal static let cacheableUnaryCall = Connect.MethodSpec(name: "CacheableUnaryCall", service: "grpc.testing.TestService", type: .unary)
internal static let streamingOutputCall = Connect.MethodSpec(name: "StreamingOutputCall", service: "grpc.testing.TestService", type: .serverStream)
internal static let failStreamingOutputCall = Connect.MethodSpec(name: "FailStreamingOutputCall", service: "grpc.testing.TestService", type: .serverStream)
internal static let streamingInputCall = Connect.MethodSpec(name: "StreamingInputCall", service: "grpc.testing.TestService", type: .clientStream)
internal static let fullDuplexCall = Connect.MethodSpec(name: "FullDuplexCall", service: "grpc.testing.TestService", type: .bidirectionalStream)
internal static let halfDuplexCall = Connect.MethodSpec(name: "HalfDuplexCall", service: "grpc.testing.TestService", type: .bidirectionalStream)
internal static let unimplementedCall = Connect.MethodSpec(name: "UnimplementedCall", service: "grpc.testing.TestService", type: .unary)
internal static let unimplementedStreamingOutputCall = Connect.MethodSpec(name: "UnimplementedStreamingOutputCall", service: "grpc.testing.TestService", type: .serverStream)
}
}
}

/// A simple service NOT implemented at servers so clients can test for
Expand Down Expand Up @@ -249,6 +265,13 @@ internal final class Grpc_Testing_UnimplementedServiceClient: Grpc_Testing_Unimp
internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:]) -> any Connect.ServerOnlyAsyncStreamInterface<Grpc_Testing_Empty, Grpc_Testing_Empty> {
return self.client.serverOnlyStream(path: "grpc.testing.UnimplementedService/UnimplementedStreamingOutputCall", headers: headers)
}

internal enum Metadata {
internal enum Methods {
internal static let unimplementedCall = Connect.MethodSpec(name: "UnimplementedCall", service: "grpc.testing.UnimplementedService", type: .unary)
internal static let unimplementedStreamingOutputCall = Connect.MethodSpec(name: "UnimplementedStreamingOutputCall", service: "grpc.testing.UnimplementedService", type: .serverStream)
}
}
}

/// A service used to control reconnect server.
Expand Down Expand Up @@ -290,6 +313,13 @@ internal final class Grpc_Testing_ReconnectServiceClient: Grpc_Testing_Reconnect
internal func `stop`(request: Grpc_Testing_Empty, headers: Connect.Headers = [:]) async -> ResponseMessage<Grpc_Testing_ReconnectInfo> {
return await self.client.unary(path: "grpc.testing.ReconnectService/Stop", request: request, headers: headers)
}

internal enum Metadata {
internal enum Methods {
internal static let start = Connect.MethodSpec(name: "Start", service: "grpc.testing.ReconnectService", type: .unary)
internal static let stop = Connect.MethodSpec(name: "Stop", service: "grpc.testing.ReconnectService", type: .unary)
}
}
}

/// A service used to obtain stats for verifying LB behavior.
Expand Down Expand Up @@ -335,6 +365,13 @@ internal final class Grpc_Testing_LoadBalancerStatsServiceClient: Grpc_Testing_L
internal func `getClientAccumulatedStats`(request: Grpc_Testing_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers = [:]) async -> ResponseMessage<Grpc_Testing_LoadBalancerAccumulatedStatsResponse> {
return await self.client.unary(path: "grpc.testing.LoadBalancerStatsService/GetClientAccumulatedStats", request: request, headers: headers)
}

internal enum Metadata {
internal enum Methods {
internal static let getClientStats = Connect.MethodSpec(name: "GetClientStats", service: "grpc.testing.LoadBalancerStatsService", type: .unary)
internal static let getClientAccumulatedStats = Connect.MethodSpec(name: "GetClientAccumulatedStats", service: "grpc.testing.LoadBalancerStatsService", type: .unary)
}
}
}

/// A service to remotely control health status of an xDS test server.
Expand Down Expand Up @@ -376,6 +413,13 @@ internal final class Grpc_Testing_XdsUpdateHealthServiceClient: Grpc_Testing_Xds
internal func `setNotServing`(request: Grpc_Testing_Empty, headers: Connect.Headers = [:]) async -> ResponseMessage<Grpc_Testing_Empty> {
return await self.client.unary(path: "grpc.testing.XdsUpdateHealthService/SetNotServing", request: request, headers: headers)
}

internal enum Metadata {
internal enum Methods {
internal static let setServing = Connect.MethodSpec(name: "SetServing", service: "grpc.testing.XdsUpdateHealthService", type: .unary)
internal static let setNotServing = Connect.MethodSpec(name: "SetNotServing", service: "grpc.testing.XdsUpdateHealthService", type: .unary)
}
}
}

/// A service to dynamically update the configuration of an xDS test client.
Expand Down Expand Up @@ -405,4 +449,10 @@ internal final class Grpc_Testing_XdsUpdateClientConfigureServiceClient: Grpc_Te
internal func `configure`(request: Grpc_Testing_ClientConfigureRequest, headers: Connect.Headers = [:]) async -> ResponseMessage<Grpc_Testing_ClientConfigureResponse> {
return await self.client.unary(path: "grpc.testing.XdsUpdateClientConfigureService/Configure", request: request, headers: headers)
}

internal enum Metadata {
internal enum Methods {
internal static let configure = Connect.MethodSpec(name: "Configure", service: "grpc.testing.XdsUpdateClientConfigureService", type: .unary)
}
}
}