From c3996a00e0561ec2f0f58cae64c02af4afa81c8e Mon Sep 17 00:00:00 2001 From: Michael Rebello Date: Wed, 18 Jan 2023 16:47:59 -0800 Subject: [PATCH 1/5] Add `GenerateServiceMetadata` option to generate descriptors Specifying `GenerateServiceMetadata=true` will have the generator output a set of descriptors for RPCs within each service. For example: ```swift internal enum Grpc_Testing_TestServiceMetadata { internal enum Methods { internal static let emptyCall = Connect.MethodDescriptor(name: "EmptyCall", path: "grpc.testing.TestService/EmptyCall") internal static let unaryCall = Connect.MethodDescriptor(name: "UnaryCall", path: "grpc.testing.TestService/UnaryCall") internal static let failUnaryCall = Connect.MethodDescriptor(name: "FailUnaryCall", path: "grpc.testing.TestService/FailUnaryCall") internal static let cacheableUnaryCall = Connect.MethodDescriptor(name: "CacheableUnaryCall", path: "grpc.testing.TestService/CacheableUnaryCall") internal static let streamingOutputCall = Connect.MethodDescriptor(name: "StreamingOutputCall", path: "grpc.testing.TestService/StreamingOutputCall") internal static let failStreamingOutputCall = Connect.MethodDescriptor(name: "FailStreamingOutputCall", path: "grpc.testing.TestService/FailStreamingOutputCall") internal static let streamingInputCall = Connect.MethodDescriptor(name: "StreamingInputCall", path: "grpc.testing.TestService/StreamingInputCall") internal static let fullDuplexCall = Connect.MethodDescriptor(name: "FullDuplexCall", path: "grpc.testing.TestService/FullDuplexCall") internal static let halfDuplexCall = Connect.MethodDescriptor(name: "HalfDuplexCall", path: "grpc.testing.TestService/HalfDuplexCall") internal static let unimplementedCall = Connect.MethodDescriptor(name: "UnimplementedCall", path: "grpc.testing.TestService/UnimplementedCall") internal static let unimplementedStreamingOutputCall = Connect.MethodDescriptor(name: "UnimplementedStreamingOutputCall", path: "grpc.testing.TestService/UnimplementedStreamingOutputCall") } } ``` https://github.com/bufbuild/connect-swift/discussions/85 --- .../Connect/Interfaces/MethodDescriptor.swift | 26 +++++++++ .../GeneratorOptions.swift | 8 +++ .../ConnectClientGenerator.swift | 30 ++++++++++ .../ConnectTests/ServiceMetadataTests.swift | 26 +++++++++ .../Generated/grpc/testing/test.connect.swift | 56 +++++++++++++++++++ Tests/ConnectLibraryTests/buf.gen.yaml | 1 + 6 files changed, 147 insertions(+) create mode 100644 Libraries/Connect/Interfaces/MethodDescriptor.swift create mode 100644 Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift diff --git a/Libraries/Connect/Interfaces/MethodDescriptor.swift b/Libraries/Connect/Interfaces/MethodDescriptor.swift new file mode 100644 index 00000000..ad16915e --- /dev/null +++ b/Libraries/Connect/Interfaces/MethodDescriptor.swift @@ -0,0 +1,26 @@ +// 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 on a specifific RPC method. +public struct MethodDescriptor: Equatable { + /// The name of the RPC method (1:1 with the `.proto` file). + public let name: String + /// The path of the RPC, constructed using the package, service, and RPC name. + public let path: String + + public init(name: String, path: String) { + self.name = name + self.path = path + } +} diff --git a/Plugins/ConnectPluginUtilities/GeneratorOptions.swift b/Plugins/ConnectPluginUtilities/GeneratorOptions.swift index 493236de..0d7ca9d7 100644 --- a/Plugins/ConnectPluginUtilities/GeneratorOptions.swift +++ b/Plugins/ConnectPluginUtilities/GeneratorOptions.swift @@ -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" @@ -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 = false public private(set) var keepMethodCasing = false public private(set) var protoToModuleMappings = ProtoFileToModuleMappings() public private(set) var swiftProtobufModuleName = "SwiftProtobuf" @@ -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 diff --git a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift index adc5f9bc..3789e4ba 100644 --- a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift +++ b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift @@ -40,6 +40,11 @@ final class ConnectClientGenerator: Generator { for service in self.descriptor.services { self.printLine() self.printService(service) + + if self.options.generateServiceMetadata { + self.printLine() + self.printDescriptors(for: service) + } } } @@ -86,6 +91,31 @@ final class ConnectClientGenerator: Generator { self.printLine("}") } + private func printDescriptors(for service: ServiceDescriptor) { + let metadataTypeName = self.namer.typePrefix(forFile: service.file) + + NamingUtils.toUpperCamelCase(service.name) + + "Metadata" + self.printLine( + "/// Provides metadata for `\(service.implementationName(using: self.namer))`." + ) + self.printLine("\(self.visibility) enum \(metadataTypeName) {") + 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.MethodDescriptor(name: "\(method.name)", path: "\(method.methodPath)") + """ + ) + } + } + self.printLine("}") + } + self.printLine("}") + } + private func printCallbackMethodInterface(for method: MethodDescriptor) { self.printLine() self.printCommentsIfNeeded(for: method) diff --git a/Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift b/Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift new file mode 100644 index 00000000..e6290840 --- /dev/null +++ b/Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift @@ -0,0 +1,26 @@ +// 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 testMethodDescriptorsAreGeneratedForService() { + let expectedDescriptor = MethodDescriptor( + name: "UnaryCall", + path: "grpc.testing.TestService/UnaryCall" + ) + XCTAssertEqual(Grpc_Testing_TestServiceMetadata.Methods.unaryCall, expectedDescriptor) + } +} diff --git a/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift b/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift index 52188aa4..a4999aef 100644 --- a/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift +++ b/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift @@ -207,6 +207,23 @@ internal final class Grpc_Testing_TestServiceClient: Grpc_Testing_TestServiceCli } } +/// Provides metadata for `Grpc_Testing_TestServiceClient`. +internal enum Grpc_Testing_TestServiceMetadata { + internal enum Methods { + internal static let emptyCall = Connect.MethodDescriptor(name: "EmptyCall", path: "grpc.testing.TestService/EmptyCall") + internal static let unaryCall = Connect.MethodDescriptor(name: "UnaryCall", path: "grpc.testing.TestService/UnaryCall") + internal static let failUnaryCall = Connect.MethodDescriptor(name: "FailUnaryCall", path: "grpc.testing.TestService/FailUnaryCall") + internal static let cacheableUnaryCall = Connect.MethodDescriptor(name: "CacheableUnaryCall", path: "grpc.testing.TestService/CacheableUnaryCall") + internal static let streamingOutputCall = Connect.MethodDescriptor(name: "StreamingOutputCall", path: "grpc.testing.TestService/StreamingOutputCall") + internal static let failStreamingOutputCall = Connect.MethodDescriptor(name: "FailStreamingOutputCall", path: "grpc.testing.TestService/FailStreamingOutputCall") + internal static let streamingInputCall = Connect.MethodDescriptor(name: "StreamingInputCall", path: "grpc.testing.TestService/StreamingInputCall") + internal static let fullDuplexCall = Connect.MethodDescriptor(name: "FullDuplexCall", path: "grpc.testing.TestService/FullDuplexCall") + internal static let halfDuplexCall = Connect.MethodDescriptor(name: "HalfDuplexCall", path: "grpc.testing.TestService/HalfDuplexCall") + internal static let unimplementedCall = Connect.MethodDescriptor(name: "UnimplementedCall", path: "grpc.testing.TestService/UnimplementedCall") + internal static let unimplementedStreamingOutputCall = Connect.MethodDescriptor(name: "UnimplementedStreamingOutputCall", path: "grpc.testing.TestService/UnimplementedStreamingOutputCall") + } +} + /// A simple service NOT implemented at servers so clients can test for /// that case. internal protocol Grpc_Testing_UnimplementedServiceClientInterface { @@ -251,6 +268,14 @@ internal final class Grpc_Testing_UnimplementedServiceClient: Grpc_Testing_Unimp } } +/// Provides metadata for `Grpc_Testing_UnimplementedServiceClient`. +internal enum Grpc_Testing_UnimplementedServiceMetadata { + internal enum Methods { + internal static let unimplementedCall = Connect.MethodDescriptor(name: "UnimplementedCall", path: "grpc.testing.UnimplementedService/UnimplementedCall") + internal static let unimplementedStreamingOutputCall = Connect.MethodDescriptor(name: "UnimplementedStreamingOutputCall", path: "grpc.testing.UnimplementedService/UnimplementedStreamingOutputCall") + } +} + /// A service used to control reconnect server. internal protocol Grpc_Testing_ReconnectServiceClientInterface { @@ -292,6 +317,14 @@ internal final class Grpc_Testing_ReconnectServiceClient: Grpc_Testing_Reconnect } } +/// Provides metadata for `Grpc_Testing_ReconnectServiceClient`. +internal enum Grpc_Testing_ReconnectServiceMetadata { + internal enum Methods { + internal static let start = Connect.MethodDescriptor(name: "Start", path: "grpc.testing.ReconnectService/Start") + internal static let stop = Connect.MethodDescriptor(name: "Stop", path: "grpc.testing.ReconnectService/Stop") + } +} + /// A service used to obtain stats for verifying LB behavior. internal protocol Grpc_Testing_LoadBalancerStatsServiceClientInterface { @@ -337,6 +370,14 @@ internal final class Grpc_Testing_LoadBalancerStatsServiceClient: Grpc_Testing_L } } +/// Provides metadata for `Grpc_Testing_LoadBalancerStatsServiceClient`. +internal enum Grpc_Testing_LoadBalancerStatsServiceMetadata { + internal enum Methods { + internal static let getClientStats = Connect.MethodDescriptor(name: "GetClientStats", path: "grpc.testing.LoadBalancerStatsService/GetClientStats") + internal static let getClientAccumulatedStats = Connect.MethodDescriptor(name: "GetClientAccumulatedStats", path: "grpc.testing.LoadBalancerStatsService/GetClientAccumulatedStats") + } +} + /// A service to remotely control health status of an xDS test server. internal protocol Grpc_Testing_XdsUpdateHealthServiceClientInterface { @@ -378,6 +419,14 @@ internal final class Grpc_Testing_XdsUpdateHealthServiceClient: Grpc_Testing_Xds } } +/// Provides metadata for `Grpc_Testing_XdsUpdateHealthServiceClient`. +internal enum Grpc_Testing_XdsUpdateHealthServiceMetadata { + internal enum Methods { + internal static let setServing = Connect.MethodDescriptor(name: "SetServing", path: "grpc.testing.XdsUpdateHealthService/SetServing") + internal static let setNotServing = Connect.MethodDescriptor(name: "SetNotServing", path: "grpc.testing.XdsUpdateHealthService/SetNotServing") + } +} + /// A service to dynamically update the configuration of an xDS test client. internal protocol Grpc_Testing_XdsUpdateClientConfigureServiceClientInterface { @@ -406,3 +455,10 @@ internal final class Grpc_Testing_XdsUpdateClientConfigureServiceClient: Grpc_Te return await self.client.unary(path: "grpc.testing.XdsUpdateClientConfigureService/Configure", request: request, headers: headers) } } + +/// Provides metadata for `Grpc_Testing_XdsUpdateClientConfigureServiceClient`. +internal enum Grpc_Testing_XdsUpdateClientConfigureServiceMetadata { + internal enum Methods { + internal static let configure = Connect.MethodDescriptor(name: "Configure", path: "grpc.testing.XdsUpdateClientConfigureService/Configure") + } +} diff --git a/Tests/ConnectLibraryTests/buf.gen.yaml b/Tests/ConnectLibraryTests/buf.gen.yaml index 61143a5c..2966ad45 100644 --- a/Tests/ConnectLibraryTests/buf.gen.yaml +++ b/Tests/ConnectLibraryTests/buf.gen.yaml @@ -7,6 +7,7 @@ plugins: opt: > GenerateAsyncMethods=true, GenerateCallbackMethods=true, + GenerateServiceMetadata=true, Visibility=Internal out: ./Generated path: ../../.tmp/bin/protoc-gen-connect-swift From c70839fb6e3f9bed7652df86d8f4e3d05e5f51b7 Mon Sep 17 00:00:00 2001 From: Michael Rebello Date: Wed, 18 Jan 2023 16:57:07 -0800 Subject: [PATCH 2/5] fix indentation --- Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift index 3789e4ba..437c86f2 100644 --- a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift +++ b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift @@ -104,10 +104,11 @@ final class ConnectClientGenerator: Generator { self.indent { for method in service.methods { self.printLine( - """ - \(self.visibility) static let \(method.name(using: self.options)) = \ - Connect.MethodDescriptor(name: "\(method.name)", path: "\(method.methodPath)") - """ + """ + \(self.visibility) static let \(method.name(using: self.options)) = \ + Connect.MethodDescriptor(name: "\(method.name)", \ + path: "\(method.methodPath)") + """ ) } } From 488dade663133d003543c672757cf17a88f4afd3 Mon Sep 17 00:00:00 2001 From: Michael Rebello Date: Thu, 19 Jan 2023 10:17:27 -0800 Subject: [PATCH 3/5] code review --- .../GeneratedSources/eliza.connect.swift | 8 ++ ...ethodDescriptor.swift => MethodSpec.swift} | 25 ++++-- .../GeneratorOptions.swift | 2 +- .../MethodDescriptor+Extensions.swift | 6 +- .../ServiceDescriptor+Extensions.swift | 8 ++ .../ConnectClientGenerator.swift | 39 +++++---- .../ConnectTests/ServiceMetadataTests.swift | 53 ++++++++++-- .../Generated/grpc/testing/test.connect.swift | 82 +++++++++---------- Tests/ConnectLibraryTests/buf.gen.yaml | 1 - 9 files changed, 148 insertions(+), 76 deletions(-) rename Libraries/Connect/Interfaces/{MethodDescriptor.swift => MethodSpec.swift} (55%) diff --git a/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift b/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift index 410f6dd8..4d9057e1 100644 --- a/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift +++ b/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift @@ -47,4 +47,12 @@ internal final class Buf_Connect_Demo_Eliza_V1_ElizaServiceClient: Buf_Connect_D internal func `introduce`(headers: Connect.Headers = [:]) -> any Connect.ServerOnlyAsyncStreamInterface { return self.client.serverOnlyStream(path: "buf.connect.demo.eliza.v1.ElizaService/Introduce", headers: headers) } + + internal enum Metadata { + internal enum Methods { + internal static let say = Connect.MethodSpec(name: "Say", service: "buf.connect.demo.eliza.v1.ElizaService", type: .unary) + internal static let converse = Connect.MethodSpec(name: "Converse", service: "buf.connect.demo.eliza.v1.ElizaService", type: .bidirectionalStream) + internal static let introduce = Connect.MethodSpec(name: "Introduce", service: "buf.connect.demo.eliza.v1.ElizaService", type: .serverStream) + } + } } diff --git a/Libraries/Connect/Interfaces/MethodDescriptor.swift b/Libraries/Connect/Interfaces/MethodSpec.swift similarity index 55% rename from Libraries/Connect/Interfaces/MethodDescriptor.swift rename to Libraries/Connect/Interfaces/MethodSpec.swift index ad16915e..352d85b5 100644 --- a/Libraries/Connect/Interfaces/MethodDescriptor.swift +++ b/Libraries/Connect/Interfaces/MethodSpec.swift @@ -12,15 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Contains metadata on a specifific RPC method. -public struct MethodDescriptor: Equatable { +/// Contains metadata for a specific RPC method. +public struct MethodSpec: Equatable, Codable { /// The name of the RPC method (1:1 with the `.proto` file). public let name: String + /// The fully qualified name of the method's service. + public let service: String + /// The type of method that this is (unary, bidirectional stream, etc.). + public let type: MethodType + /// The path of the RPC, constructed using the package, service, and RPC name. - public let path: String + 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, path: String) { + public init(name: String, service: String, type: MethodType) { self.name = name - self.path = path + self.service = service + self.type = type } } diff --git a/Plugins/ConnectPluginUtilities/GeneratorOptions.swift b/Plugins/ConnectPluginUtilities/GeneratorOptions.swift index 0d7ca9d7..02249675 100644 --- a/Plugins/ConnectPluginUtilities/GeneratorOptions.swift +++ b/Plugins/ConnectPluginUtilities/GeneratorOptions.swift @@ -77,7 +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 = 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" diff --git a/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift b/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift index cb81bfe9..c9dd97db 100644 --- a/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift +++ b/Plugins/ConnectPluginUtilities/MethodDescriptor+Extensions.swift @@ -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 { diff --git a/Plugins/ConnectPluginUtilities/ServiceDescriptor+Extensions.swift b/Plugins/ConnectPluginUtilities/ServiceDescriptor+Extensions.swift index a632ceb7..d6342267 100644 --- a/Plugins/ConnectPluginUtilities/ServiceDescriptor+Extensions.swift +++ b/Plugins/ConnectPluginUtilities/ServiceDescriptor+Extensions.swift @@ -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 { diff --git a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift index 437c86f2..e1bbef17 100644 --- a/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift +++ b/Plugins/ConnectSwiftPlugin/ConnectClientGenerator.swift @@ -40,11 +40,6 @@ final class ConnectClientGenerator: Generator { for service in self.descriptor.services { self.printLine() self.printService(service) - - if self.options.generateServiceMetadata { - self.printLine() - self.printDescriptors(for: service) - } } } @@ -87,18 +82,17 @@ final class ConnectClientGenerator: Generator { self.printAsyncAwaitMethodImplementation(for: method) } } + + if self.options.generateServiceMetadata { + self.printSpecs(for: service) + } } self.printLine("}") } - private func printDescriptors(for service: ServiceDescriptor) { - let metadataTypeName = self.namer.typePrefix(forFile: service.file) - + NamingUtils.toUpperCamelCase(service.name) - + "Metadata" - self.printLine( - "/// Provides metadata for `\(service.implementationName(using: self.namer))`." - ) - self.printLine("\(self.visibility) enum \(metadataTypeName) {") + private func printSpecs(for service: ServiceDescriptor) { + self.printLine() + self.printLine("\(self.visibility) enum Metadata {") self.indent { self.printLine("\(self.visibility) enum Methods {") self.indent { @@ -106,8 +100,11 @@ final class ConnectClientGenerator: Generator { self.printLine( """ \(self.visibility) static let \(method.name(using: self.options)) = \ - Connect.MethodDescriptor(name: "\(method.name)", \ - path: "\(method.methodPath)") + Connect.MethodSpec(\ + name: "\(method.name)", \ + service: "\(method.service.servicePath)", \ + type: \(method.specStreamType())\ + ) """ ) } @@ -177,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 """ diff --git a/Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift b/Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift index e6290840..44c8d755 100644 --- a/Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift +++ b/Tests/ConnectLibraryTests/ConnectTests/ServiceMetadataTests.swift @@ -16,11 +16,54 @@ import Connect import XCTest final class ServiceMetadataTests: XCTestCase { - func testMethodDescriptorsAreGeneratedForService() { - let expectedDescriptor = MethodDescriptor( - name: "UnaryCall", - path: "grpc.testing.TestService/UnaryCall" + 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" ) - XCTAssertEqual(Grpc_Testing_TestServiceMetadata.Methods.unaryCall, expectedDescriptor) } } diff --git a/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift b/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift index a4999aef..86d1b97b 100644 --- a/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift +++ b/Tests/ConnectLibraryTests/Generated/grpc/testing/test.connect.swift @@ -205,22 +205,21 @@ internal final class Grpc_Testing_TestServiceClient: Grpc_Testing_TestServiceCli internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:]) -> any Connect.ServerOnlyAsyncStreamInterface { return self.client.serverOnlyStream(path: "grpc.testing.TestService/UnimplementedStreamingOutputCall", headers: headers) } -} -/// Provides metadata for `Grpc_Testing_TestServiceClient`. -internal enum Grpc_Testing_TestServiceMetadata { - internal enum Methods { - internal static let emptyCall = Connect.MethodDescriptor(name: "EmptyCall", path: "grpc.testing.TestService/EmptyCall") - internal static let unaryCall = Connect.MethodDescriptor(name: "UnaryCall", path: "grpc.testing.TestService/UnaryCall") - internal static let failUnaryCall = Connect.MethodDescriptor(name: "FailUnaryCall", path: "grpc.testing.TestService/FailUnaryCall") - internal static let cacheableUnaryCall = Connect.MethodDescriptor(name: "CacheableUnaryCall", path: "grpc.testing.TestService/CacheableUnaryCall") - internal static let streamingOutputCall = Connect.MethodDescriptor(name: "StreamingOutputCall", path: "grpc.testing.TestService/StreamingOutputCall") - internal static let failStreamingOutputCall = Connect.MethodDescriptor(name: "FailStreamingOutputCall", path: "grpc.testing.TestService/FailStreamingOutputCall") - internal static let streamingInputCall = Connect.MethodDescriptor(name: "StreamingInputCall", path: "grpc.testing.TestService/StreamingInputCall") - internal static let fullDuplexCall = Connect.MethodDescriptor(name: "FullDuplexCall", path: "grpc.testing.TestService/FullDuplexCall") - internal static let halfDuplexCall = Connect.MethodDescriptor(name: "HalfDuplexCall", path: "grpc.testing.TestService/HalfDuplexCall") - internal static let unimplementedCall = Connect.MethodDescriptor(name: "UnimplementedCall", path: "grpc.testing.TestService/UnimplementedCall") - internal static let unimplementedStreamingOutputCall = Connect.MethodDescriptor(name: "UnimplementedStreamingOutputCall", path: "grpc.testing.TestService/UnimplementedStreamingOutputCall") + 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) + } } } @@ -266,13 +265,12 @@ internal final class Grpc_Testing_UnimplementedServiceClient: Grpc_Testing_Unimp internal func `unimplementedStreamingOutputCall`(headers: Connect.Headers = [:]) -> any Connect.ServerOnlyAsyncStreamInterface { return self.client.serverOnlyStream(path: "grpc.testing.UnimplementedService/UnimplementedStreamingOutputCall", headers: headers) } -} -/// Provides metadata for `Grpc_Testing_UnimplementedServiceClient`. -internal enum Grpc_Testing_UnimplementedServiceMetadata { - internal enum Methods { - internal static let unimplementedCall = Connect.MethodDescriptor(name: "UnimplementedCall", path: "grpc.testing.UnimplementedService/UnimplementedCall") - internal static let unimplementedStreamingOutputCall = Connect.MethodDescriptor(name: "UnimplementedStreamingOutputCall", path: "grpc.testing.UnimplementedService/UnimplementedStreamingOutputCall") + 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) + } } } @@ -315,13 +313,12 @@ internal final class Grpc_Testing_ReconnectServiceClient: Grpc_Testing_Reconnect internal func `stop`(request: Grpc_Testing_Empty, headers: Connect.Headers = [:]) async -> ResponseMessage { return await self.client.unary(path: "grpc.testing.ReconnectService/Stop", request: request, headers: headers) } -} -/// Provides metadata for `Grpc_Testing_ReconnectServiceClient`. -internal enum Grpc_Testing_ReconnectServiceMetadata { - internal enum Methods { - internal static let start = Connect.MethodDescriptor(name: "Start", path: "grpc.testing.ReconnectService/Start") - internal static let stop = Connect.MethodDescriptor(name: "Stop", path: "grpc.testing.ReconnectService/Stop") + 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) + } } } @@ -368,13 +365,12 @@ internal final class Grpc_Testing_LoadBalancerStatsServiceClient: Grpc_Testing_L internal func `getClientAccumulatedStats`(request: Grpc_Testing_LoadBalancerAccumulatedStatsRequest, headers: Connect.Headers = [:]) async -> ResponseMessage { return await self.client.unary(path: "grpc.testing.LoadBalancerStatsService/GetClientAccumulatedStats", request: request, headers: headers) } -} -/// Provides metadata for `Grpc_Testing_LoadBalancerStatsServiceClient`. -internal enum Grpc_Testing_LoadBalancerStatsServiceMetadata { - internal enum Methods { - internal static let getClientStats = Connect.MethodDescriptor(name: "GetClientStats", path: "grpc.testing.LoadBalancerStatsService/GetClientStats") - internal static let getClientAccumulatedStats = Connect.MethodDescriptor(name: "GetClientAccumulatedStats", path: "grpc.testing.LoadBalancerStatsService/GetClientAccumulatedStats") + 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) + } } } @@ -417,13 +413,12 @@ internal final class Grpc_Testing_XdsUpdateHealthServiceClient: Grpc_Testing_Xds internal func `setNotServing`(request: Grpc_Testing_Empty, headers: Connect.Headers = [:]) async -> ResponseMessage { return await self.client.unary(path: "grpc.testing.XdsUpdateHealthService/SetNotServing", request: request, headers: headers) } -} -/// Provides metadata for `Grpc_Testing_XdsUpdateHealthServiceClient`. -internal enum Grpc_Testing_XdsUpdateHealthServiceMetadata { - internal enum Methods { - internal static let setServing = Connect.MethodDescriptor(name: "SetServing", path: "grpc.testing.XdsUpdateHealthService/SetServing") - internal static let setNotServing = Connect.MethodDescriptor(name: "SetNotServing", path: "grpc.testing.XdsUpdateHealthService/SetNotServing") + 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) + } } } @@ -454,11 +449,10 @@ internal final class Grpc_Testing_XdsUpdateClientConfigureServiceClient: Grpc_Te internal func `configure`(request: Grpc_Testing_ClientConfigureRequest, headers: Connect.Headers = [:]) async -> ResponseMessage { return await self.client.unary(path: "grpc.testing.XdsUpdateClientConfigureService/Configure", request: request, headers: headers) } -} -/// Provides metadata for `Grpc_Testing_XdsUpdateClientConfigureServiceClient`. -internal enum Grpc_Testing_XdsUpdateClientConfigureServiceMetadata { - internal enum Methods { - internal static let configure = Connect.MethodDescriptor(name: "Configure", path: "grpc.testing.XdsUpdateClientConfigureService/Configure") + internal enum Metadata { + internal enum Methods { + internal static let configure = Connect.MethodSpec(name: "Configure", service: "grpc.testing.XdsUpdateClientConfigureService", type: .unary) + } } } diff --git a/Tests/ConnectLibraryTests/buf.gen.yaml b/Tests/ConnectLibraryTests/buf.gen.yaml index 2966ad45..61143a5c 100644 --- a/Tests/ConnectLibraryTests/buf.gen.yaml +++ b/Tests/ConnectLibraryTests/buf.gen.yaml @@ -7,7 +7,6 @@ plugins: opt: > GenerateAsyncMethods=true, GenerateCallbackMethods=true, - GenerateServiceMetadata=true, Visibility=Internal out: ./Generated path: ../../.tmp/bin/protoc-gen-connect-swift From 1a941aaacc741650d3183595bd0afcb172b794bf Mon Sep 17 00:00:00 2001 From: Michael Rebello Date: Thu, 19 Jan 2023 10:21:51 -0800 Subject: [PATCH 4/5] docstrings --- Libraries/Connect/Interfaces/MethodSpec.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Libraries/Connect/Interfaces/MethodSpec.swift b/Libraries/Connect/Interfaces/MethodSpec.swift index 352d85b5..ff8cec48 100644 --- a/Libraries/Connect/Interfaces/MethodSpec.swift +++ b/Libraries/Connect/Interfaces/MethodSpec.swift @@ -14,14 +14,15 @@ /// Contains metadata for a specific RPC method. public struct MethodSpec: Equatable, Codable { - /// The name of the RPC method (1:1 with the `.proto` file). + /// 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. + /// The fully qualified name of the method's service. E.g., `foo.v1.FooService`. public let service: String - /// The type of method that this is (unary, bidirectional stream, etc.). + /// The type of method (unary, bidirectional stream, etc.). public let type: MethodType - /// The path of the RPC, constructed using the package, service, and RPC name. + /// 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)" } From 3b829d1c80f1f10ebc0c32a72ff3d9aba42ec181 Mon Sep 17 00:00:00 2001 From: Michael Rebello Date: Thu, 19 Jan 2023 10:33:14 -0800 Subject: [PATCH 5/5] don't generate for eliza --- .../GeneratedSources/eliza.connect.swift | 8 -------- Examples/buf.gen.yaml | 4 +++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift b/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift index 4d9057e1..410f6dd8 100644 --- a/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift +++ b/Examples/ElizaSharedSources/GeneratedSources/eliza.connect.swift @@ -47,12 +47,4 @@ internal final class Buf_Connect_Demo_Eliza_V1_ElizaServiceClient: Buf_Connect_D internal func `introduce`(headers: Connect.Headers = [:]) -> any Connect.ServerOnlyAsyncStreamInterface { return self.client.serverOnlyStream(path: "buf.connect.demo.eliza.v1.ElizaService/Introduce", headers: headers) } - - internal enum Metadata { - internal enum Methods { - internal static let say = Connect.MethodSpec(name: "Say", service: "buf.connect.demo.eliza.v1.ElizaService", type: .unary) - internal static let converse = Connect.MethodSpec(name: "Converse", service: "buf.connect.demo.eliza.v1.ElizaService", type: .bidirectionalStream) - internal static let introduce = Connect.MethodSpec(name: "Introduce", service: "buf.connect.demo.eliza.v1.ElizaService", type: .serverStream) - } - } } diff --git a/Examples/buf.gen.yaml b/Examples/buf.gen.yaml index 3766e1d9..ce0f6787 100644 --- a/Examples/buf.gen.yaml +++ b/Examples/buf.gen.yaml @@ -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