From 3a12e8fff3c964a11811afd2bc296b48df34a318 Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Mon, 15 Jan 2024 19:49:55 +0000 Subject: [PATCH 1/4] [CodeGenLib] Add generated names properties Motivation: The names of the namespaces/service/methods used in the type names in the generated code can be different from the names identifying the namespaces/service/methods in the IDL. This is why we need to have different properties for them. Methods also have a third name - for the function signatures. Modifications: - Added the new names properties in the ServiceDescriptor and MethodDescriptor from CodeGenerationRequest - Modified the existing tests accordingly - Added new validation methods verifying the generated names (or signature name) - Added tests for the new checks Result: Users will be able to set a identifying name, a generated name (and a signature name for the methods) for the namespaces and services. --- Sources/GRPCCodeGen/CodeGenError.swift | 6 + .../GRPCCodeGen/CodeGenerationRequest.swift | 24 +- .../Translator/ClientCodeTranslator.swift | 47 +-- .../IDLToStructuredSwiftTranslator.swift | 143 +++++++- .../Translator/ServerCodeTranslator.swift | 21 +- .../Translator/TypealiasTranslator.swift | 35 +- ...lientCodeTranslatorSnippetBasedTests.swift | 252 ++++++++------ ...uredSwiftTranslatorSnippetBasedTests.swift | 320 +++++++++++++++++- ...erverCodeTranslatorSnippetBasedTests.swift | 224 ++++++------ ...TypealiasTranslatorSnippetBasedTests.swift | 150 +++++--- 10 files changed, 889 insertions(+), 333 deletions(-) diff --git a/Sources/GRPCCodeGen/CodeGenError.swift b/Sources/GRPCCodeGen/CodeGenError.swift index 6cfffa32c..d81287229 100644 --- a/Sources/GRPCCodeGen/CodeGenError.swift +++ b/Sources/GRPCCodeGen/CodeGenError.swift @@ -39,6 +39,7 @@ extension CodeGenError { case nonUniqueServiceName case nonUniqueMethodName case invalidKind + case invalidGeneratedNamespace } private var value: Value @@ -60,6 +61,11 @@ extension CodeGenError { public static var invalidKind: Self { Self(.invalidKind) } + + /// An invalid generated namespace is used for an import. + public static var invalidGeneratedNamespace: Self { + Self(.invalidGeneratedNamespace) + } } } diff --git a/Sources/GRPCCodeGen/CodeGenerationRequest.swift b/Sources/GRPCCodeGen/CodeGenerationRequest.swift index 1efb81636..3b7c49236 100644 --- a/Sources/GRPCCodeGen/CodeGenerationRequest.swift +++ b/Sources/GRPCCodeGen/CodeGenerationRequest.swift @@ -216,18 +216,24 @@ public struct CodeGenerationRequest { } /// Represents a service described in an IDL file. - public struct ServiceDescriptor { + public struct ServiceDescriptor: Hashable { /// Documentation from comments above the IDL service description. public var documentation: String /// Service name. public var name: String + /// The service name used in the generated type names. + public var generatedName: String + /// The service namespace. /// /// For `.proto` files it is the package name. public var namespace: String + /// The namespace identifier used in the generated type names. + public var generatedNamespace: String + /// A description of each method of a service. /// /// - SeeAlso: ``MethodDescriptor``. @@ -236,23 +242,33 @@ public struct CodeGenerationRequest { public init( documentation: String, name: String, + generatedName: String, namespace: String, + generatedNamespace: String, methods: [MethodDescriptor] ) { self.documentation = documentation self.name = name + self.generatedName = generatedName self.namespace = namespace + self.generatedNamespace = generatedNamespace self.methods = methods } /// Represents a method described in an IDL file. - public struct MethodDescriptor { + public struct MethodDescriptor: Hashable { /// Documentation from comments above the IDL method description. public var documentation: String /// Method name. public var name: String + /// The name used in the generated type names. + public var generatedName: String + + /// The function name used in the generated code in declarations of the method. + public var signatureName: String + /// Identifies if the method is input streaming. public var isInputStreaming: Bool @@ -268,6 +284,8 @@ public struct CodeGenerationRequest { public init( documentation: String, name: String, + generatedName: String, + signatureName: String, isInputStreaming: Bool, isOutputStreaming: Bool, inputType: String, @@ -275,6 +293,8 @@ public struct CodeGenerationRequest { ) { self.documentation = documentation self.name = name + self.generatedName = generatedName + self.signatureName = signatureName self.isInputStreaming = isInputStreaming self.isOutputStreaming = isOutputStreaming self.inputType = inputType diff --git a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift index 385f513f7..854431e9b 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift @@ -22,40 +22,40 @@ /// a representation for the following generated code: /// /// ```swift -/// public protocol foo_BarClientProtocol: Sendable { +/// public protocol Foo_BarClientProtocol: Sendable { /// func baz( /// request: ClientRequest.Single, /// serializer: some MessageSerializer, /// deserializer: some MessageDeserializer, -/// _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R -/// ) async throws -> ServerResponse.Stream +/// _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R +/// ) async throws -> ServerResponse.Stream /// } -/// extension foo.Bar.ClientProtocol { +/// extension Foo.Bar.ClientProtocol { /// public func get( -/// request: ClientRequest.Single, -/// _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R +/// request: ClientRequest.Single, +/// _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R /// ) async rethrows -> R { /// try await self.baz( /// request: request, -/// serializer: ProtobufSerializer(), -/// deserializer: ProtobufDeserializer(), +/// serializer: ProtobufSerializer(), +/// deserializer: ProtobufDeserializer(), /// body /// ) /// } -/// struct foo_BarClient: foo.Bar.ClientProtocol { +/// struct Foo_BarClient: Foo.Bar.ClientProtocol { /// let client: GRPCCore.GRPCClient /// init(client: GRPCCore.GRPCClient) { /// self.client = client /// } /// func methodA( -/// request: ClientRequest.Stream, -/// serializer: some MessageSerializer, -/// deserializer: some MessageDeserializer, -/// _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R +/// request: ClientRequest.Stream, +/// serializer: some MessageSerializer, +/// deserializer: some MessageDeserializer, +/// _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R /// ) async rethrows -> R { /// try await self.client.clientStreaming( /// request: request, -/// descriptor: namespaceA.ServiceA.Methods.methodA.descriptor, +/// descriptor: NamespaceA.ServiceA.Methods.MethodA.descriptor, /// serializer: serializer, /// deserializer: deserializer, /// handler: body @@ -108,7 +108,7 @@ extension ClientCodeTranslator { let clientProtocol = Declaration.protocol( ProtocolDescription( - name: "\(service.namespacedPrefix)ClientProtocol", + name: "\(service.namespacedGeneratedName)ClientProtocol", conformances: ["Sendable"], members: methods ) @@ -130,7 +130,7 @@ extension ClientCodeTranslator { } let clientProtocolExtension = Declaration.extension( ExtensionDescription( - onType: "\(service.namespacedTypealiasPrefix).ClientProtocol", + onType: "\(service.namespacedTypealiasGeneratedName).ClientProtocol", declarations: methods ) ) @@ -151,7 +151,7 @@ extension ClientCodeTranslator { ) let functionSignature = FunctionSignatureDescription( kind: .function( - name: method.name, + name: method.signatureName, isStatic: false ), generics: [.member("R")], @@ -180,7 +180,7 @@ extension ClientCodeTranslator { ) -> [CodeBlock] { let functionCall = Expression.functionCall( calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self"), right: method.name) + MemberAccessDescription(left: .identifierPattern("self"), right: method.signatureName) ), arguments: [ FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")), @@ -317,8 +317,8 @@ extension ClientCodeTranslator { return .struct( StructDescription( - name: "\(service.namespacedPrefix)Client", - conformances: ["\(service.namespacedTypealiasPrefix).ClientProtocol"], + name: "\(service.namespacedGeneratedName)Client", + conformances: ["\(service.namespacedTypealiasGeneratedName).ClientProtocol"], members: [clientProperty, initializer] + methods ) ) @@ -380,7 +380,7 @@ extension ClientCodeTranslator { .init( label: "descriptor", expression: .identifierPattern( - "\(service.namespacedTypealiasPrefix).Methods.\(method.name).descriptor" + "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName).descriptor" ) ), .init(label: "serializer", expression: .identifierPattern("serializer")), @@ -395,7 +395,7 @@ extension ClientCodeTranslator { return .function( kind: .function( - name: "\(method.name)", + name: "\(method.signatureName)", isStatic: false ), generics: [.member("R")], @@ -418,7 +418,8 @@ extension ClientCodeTranslator { service: CodeGenerationRequest.ServiceDescriptor, type: InputOutputType ) -> String { - var components: String = "\(service.namespacedTypealiasPrefix).Methods.\(method.name)" + var components: String = + "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName)" switch type { case .input: diff --git a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift index 947a1add1..451b0086e 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift @@ -90,11 +90,19 @@ extension IDLToStructuredSwiftTranslator { } private func validateInput(_ codeGenerationRequest: CodeGenerationRequest) throws { + let servicesByGeneratedNamespace = Dictionary( + grouping: codeGenerationRequest.services, + by: { $0.generatedNamespace } + ) let servicesByNamespace = Dictionary( grouping: codeGenerationRequest.services, by: { $0.namespace } ) - try self.checkServiceNamesAreUnique(for: servicesByNamespace) + try self.checkServiceNamesAreUnique(for: servicesByGeneratedNamespace) + try self.checkServicesNamespacesAndGeneratedNamespacesCoincide( + servicesByNamespace: servicesByNamespace + ) + try checkEmptyNamespaceAndGeneratedNamespace(for: codeGenerationRequest.services) for service in codeGenerationRequest.services { try self.checkMethodNamesAreUnique(in: service) } @@ -109,21 +117,23 @@ extension IDLToStructuredSwiftTranslator { if let noNamespaceServices = servicesByNamespace[""] { let namespaces = servicesByNamespace.keys for service in noNamespaceServices { - if namespaces.contains(service.name) { + if namespaces.contains(service.generatedName) { throw CodeGenError( code: .nonUniqueServiceName, message: """ - Services with no namespace must not have the same names as the namespaces. \ - \(service.name) is used as a name for a service with no namespace and a namespace. + Services with no namespace must not have the same generated names as the namespaces. \ + \(service.generatedName) is used as a generated name for a service with no namespace and a namespace. """ ) } } } - // Check that service names are unique within each namespace. + // Check that service names and service generated names are unique within each namespace. for (namespace, services) in servicesByNamespace { var serviceNames: Set = [] + var generatedNames: Set = [] + for service in services { if serviceNames.contains(service.name) { let errorMessage: String @@ -144,6 +154,26 @@ extension IDLToStructuredSwiftTranslator { ) } serviceNames.insert(service.name) + + if generatedNames.contains(service.generatedName) { + let errorMessage: String + if namespace.isEmpty { + errorMessage = """ + Services in an empty namespace must have unique generated names. \ + \(service.generatedName) is used as a name for multiple services without namespaces. + """ + } else { + errorMessage = """ + Services within the same namespace must have unique generated names. \ + \(service.generatedName) is used as a generated name for multiple services in the \(service.namespace) namespace. + """ + } + throw CodeGenError( + code: .nonUniqueServiceName, + message: errorMessage + ) + } + generatedNames.insert(service.generatedName) } } } @@ -152,6 +182,7 @@ extension IDLToStructuredSwiftTranslator { private func checkMethodNamesAreUnique( in service: CodeGenerationRequest.ServiceDescriptor ) throws { + // Check the method names. let methodNames = service.methods.map { $0.name } var seenNames = Set() @@ -167,23 +198,113 @@ extension IDLToStructuredSwiftTranslator { } seenNames.insert(methodName) } + + // Check the method generated names. + let generatedNames = service.methods.map { $0.generatedName } + var seenGeneratedNames = Set() + + for generatedName in generatedNames { + if seenGeneratedNames.contains(generatedName) { + throw CodeGenError( + code: .nonUniqueMethodName, + message: """ + Methods of a service must have unique generated names. \ + \(generatedName) is used as a generated name for multiple methods of the \(service.name) service. + """ + ) + } + seenGeneratedNames.insert(generatedName) + } + + // Check the function signature names. + let signatureNames = service.methods.map { $0.signatureName } + var seenSignatureNames = Set() + + for signatureName in signatureNames { + if seenSignatureNames.contains(signatureName) { + throw CodeGenError( + code: .nonUniqueMethodName, + message: """ + Methods of a service must have unique signature names. \ + \(signatureName) is used as a signature name for multiple methods of the \(service.name) service. + """ + ) + } + seenSignatureNames.insert(signatureName) + } + } + + private func checkEmptyNamespaceAndGeneratedNamespace( + for services: [CodeGenerationRequest.ServiceDescriptor] + ) throws { + for service in services { + if service.namespace.isEmpty { + if !service.generatedNamespace.isEmpty { + throw CodeGenError( + code: .invalidGeneratedNamespace, + message: """ + Services with an empty namespace must have an empty generated namespace. \ + \(service.name) has an empty namespace, but a non-empty generated namespace. + """ + ) + } + } else { + if service.generatedNamespace.isEmpty { + throw CodeGenError( + code: .invalidGeneratedNamespace, + message: """ + Services with a non-empty namespace must have a non-empty generated namespace. \ + \(service.name) has a non-empty namespace, but an empty generated namespace. + """ + ) + } + } + } + } + + private func checkServicesNamespacesAndGeneratedNamespacesCoincide( + servicesByNamespace: [String: [CodeGenerationRequest.ServiceDescriptor]] + ) throws { + for (_, services) in servicesByNamespace { + let generatedNamespace = services[0].generatedNamespace + for service in services { + if service.generatedNamespace != generatedNamespace { + throw CodeGenError( + code: .invalidGeneratedNamespace, + message: """ + All services within a namespace must have the same generated namespace. \ + \(service.name) has not the same generated namespace as other services \ + within the \(service.namespace) namespace. + """ + ) + } + } + } } } extension CodeGenerationRequest.ServiceDescriptor { - var namespacedTypealiasPrefix: String { - if self.namespace.isEmpty { - return self.name + var namespacedTypealiasGeneratedName: String { + if self.generatedNamespace.isEmpty { + return self.generatedName } else { - return "\(self.namespace).\(self.name)" + return "\(self.generatedNamespace).\(self.generatedName)" + } + } + + var namespacedGeneratedName: String { + if self.generatedNamespace.isEmpty { + return self.generatedName + } else { + return "\(self.generatedNamespace)_\(self.generatedName)" } } - var namespacedPrefix: String { + var fullyQualifiedName: String { if self.namespace.isEmpty { return self.name } else { - return "\(self.namespace)_\(self.name)" + return "\(self.namespace).\(self.name)" } } } diff --git a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift index 661389492..df6263b63 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift @@ -126,7 +126,7 @@ extension ServerCodeTranslator { in service: CodeGenerationRequest.ServiceDescriptor ) -> FunctionSignatureDescription { return FunctionSignatureDescription( - kind: .function(name: method.name), + kind: .function(name: method.signatureName), parameters: [ .init( label: "request", @@ -244,7 +244,7 @@ extension ServerCodeTranslator { let getFunctionCall = Expression.functionCall( calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self"), right: method.name) + MemberAccessDescription(left: .identifierPattern("self"), right: method.signatureName) ), arguments: [ FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")) @@ -304,7 +304,7 @@ extension ServerCodeTranslator { ) let functionSignature = FunctionSignatureDescription( - kind: .function(name: method.name), + kind: .function(name: method.signatureName), parameters: [ .init( label: "request", @@ -382,7 +382,7 @@ extension ServerCodeTranslator { // Call to the corresponding ServiceProtocol method. let serviceProtocolMethod = Expression.functionCall( calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self"), right: method.name) + MemberAccessDescription(left: .identifierPattern("self"), right: method.signatureName) ), arguments: [FunctionArgumentDescription(label: "request", expression: serverRequest)] ) @@ -430,7 +430,8 @@ extension ServerCodeTranslator { service: CodeGenerationRequest.ServiceDescriptor, type: InputOutputType ) -> String { - var components: String = "\(service.namespacedTypealiasPrefix).Methods.\(method.name)" + var components: String = + "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName)" switch type { case .input: @@ -447,7 +448,7 @@ extension ServerCodeTranslator { for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, service: CodeGenerationRequest.ServiceDescriptor ) -> String { - return "\(service.namespacedTypealiasPrefix).Methods.\(method.name).descriptor" + return "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName).descriptor" } /// Generates the fully qualified name of the type alias for a service protocol. @@ -456,9 +457,9 @@ extension ServerCodeTranslator { streaming: Bool ) -> String { if streaming { - return "\(service.namespacedTypealiasPrefix).StreamingServiceProtocol" + return "\(service.namespacedTypealiasGeneratedName).StreamingServiceProtocol" } - return "\(service.namespacedTypealiasPrefix).ServiceProtocol" + return "\(service.namespacedTypealiasGeneratedName).ServiceProtocol" } /// Generates the name of a service protocol. @@ -467,8 +468,8 @@ extension ServerCodeTranslator { streaming: Bool ) -> String { if streaming { - return "\(service.namespacedPrefix)StreamingServiceProtocol" + return "\(service.namespacedGeneratedName)StreamingServiceProtocol" } - return "\(service.namespacedPrefix)ServiceProtocol" + return "\(service.namespacedGeneratedName)ServiceProtocol" } } diff --git a/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift index 56c43942e..47bb4cf6a 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift @@ -21,7 +21,7 @@ /// For example, in the case of the ``Echo`` service, the ``TypealiasTranslator`` will create /// a representation for the following generated code: /// ```swift -/// public enum echo { +/// public enum Echo { /// public enum Echo { /// public enum Methods { /// public enum Get { @@ -64,14 +64,14 @@ struct TypealiasTranslator: SpecializedTranslator { func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] { var codeBlocks: [CodeBlock] = [] let services = codeGenerationRequest.services - let servicesByNamespace = Dictionary(grouping: services, by: { $0.namespace }) + let servicesByNamespace = Dictionary(grouping: services, by: { $0.generatedNamespace }) // Sorting the keys and the services in each list of the dictionary is necessary // so that the generated enums are deterministically ordered. - for (namespace, services) in servicesByNamespace.sorted(by: { $0.key < $1.key }) { + for (generatedNamespace, services) in servicesByNamespace.sorted(by: { $0.key < $1.key }) { let namespaceCodeBlocks = try self.makeNamespaceEnum( - for: namespace, - containing: services.sorted(by: { $0.name < $1.name }) + for: generatedNamespace, + containing: services.sorted(by: { $0.generatedName < $1.generatedName }) ) codeBlocks.append(contentsOf: namespaceCodeBlocks) } @@ -110,7 +110,7 @@ extension TypealiasTranslator { private func makeServiceEnum( from service: CodeGenerationRequest.ServiceDescriptor ) throws -> Declaration { - var serviceEnum = EnumDescription(name: service.name) + var serviceEnum = EnumDescription(name: service.generatedName) var methodsEnum = EnumDescription(name: "Methods") let methods = service.methods @@ -148,7 +148,7 @@ extension TypealiasTranslator { from method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, in service: CodeGenerationRequest.ServiceDescriptor ) -> Declaration { - var methodEnum = EnumDescription(name: method.name) + var methodEnum = EnumDescription(name: method.generatedName) let inputTypealias = Declaration.typealias( name: "Input", @@ -174,21 +174,13 @@ extension TypealiasTranslator { in service: CodeGenerationRequest.ServiceDescriptor ) -> Declaration { let descriptorDeclarationLeft = Expression.identifier(.pattern("descriptor")) - - let fullyQualifiedServiceName: String - if service.namespace.isEmpty { - fullyQualifiedServiceName = service.name - } else { - fullyQualifiedServiceName = "\(service.namespace).\(service.name)" - } - let descriptorDeclarationRight = Expression.functionCall( FunctionCallDescription( calledExpression: .identifierType(.member("MethodDescriptor")), arguments: [ FunctionArgumentDescription( label: "service", - expression: .literal(fullyQualifiedServiceName) + expression: .literal(service.fullyQualifiedName) ), FunctionArgumentDescription( label: "method", @@ -197,6 +189,7 @@ extension TypealiasTranslator { ] ) ) + return .variable( isStatic: true, kind: .let, @@ -209,7 +202,7 @@ extension TypealiasTranslator { for service: CodeGenerationRequest.ServiceDescriptor ) -> Declaration { var methodDescriptors = [Expression]() - let methodNames = service.methods.map { $0.name } + let methodNames = service.methods.map { $0.generatedName } for methodName in methodNames { let methodDescriptorPath = Expression.memberAccess( @@ -235,11 +228,11 @@ extension TypealiasTranslator { ) -> [Declaration] { let streamingServiceProtocolTypealias = Declaration.typealias( name: "StreamingServiceProtocol", - existingType: .member("\(service.namespacedPrefix)ServiceStreamingProtocol") + existingType: .member("\(service.namespacedGeneratedName)ServiceStreamingProtocol") ) let serviceProtocolTypealias = Declaration.typealias( name: "ServiceProtocol", - existingType: .member("\(service.namespacedPrefix)ServiceProtocol") + existingType: .member("\(service.namespacedGeneratedName)ServiceProtocol") ) return [streamingServiceProtocolTypealias, serviceProtocolTypealias] @@ -250,7 +243,7 @@ extension TypealiasTranslator { ) -> Declaration { return .typealias( name: "ClientProtocol", - existingType: .member("\(service.namespacedPrefix)ClientProtocol") + existingType: .member("\(service.namespacedGeneratedName)ClientProtocol") ) } @@ -259,7 +252,7 @@ extension TypealiasTranslator { ) -> Declaration { return .typealias( name: "Client", - existingType: .member("\(service.namespacedPrefix)Client") + existingType: .member("\(service.namespacedGeneratedName)Client") ) } } diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift index 6e4daa1a6..26d910583 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift @@ -27,7 +27,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorUnaryMethod() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "methodA", + name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -36,50 +38,52 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAClientProtocol: Sendable { + protocol NamespaceA_ServiceAClientProtocol: Sendable { /// Documentation for MethodA func methodA( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable } - extension namespaceA.ServiceA.ClientProtocol { + extension NamespaceA.ServiceA.ClientProtocol { func methodA( - request: ClientRequest.Single, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Single, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.methodA( request: request, - serializer: ProtobufSerializer(), - deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), + deserializer: ProtobufDeserializer(), body ) } } /// Documentation for ServiceA - struct namespaceA_ServiceAClient: namespaceA.ServiceA.ClientProtocol { + struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { let client: GRPCCore.GRPCClient init(client: GRPCCore.GRPCClient) { self.client = client } /// Documentation for MethodA func methodA( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.client.unary( request: request, - descriptor: namespaceA.ServiceA.Methods.methodA.descriptor, + descriptor: NamespaceA.ServiceA.Methods.MethodA.descriptor, serializer: serializer, deserializer: deserializer, handler: body @@ -97,7 +101,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorClientStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "methodA", + name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -106,50 +112,52 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAClientProtocol: Sendable { + protocol NamespaceA_ServiceAClientProtocol: Sendable { /// Documentation for MethodA func methodA( - request: ClientRequest.Stream, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Stream, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable } - extension namespaceA.ServiceA.ClientProtocol { + extension NamespaceA.ServiceA.ClientProtocol { func methodA( - request: ClientRequest.Stream, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Stream, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.methodA( request: request, - serializer: ProtobufSerializer(), - deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), + deserializer: ProtobufDeserializer(), body ) } } /// Documentation for ServiceA - struct namespaceA_ServiceAClient: namespaceA.ServiceA.ClientProtocol { + struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { let client: GRPCCore.GRPCClient init(client: GRPCCore.GRPCClient) { self.client = client } /// Documentation for MethodA func methodA( - request: ClientRequest.Stream, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Stream, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.client.clientStreaming( request: request, - descriptor: namespaceA.ServiceA.Methods.methodA.descriptor, + descriptor: NamespaceA.ServiceA.Methods.MethodA.descriptor, serializer: serializer, deserializer: deserializer, handler: body @@ -168,6 +176,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "methodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -176,50 +186,52 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAClientProtocol: Sendable { + protocol NamespaceA_ServiceAClientProtocol: Sendable { /// Documentation for MethodA func methodA( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable } - extension namespaceA.ServiceA.ClientProtocol { + extension NamespaceA.ServiceA.ClientProtocol { func methodA( - request: ClientRequest.Single, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Single, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable { try await self.methodA( request: request, - serializer: ProtobufSerializer(), - deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), + deserializer: ProtobufDeserializer(), body ) } } /// Documentation for ServiceA - struct namespaceA_ServiceAClient: namespaceA.ServiceA.ClientProtocol { + struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { let client: GRPCCore.GRPCClient init(client: GRPCCore.GRPCClient) { self.client = client } /// Documentation for MethodA func methodA( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable { try await self.client.serverStreaming( request: request, - descriptor: namespaceA.ServiceA.Methods.methodA.descriptor, + descriptor: NamespaceA.ServiceA.Methods.MethodA.descriptor, serializer: serializer, deserializer: deserializer, handler: body @@ -238,6 +250,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "methodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: true, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -246,50 +260,52 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAClientProtocol: Sendable { + protocol NamespaceA_ServiceAClientProtocol: Sendable { /// Documentation for MethodA func methodA( - request: ClientRequest.Stream, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Stream, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable } - extension namespaceA.ServiceA.ClientProtocol { + extension NamespaceA.ServiceA.ClientProtocol { func methodA( - request: ClientRequest.Stream, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Stream, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable { try await self.methodA( request: request, - serializer: ProtobufSerializer(), - deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), + deserializer: ProtobufDeserializer(), body ) } } /// Documentation for ServiceA - struct namespaceA_ServiceAClient: namespaceA.ServiceA.ClientProtocol { + struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { let client: GRPCCore.GRPCClient init(client: GRPCCore.GRPCClient) { self.client = client } /// Documentation for MethodA func methodA( - request: ClientRequest.Stream, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Stream, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable { try await self.client.bidirectionalStreaming( request: request, - descriptor: namespaceA.ServiceA.Methods.methodA.descriptor, + descriptor: NamespaceA.ServiceA.Methods.MethodA.descriptor, serializer: serializer, deserializer: deserializer, handler: body @@ -308,6 +324,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", name: "methodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -316,6 +334,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let methodB = MethodDescriptor( documentation: "Documentation for MethodB", name: "methodB", + generatedName: "MethodB", + signatureName: "methodB", isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -324,68 +344,70 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [methodA, methodB] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAClientProtocol: Sendable { + protocol NamespaceA_ServiceAClientProtocol: Sendable { /// Documentation for MethodA func methodA( - request: ClientRequest.Stream, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Stream, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable /// Documentation for MethodB func methodB( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable } - extension namespaceA.ServiceA.ClientProtocol { + extension NamespaceA.ServiceA.ClientProtocol { func methodA( - request: ClientRequest.Stream, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Stream, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.methodA( request: request, - serializer: ProtobufSerializer(), - deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), + deserializer: ProtobufDeserializer(), body ) } func methodB( - request: ClientRequest.Single, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Single, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable { try await self.methodB( request: request, - serializer: ProtobufSerializer(), - deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), + deserializer: ProtobufDeserializer(), body ) } } /// Documentation for ServiceA - struct namespaceA_ServiceAClient: namespaceA.ServiceA.ClientProtocol { + struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { let client: GRPCCore.GRPCClient init(client: GRPCCore.GRPCClient) { self.client = client } /// Documentation for MethodA func methodA( - request: ClientRequest.Stream, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Stream, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.client.clientStreaming( request: request, - descriptor: namespaceA.ServiceA.Methods.methodA.descriptor, + descriptor: NamespaceA.ServiceA.Methods.MethodA.descriptor, serializer: serializer, deserializer: deserializer, handler: body @@ -393,14 +415,14 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } /// Documentation for MethodB func methodB( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Stream) async throws -> R ) async throws -> R where R: Sendable { try await self.client.serverStreaming( request: request, - descriptor: namespaceA.ServiceA.Methods.methodB.descriptor, + descriptor: NamespaceA.ServiceA.Methods.MethodB.descriptor, serializer: serializer, deserializer: deserializer, handler: body @@ -419,6 +441,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "methodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: false, inputType: "ServiceARequest", @@ -427,7 +451,9 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "", + generatedNamespace: "", methods: [method] ) let expectedSwift = @@ -436,21 +462,21 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { protocol ServiceAClientProtocol: Sendable { /// Documentation for MethodA func methodA( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable } extension ServiceA.ClientProtocol { func methodA( - request: ClientRequest.Single, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Single, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.methodA( request: request, - serializer: ProtobufSerializer(), - deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), + deserializer: ProtobufDeserializer(), body ) } @@ -463,14 +489,14 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } /// Documentation for MethodA func methodA( - request: ClientRequest.Single, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R + request: ClientRequest.Single, + serializer: some MessageSerializer, + deserializer: some MessageDeserializer, + _ body: @Sendable @escaping (ClientResponse.Single) async throws -> R ) async throws -> R where R: Sendable { try await self.client.unary( request: request, - descriptor: ServiceA.Methods.methodA.descriptor, + descriptor: ServiceA.Methods.MethodA.descriptor, serializer: serializer, deserializer: deserializer, handler: body @@ -489,23 +515,27 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { let serviceA = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for ServiceB", name: "ServiceB", + generatedName: "ServiceB", namespace: "", + generatedNamespace: "", methods: [] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAClientProtocol: Sendable {} - extension namespaceA.ServiceA.ClientProtocol { + protocol NamespaceA_ServiceAClientProtocol: Sendable {} + extension NamespaceA.ServiceA.ClientProtocol { } /// Documentation for ServiceA - struct namespaceA_ServiceAClient: namespaceA.ServiceA.ClientProtocol { + struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { let client: GRPCCore.GRPCClient init(client: GRPCCore.GRPCClient) { self.client = client diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift index b7a32934b..371d65f1f 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift @@ -150,7 +150,9 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", + generatedName: "AService", namespace: "", + generatedNamespace: "", methods: [] ) @@ -178,11 +180,55 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { } } + func testSameGeneratedNameServicesNoNamespaceError() throws { + let serviceA = ServiceDescriptor( + documentation: "Documentation for AService", + name: "AService", + generatedName: "AService", + namespace: "", + generatedNamespace: "", + methods: [] + ) + + let serviceB = ServiceDescriptor( + documentation: "Documentation for BService", + name: "BService", + generatedName: "AService", + namespace: "", + generatedNamespace: "", + methods: [] + ) + + let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) + let translator = IDLToStructuredSwiftTranslator() + XCTAssertThrowsError( + ofType: CodeGenError.self, + try translator.translate( + codeGenerationRequest: codeGenerationRequest, + client: true, + server: true + ) + ) { + error in + XCTAssertEqual( + error as CodeGenError, + CodeGenError( + code: .nonUniqueServiceName, + message: """ + Services in an empty namespace must have unique generated names. \ + AService is used as a name for multiple services without namespaces. + """ + ) + ) + } + } func testSameNameServicesSameNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", + generatedName: "AService", namespace: "namespacea", + generatedNamespace: "NamespaceA", methods: [] ) @@ -210,10 +256,54 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { } } + func testSameGeneratedNameServicesSameNamespaceError() throws { + let serviceA = ServiceDescriptor( + documentation: "Documentation for AService", + name: "AService", + generatedName: "AService", + namespace: "namespacea", + generatedNamespace: "NamespaceA", + methods: [] + ) + let serviceB = ServiceDescriptor( + documentation: "Documentation for BService", + name: "BService", + generatedName: "AService", + namespace: "namespacea", + generatedNamespace: "NamespaceA", + methods: [] + ) + + let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) + let translator = IDLToStructuredSwiftTranslator() + XCTAssertThrowsError( + ofType: CodeGenError.self, + try translator.translate( + codeGenerationRequest: codeGenerationRequest, + client: true, + server: true + ) + ) { + error in + XCTAssertEqual( + error as CodeGenError, + CodeGenError( + code: .nonUniqueServiceName, + message: """ + Services within the same namespace must have unique generated names. \ + AService is used as a generated name for multiple services in the namespacea namespace. + """ + ) + ) + } + } + func testSameNameMethodsSameServiceError() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -222,7 +312,9 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", + generatedName: "AService", namespace: "namespacea", + generatedNamespace: "NamespaceA", methods: [methodA, methodA] ) @@ -250,17 +342,129 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { } } - func testSameNameNoNamespaceServiceAndNamespaceError() throws { + func testSameGeneratedNameMethodsSameServiceError() throws { + let methodA = MethodDescriptor( + documentation: "Documentation for MethodA", + name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", + isInputStreaming: false, + isOutputStreaming: false, + inputType: "NamespaceA_ServiceARequest", + outputType: "NamespaceA_ServiceAResponse" + ) + let methodB = MethodDescriptor( + documentation: "Documentation for MethodA", + name: "MethodB", + generatedName: "MethodA", + signatureName: "methodB", + isInputStreaming: false, + isOutputStreaming: false, + inputType: "NamespaceA_ServiceARequest", + outputType: "NamespaceA_ServiceAResponse" + ) + let service = ServiceDescriptor( + documentation: "Documentation for AService", + name: "AService", + generatedName: "AService", + namespace: "namespacea", + generatedNamespace: "NamespaceA", + methods: [methodA, methodB] + ) + + let codeGenerationRequest = makeCodeGenerationRequest(services: [service]) + let translator = IDLToStructuredSwiftTranslator() + XCTAssertThrowsError( + ofType: CodeGenError.self, + try translator.translate( + codeGenerationRequest: codeGenerationRequest, + client: true, + server: true + ) + ) { + error in + XCTAssertEqual( + error as CodeGenError, + CodeGenError( + code: .nonUniqueMethodName, + message: """ + Methods of a service must have unique generated names. \ + MethodA is used as a generated name for multiple methods of the AService service. + """ + ) + ) + } + } + + func testSameSignatureNameMethodsSameServiceError() throws { + let methodA = MethodDescriptor( + documentation: "Documentation for MethodA", + name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", + isInputStreaming: false, + isOutputStreaming: false, + inputType: "NamespaceA_ServiceARequest", + outputType: "NamespaceA_ServiceAResponse" + ) + let methodB = MethodDescriptor( + documentation: "Documentation for MethodA", + name: "MethodB", + generatedName: "MethodB", + signatureName: "methodA", + isInputStreaming: false, + isOutputStreaming: false, + inputType: "NamespaceA_ServiceARequest", + outputType: "NamespaceA_ServiceAResponse" + ) + let service = ServiceDescriptor( + documentation: "Documentation for AService", + name: "AService", + generatedName: "AService", + namespace: "namespacea", + generatedNamespace: "NamespaceA", + methods: [methodA, methodB] + ) + + let codeGenerationRequest = makeCodeGenerationRequest(services: [service]) + let translator = IDLToStructuredSwiftTranslator() + XCTAssertThrowsError( + ofType: CodeGenError.self, + try translator.translate( + codeGenerationRequest: codeGenerationRequest, + client: true, + server: true + ) + ) { + error in + XCTAssertEqual( + error as CodeGenError, + CodeGenError( + code: .nonUniqueMethodName, + message: """ + Methods of a service must have unique signature names. \ + methodA is used as a signature name for multiple methods of the AService service. + """ + ) + ) + } + } + + func testSameGeneratedNameNoNamespaceServiceAndNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for SameName service with no namespace", name: "SameName", + generatedName: "SameName", namespace: "", + generatedNamespace: "", methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", + generatedName: "BService", namespace: "SameName", + generatedNamespace: "SameName", methods: [] ) let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) @@ -279,8 +483,118 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueServiceName, message: """ - Services with no namespace must not have the same names as the namespaces. \ - SameName is used as a name for a service with no namespace and a namespace. + Services with no namespace must not have the same generated names as the namespaces. \ + SameName is used as a generated name for a service with no namespace and a namespace. + """ + ) + ) + } + } + + func testEmptyNamespaceAndNonEmptyGeneratedNamespaceError() throws { + let serviceA = ServiceDescriptor( + documentation: "Documentation for SameName service with no namespace", + name: "SameName", + generatedName: "SameName", + namespace: "", + generatedNamespace: "NonEmpty", + methods: [] + ) + + let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA]) + let translator = IDLToStructuredSwiftTranslator() + XCTAssertThrowsError( + ofType: CodeGenError.self, + try translator.translate( + codeGenerationRequest: codeGenerationRequest, + client: true, + server: true + ) + ) { + error in + XCTAssertEqual( + error as CodeGenError, + CodeGenError( + code: .invalidGeneratedNamespace, + message: """ + Services with an empty namespace must have an empty generated namespace. \ + SameName has an empty namespace, but a non-empty generated namespace. + """ + ) + ) + } + } + + func testNonEmptyNamespaceAndEmptyGeneratedNamespaceError() throws { + let serviceA = ServiceDescriptor( + documentation: "Documentation for SameName service with no namespace", + name: "SameName", + generatedName: "SameName", + namespace: "NonEmpty", + generatedNamespace: "", + methods: [] + ) + + let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA]) + let translator = IDLToStructuredSwiftTranslator() + XCTAssertThrowsError( + ofType: CodeGenError.self, + try translator.translate( + codeGenerationRequest: codeGenerationRequest, + client: true, + server: true + ) + ) { + error in + XCTAssertEqual( + error as CodeGenError, + CodeGenError( + code: .invalidGeneratedNamespace, + message: """ + Services with a non-empty namespace must have a non-empty generated namespace. \ + SameName has a non-empty namespace, but an empty generated namespace. + """ + ) + ) + } + } + + func testNamespaceAndGeneratedNamespaceServicesCoincide() throws { + // Same namespaces, different generated namespaces. + let serviceA = ServiceDescriptor( + documentation: "Documentation for SameName service with no namespace", + name: "ServiceA", + generatedName: "ServiceA", + namespace: "namespaceA", + generatedNamespace: "NamespaceA", + methods: [] + ) + var serviceB = ServiceDescriptor( + documentation: "Documentation for SameName service with no namespace", + name: "ServiceB", + generatedName: "ServiceB", + namespace: "namespaceA", + generatedNamespace: "NamespaceB", + methods: [] + ) + var codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) + let translator = IDLToStructuredSwiftTranslator() + XCTAssertThrowsError( + ofType: CodeGenError.self, + try translator.translate( + codeGenerationRequest: codeGenerationRequest, + client: true, + server: true + ) + ) { + error in + XCTAssertEqual( + error as CodeGenError, + CodeGenError( + code: .invalidGeneratedNamespace, + message: """ + Services within a namespace must have the same generated namespace. \ + Not all services within namespaceA have the NamespaceA generated namespace. """ ) ) diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift index 89df4bc52..4496a4e54 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift @@ -27,7 +27,9 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorUnaryMethod() throws { let method = MethodDescriptor( documentation: "Documentation for unaryMethod", - name: "unaryMethod", + name: "UnaryMethod", + generatedName: "Unary", + signatureName: "unary", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -35,39 +37,41 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", + name: "AlongNameForServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { + protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for unaryMethod - func unaryMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func unary(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. - public extension namespaceA.ServiceA.StreamingServiceProtocol { + public extension NamespaceA.ServiceA.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) { router.registerHandler( - for: namespaceA.ServiceA.Methods.unaryMethod.descriptor, - deserializer: ProtobufDeserializer(), - serializer: ProtobufSerializer(), + for: NamespaceA.ServiceA.Methods.Unary.descriptor, + deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), handler: { request in - try await self.unaryMethod(request: request) + try await self.unary(request: request) } ) } } /// Documentation for ServiceA - protocol namespaceA_ServiceAServiceProtocol: namespaceA.ServiceA.StreamingServiceProtocol { + protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol { /// Documentation for unaryMethod - func unaryMethod(request: ServerRequest.Single) async throws -> ServerResponse.Single + func unary(request: ServerRequest.Single) async throws -> ServerResponse.Single } - /// Partial conformance to `namespaceA_ServiceAStreamingServiceProtocol`. - public extension namespaceA.ServiceA.ServiceProtocol { - func unaryMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { - let response = try await self.unaryMethod(request: ServerRequest.Single(stream: request)) + /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. + public extension NamespaceA.ServiceA.ServiceProtocol { + func unary(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { + let response = try await self.unary(request: ServerRequest.Single(stream: request)) return ServerResponse.Stream(single: response) } } @@ -82,7 +86,9 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorInputStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for inputStreamingMethod", - name: "inputStreamingMethod", + name: "InputStreamingMethod", + generatedName: "InputStreaming", + signatureName: "inputStreaming", isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -91,38 +97,40 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { + protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for inputStreamingMethod - func inputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. - public extension namespaceA.ServiceA.StreamingServiceProtocol { + public extension NamespaceA.ServiceA.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) { router.registerHandler( - for: namespaceA.ServiceA.Methods.inputStreamingMethod.descriptor, - deserializer: ProtobufDeserializer(), - serializer: ProtobufSerializer(), + for: NamespaceA.ServiceA.Methods.InputStreaming.descriptor, + deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), handler: { request in - try await self.inputStreamingMethod(request: request) + try await self.inputStreaming(request: request) } ) } } /// Documentation for ServiceA - protocol namespaceA_ServiceAServiceProtocol: namespaceA.ServiceA.StreamingServiceProtocol { + protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol { /// Documentation for inputStreamingMethod - func inputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Single + func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Single } - /// Partial conformance to `namespaceA_ServiceAStreamingServiceProtocol`. - public extension namespaceA.ServiceA.ServiceProtocol { - func inputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { - let response = try await self.inputStreamingMethod(request: request) + /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. + public extension NamespaceA.ServiceA.ServiceProtocol { + func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { + let response = try await self.inputStreaming(request: request) return ServerResponse.Stream(single: response) } } @@ -137,7 +145,9 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorOutputStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for outputStreamingMethod", - name: "outputStreamingMethod", + name: "OutputStreamingMethod", + generatedName: "OutputStreaming", + signatureName: "outputStreaming", isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -145,39 +155,41 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", + name: "ServiceATest", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { + protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for outputStreamingMethod - func outputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func outputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. - public extension namespaceA.ServiceA.StreamingServiceProtocol { + public extension NamespaceA.ServiceA.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) { router.registerHandler( - for: namespaceA.ServiceA.Methods.outputStreamingMethod.descriptor, - deserializer: ProtobufDeserializer(), - serializer: ProtobufSerializer(), + for: NamespaceA.ServiceA.Methods.OutputStreaming.descriptor, + deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), handler: { request in - try await self.outputStreamingMethod(request: request) + try await self.outputStreaming(request: request) } ) } } /// Documentation for ServiceA - protocol namespaceA_ServiceAServiceProtocol: namespaceA.ServiceA.StreamingServiceProtocol { + protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol { /// Documentation for outputStreamingMethod - func outputStreamingMethod(request: ServerRequest.Single) async throws -> ServerResponse.Stream + func outputStreaming(request: ServerRequest.Single) async throws -> ServerResponse.Stream } - /// Partial conformance to `namespaceA_ServiceAStreamingServiceProtocol`. - public extension namespaceA.ServiceA.ServiceProtocol { - func outputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { - let response = try await self.outputStreamingMethod(request: ServerRequest.Single(stream: request)) + /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. + public extension NamespaceA.ServiceA.ServiceProtocol { + func outputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { + let response = try await self.outputStreaming(request: ServerRequest.Single(stream: request)) return response } } @@ -192,7 +204,9 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorBidirectionalStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for bidirectionalStreamingMethod", - name: "bidirectionalStreamingMethod", + name: "BidirectionalStreamingMethod", + generatedName: "BidirectionalStreaming", + signatureName: "bidirectionalStreaming", isInputStreaming: true, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -200,37 +214,39 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", + name: "ServiceATest", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { + protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for bidirectionalStreamingMethod - func bidirectionalStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func bidirectionalStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. - public extension namespaceA.ServiceA.StreamingServiceProtocol { + public extension NamespaceA.ServiceA.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) { router.registerHandler( - for: namespaceA.ServiceA.Methods.bidirectionalStreamingMethod.descriptor, - deserializer: ProtobufDeserializer(), - serializer: ProtobufSerializer(), + for: NamespaceA.ServiceA.Methods.BidirectionalStreaming.descriptor, + deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), handler: { request in - try await self.bidirectionalStreamingMethod(request: request) + try await self.bidirectionalStreaming(request: request) } ) } } /// Documentation for ServiceA - protocol namespaceA_ServiceAServiceProtocol: namespaceA.ServiceA.StreamingServiceProtocol { + protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol { /// Documentation for bidirectionalStreamingMethod - func bidirectionalStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func bidirectionalStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } - /// Partial conformance to `namespaceA_ServiceAStreamingServiceProtocol`. - public extension namespaceA.ServiceA.ServiceProtocol { + /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. + public extension NamespaceA.ServiceA.ServiceProtocol { } """ @@ -243,7 +259,9 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorMultipleMethods() throws { let inputStreamingMethod = MethodDescriptor( documentation: "Documentation for inputStreamingMethod", - name: "inputStreamingMethod", + name: "InputStreamingMethod", + generatedName: "InputStreaming", + signatureName: "inputStreaming", isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -252,6 +270,8 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let outputStreamingMethod = MethodDescriptor( documentation: "Documentation for outputStreamingMethod", name: "outputStreamingMethod", + generatedName: "OutputStreaming", + signatureName: "outputStreaming", isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -259,55 +279,57 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", + name: "ServiceATest", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [inputStreamingMethod, outputStreamingMethod] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { + protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for inputStreamingMethod - func inputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream /// Documentation for outputStreamingMethod - func outputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func outputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. - public extension namespaceA.ServiceA.StreamingServiceProtocol { + public extension NamespaceA.ServiceA.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) { router.registerHandler( - for: namespaceA.ServiceA.Methods.inputStreamingMethod.descriptor, - deserializer: ProtobufDeserializer(), - serializer: ProtobufSerializer(), + for: NamespaceA.ServiceA.Methods.InputStreaming.descriptor, + deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), handler: { request in - try await self.inputStreamingMethod(request: request) + try await self.inputStreaming(request: request) } ) router.registerHandler( - for: namespaceA.ServiceA.Methods.outputStreamingMethod.descriptor, - deserializer: ProtobufDeserializer(), - serializer: ProtobufSerializer(), + for: NamespaceA.ServiceA.Methods.OutputStreaming.descriptor, + deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), handler: { request in - try await self.outputStreamingMethod(request: request) + try await self.outputStreaming(request: request) } ) } } /// Documentation for ServiceA - protocol namespaceA_ServiceAServiceProtocol: namespaceA.ServiceA.StreamingServiceProtocol { + protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol { /// Documentation for inputStreamingMethod - func inputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Single + func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Single /// Documentation for outputStreamingMethod - func outputStreamingMethod(request: ServerRequest.Single) async throws -> ServerResponse.Stream + func outputStreaming(request: ServerRequest.Single) async throws -> ServerResponse.Stream } - /// Partial conformance to `namespaceA_ServiceAStreamingServiceProtocol`. - public extension namespaceA.ServiceA.ServiceProtocol { - func inputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { - let response = try await self.inputStreamingMethod(request: request) + /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. + public extension NamespaceA.ServiceA.ServiceProtocol { + func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { + let response = try await self.inputStreaming(request: request) return ServerResponse.Stream(single: response) } - func outputStreamingMethod(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { - let response = try await self.outputStreamingMethod(request: ServerRequest.Single(stream: request)) + func outputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { + let response = try await self.outputStreaming(request: ServerRequest.Single(stream: request)) return response } } @@ -323,6 +345,8 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "methodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -330,8 +354,10 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", + name: "ServiceATest", + generatedName: "ServiceA", namespace: "", + generatedNamespace: "", methods: [method] ) let expectedSwift = @@ -339,15 +365,15 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { /// Documentation for ServiceA protocol ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for MethodA - func methodA(request: ServerRequest.Stream) async throws -> ServerResponse.Stream + func methodA(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. public extension ServiceA.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) { router.registerHandler( - for: ServiceA.Methods.methodA.descriptor, - deserializer: ProtobufDeserializer(), - serializer: ProtobufSerializer(), + for: ServiceA.Methods.MethodA.descriptor, + deserializer: ProtobufDeserializer(), + serializer: ProtobufSerializer(), handler: { request in try await self.methodA(request: request) } @@ -357,11 +383,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { /// Documentation for ServiceA protocol ServiceAServiceProtocol: ServiceA.StreamingServiceProtocol { /// Documentation for MethodA - func methodA(request: ServerRequest.Single) async throws -> ServerResponse.Single + func methodA(request: ServerRequest.Single) async throws -> ServerResponse.Single } /// Partial conformance to `ServiceAStreamingServiceProtocol`. public extension ServiceA.ServiceProtocol { - func methodA(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { + func methodA(request: ServerRequest.Stream) async throws -> ServerResponse.Stream { let response = try await self.methodA(request: ServerRequest.Single(stream: request)) return ServerResponse.Stream(single: response) } @@ -378,38 +404,42 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let serviceA = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for ServiceB", name: "ServiceB", + generatedName: "ServiceB", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let expectedSwift = """ /// Documentation for ServiceA - protocol namespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} + protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} /// Conformance to `GRPCCore.RegistrableRPCService`. - public extension namespaceA.ServiceA.StreamingServiceProtocol { + public extension NamespaceA.ServiceA.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) {} } /// Documentation for ServiceA - protocol namespaceA_ServiceAServiceProtocol: namespaceA.ServiceA.StreamingServiceProtocol {} - /// Partial conformance to `namespaceA_ServiceAStreamingServiceProtocol`. - public extension namespaceA.ServiceA.ServiceProtocol { + protocol NamespaceA_ServiceAServiceProtocol: NamespaceA.ServiceA.StreamingServiceProtocol {} + /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. + public extension NamespaceA.ServiceA.ServiceProtocol { } /// Documentation for ServiceB - protocol namespaceA_ServiceBStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} + protocol NamespaceA_ServiceBStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} /// Conformance to `GRPCCore.RegistrableRPCService`. - public extension namespaceA.ServiceB.StreamingServiceProtocol { + public extension NamespaceA.ServiceB.StreamingServiceProtocol { func registerRPCs(with router: inout GRPCCore.RPCRouter) {} } /// Documentation for ServiceB - protocol namespaceA_ServiceBServiceProtocol: namespaceA.ServiceB.StreamingServiceProtocol {} - /// Partial conformance to `namespaceA_ServiceBStreamingServiceProtocol`. - public extension namespaceA.ServiceB.ServiceProtocol { + protocol NamespaceA_ServiceBServiceProtocol: NamespaceA.ServiceB.StreamingServiceProtocol {} + /// Partial conformance to `NamespaceA_ServiceBStreamingServiceProtocol`. + public extension NamespaceA.ServiceB.ServiceProtocol { } """ diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift index 7bca92243..bb1759acd 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift @@ -28,6 +28,8 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -36,12 +38,14 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [method] ) let expectedSwift = """ - enum namespaceA { + enum NamespaceA { enum ServiceA { enum Methods { enum MethodA { @@ -56,10 +60,10 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { static let methods: [MethodDescriptor] = [ Methods.MethodA.descriptor ] - typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol - typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol - typealias ClientProtocol = namespaceA_ServiceAClientProtocol - typealias Client = namespaceA_ServiceAClient + typealias StreamingServiceProtocol = NamespaceA_ServiceAServiceStreamingProtocol + typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol + typealias ClientProtocol = NamespaceA_ServiceAClientProtocol + typealias Client = NamespaceA_ServiceAClient } } """ @@ -76,19 +80,21 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let expectedSwift = """ - enum namespaceA { + enum NamespaceA { enum ServiceA { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol - typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol - typealias ClientProtocol = namespaceA_ServiceAClientProtocol - typealias Client = namespaceA_ServiceAClient + typealias StreamingServiceProtocol = NamespaceA_ServiceAServiceStreamingProtocol + typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol + typealias ClientProtocol = NamespaceA_ServiceAClientProtocol + typealias Client = NamespaceA_ServiceAClient } } """ @@ -105,17 +111,19 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let expectedSwift = """ - enum namespaceA { + enum NamespaceA { enum ServiceA { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol - typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol + typealias StreamingServiceProtocol = NamespaceA_ServiceAServiceStreamingProtocol + typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol } } """ @@ -132,17 +140,19 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let expectedSwift = """ - enum namespaceA { + enum NamespaceA { enum ServiceA { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias ClientProtocol = namespaceA_ServiceAClientProtocol - typealias Client = namespaceA_ServiceAClient + typealias ClientProtocol = NamespaceA_ServiceAClientProtocol + typealias Client = NamespaceA_ServiceAClient } } """ @@ -159,12 +169,14 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let expectedSwift = """ - enum namespaceA { + enum NamespaceA { enum ServiceA { enum Methods {} static let methods: [MethodDescriptor] = [] @@ -184,6 +196,8 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let method = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: false, inputType: "ServiceARequest", @@ -192,7 +206,9 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "", + generatedNamespace: "", methods: [method] ) let expectedSwift = @@ -230,6 +246,8 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", name: "MethodA", + generatedName: "MethodA", + signatureName: "methodA", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -238,6 +256,8 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let methodB = MethodDescriptor( documentation: "Documentation for MethodB", name: "MethodB", + generatedName: "MethodB", + signatureName: "methodB", isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -246,12 +266,14 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [methodA, methodB] ) let expectedSwift = """ - enum namespaceA { + enum NamespaceA { enum ServiceA { enum Methods { enum MethodA { @@ -275,10 +297,10 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { Methods.MethodA.descriptor, Methods.MethodB.descriptor ] - typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol - typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol - typealias ClientProtocol = namespaceA_ServiceAClientProtocol - typealias Client = namespaceA_ServiceAClient + typealias StreamingServiceProtocol = NamespaceA_ServiceAServiceStreamingProtocol + typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol + typealias ClientProtocol = NamespaceA_ServiceAClientProtocol + typealias Client = NamespaceA_ServiceAClient } } """ @@ -295,19 +317,21 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", name: "ServiceA", + generatedName: "ServiceA", namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let expectedSwift = """ - enum namespaceA { + enum NamespaceA { enum ServiceA { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = namespaceA_ServiceAServiceStreamingProtocol - typealias ServiceProtocol = namespaceA_ServiceAServiceProtocol - typealias ClientProtocol = namespaceA_ServiceAClientProtocol - typealias Client = namespaceA_ServiceAClient + typealias StreamingServiceProtocol = NamespaceA_ServiceAServiceStreamingProtocol + typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol + typealias ClientProtocol = NamespaceA_ServiceAClientProtocol + typealias Client = NamespaceA_ServiceAClient } } """ @@ -324,35 +348,39 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", - namespace: "namespacea", + generatedName: "Bservice", + namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", - namespace: "namespacea", + generatedName: "Aservice", + namespace: "namespaceA", + generatedNamespace: "NamespaceA", methods: [] ) let expectedSwift = """ - enum namespacea { - enum AService { + enum NamespaceA { + enum Aservice { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = namespacea_AServiceServiceStreamingProtocol - typealias ServiceProtocol = namespacea_AServiceServiceProtocol - typealias ClientProtocol = namespacea_AServiceClientProtocol - typealias Client = namespacea_AServiceClient + typealias StreamingServiceProtocol = NamespaceA_AserviceServiceStreamingProtocol + typealias ServiceProtocol = NamespaceA_AserviceServiceProtocol + typealias ClientProtocol = NamespaceA_AserviceClientProtocol + typealias Client = NamespaceA_AserviceClient } - enum BService { + enum Bservice { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = namespacea_BServiceServiceStreamingProtocol - typealias ServiceProtocol = namespacea_BServiceServiceProtocol - typealias ClientProtocol = namespacea_BServiceClientProtocol - typealias Client = namespacea_BServiceClient + typealias StreamingServiceProtocol = NamespaceA_BserviceServiceStreamingProtocol + typealias ServiceProtocol = NamespaceA_BserviceServiceProtocol + typealias ClientProtocol = NamespaceA_BserviceClientProtocol + typealias Client = NamespaceA_BserviceClient } } """ @@ -369,14 +397,18 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", + generatedName: "BService", namespace: "", + generatedNamespace: "", methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", + generatedName: "AService", namespace: "", + generatedNamespace: "", methods: [] ) @@ -412,37 +444,41 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", + generatedName: "BService", namespace: "bnamespace", + generatedNamespace: "Bnamespace", methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", + generatedName: "AService", namespace: "anamespace", + generatedNamespace: "Anamespace", methods: [] ) let expectedSwift = """ - enum anamespace { + enum Anamespace { enum AService { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol - typealias ServiceProtocol = anamespace_AServiceServiceProtocol - typealias ClientProtocol = anamespace_AServiceClientProtocol - typealias Client = anamespace_AServiceClient + typealias StreamingServiceProtocol = Anamespace_AServiceServiceStreamingProtocol + typealias ServiceProtocol = Anamespace_AServiceServiceProtocol + typealias ClientProtocol = Anamespace_AServiceClientProtocol + typealias Client = Anamespace_AServiceClient } } - enum bnamespace { + enum Bnamespace { enum BService { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = bnamespace_BServiceServiceStreamingProtocol - typealias ServiceProtocol = bnamespace_BServiceServiceProtocol - typealias ClientProtocol = bnamespace_BServiceClientProtocol - typealias Client = bnamespace_BServiceClient + typealias StreamingServiceProtocol = Bnamespace_BServiceServiceStreamingProtocol + typealias ServiceProtocol = Bnamespace_BServiceServiceProtocol + typealias ClientProtocol = Bnamespace_BServiceClientProtocol + typealias Client = Bnamespace_BServiceClient } } """ @@ -459,13 +495,17 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", name: "AService", + generatedName: "AService", namespace: "anamespace", + generatedNamespace: "Anamespace", methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", name: "BService", + generatedName: "BService", namespace: "", + generatedNamespace: "", methods: [] ) let expectedSwift = @@ -478,14 +518,14 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { typealias ClientProtocol = BServiceClientProtocol typealias Client = BServiceClient } - enum anamespace { + enum Anamespace { enum AService { enum Methods {} static let methods: [MethodDescriptor] = [] - typealias StreamingServiceProtocol = anamespace_AServiceServiceStreamingProtocol - typealias ServiceProtocol = anamespace_AServiceServiceProtocol - typealias ClientProtocol = anamespace_AServiceClientProtocol - typealias Client = anamespace_AServiceClient + typealias StreamingServiceProtocol = Anamespace_AServiceServiceStreamingProtocol + typealias ServiceProtocol = Anamespace_AServiceServiceProtocol + typealias ClientProtocol = Anamespace_AServiceClientProtocol + typealias Client = Anamespace_AServiceClient } } """ From 44d506579c972050728caa60b259aaed105432e3 Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Tue, 16 Jan 2024 09:55:52 +0000 Subject: [PATCH 2/4] fixed test --- .../Translator/IDLToStructuredSwiftTranslator.swift | 2 +- ...IDLToStructuredSwiftTranslatorSnippetBasedTests.swift | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift index 451b0086e..a5716d156 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift @@ -273,7 +273,7 @@ extension IDLToStructuredSwiftTranslator { code: .invalidGeneratedNamespace, message: """ All services within a namespace must have the same generated namespace. \ - \(service.name) has not the same generated namespace as other services \ + \(service.name) doesn't have the same generated namespace as other services \ within the \(service.namespace) namespace. """ ) diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift index 371d65f1f..c1613f6f2 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift @@ -569,7 +569,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { generatedNamespace: "NamespaceA", methods: [] ) - var serviceB = ServiceDescriptor( + let serviceB = ServiceDescriptor( documentation: "Documentation for SameName service with no namespace", name: "ServiceB", generatedName: "ServiceB", @@ -577,7 +577,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { generatedNamespace: "NamespaceB", methods: [] ) - var codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) + let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) let translator = IDLToStructuredSwiftTranslator() XCTAssertThrowsError( ofType: CodeGenError.self, @@ -593,8 +593,9 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .invalidGeneratedNamespace, message: """ - Services within a namespace must have the same generated namespace. \ - Not all services within namespaceA have the NamespaceA generated namespace. + All services within a namespace must have the same generated namespace. \ + ServiceB doesn't have the same generated namespace as other services \ + within the namespaceA namespace. """ ) ) From ba90e9b7226dcabdf0e44f949f4a00d1642f5a9d Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Tue, 16 Jan 2024 17:31:11 +0000 Subject: [PATCH 3/4] created Name struct that encapsulates the three possible names --- Sources/GRPCCodeGen/CodeGenError.swift | 6 - .../GRPCCodeGen/CodeGenerationRequest.swift | 73 +++-- .../Translator/ClientCodeTranslator.swift | 13 +- .../IDLToStructuredSwiftTranslator.swift | 210 ++++++-------- .../Translator/ServerCodeTranslator.swift | 19 +- .../Translator/TypealiasTranslator.swift | 15 +- ...lientCodeTranslatorSnippetBasedTests.swift | 81 ++---- ...uredSwiftTranslatorSnippetBasedTests.swift | 265 +++++------------- ...erverCodeTranslatorSnippetBasedTests.swift | 145 ++++++---- ...TypealiasTranslatorSnippetBasedTests.swift | 161 ++++++----- 10 files changed, 439 insertions(+), 549 deletions(-) diff --git a/Sources/GRPCCodeGen/CodeGenError.swift b/Sources/GRPCCodeGen/CodeGenError.swift index d81287229..6cfffa32c 100644 --- a/Sources/GRPCCodeGen/CodeGenError.swift +++ b/Sources/GRPCCodeGen/CodeGenError.swift @@ -39,7 +39,6 @@ extension CodeGenError { case nonUniqueServiceName case nonUniqueMethodName case invalidKind - case invalidGeneratedNamespace } private var value: Value @@ -61,11 +60,6 @@ extension CodeGenError { public static var invalidKind: Self { Self(.invalidKind) } - - /// An invalid generated namespace is used for an import. - public static var invalidGeneratedNamespace: Self { - Self(.invalidGeneratedNamespace) - } } } diff --git a/Sources/GRPCCodeGen/CodeGenerationRequest.swift b/Sources/GRPCCodeGen/CodeGenerationRequest.swift index 3b7c49236..8b24778bf 100644 --- a/Sources/GRPCCodeGen/CodeGenerationRequest.swift +++ b/Sources/GRPCCodeGen/CodeGenerationRequest.swift @@ -220,19 +220,13 @@ public struct CodeGenerationRequest { /// Documentation from comments above the IDL service description. public var documentation: String - /// Service name. - public var name: String + /// Service name in different formats. + public var name: Name - /// The service name used in the generated type names. - public var generatedName: String - - /// The service namespace. + /// The service namespace in different formats. /// /// For `.proto` files it is the package name. - public var namespace: String - - /// The namespace identifier used in the generated type names. - public var generatedNamespace: String + public var namespace: Name /// A description of each method of a service. /// @@ -241,17 +235,13 @@ public struct CodeGenerationRequest { public init( documentation: String, - name: String, - generatedName: String, - namespace: String, - generatedNamespace: String, + name: Name, + namespace: Name, methods: [MethodDescriptor] ) { self.documentation = documentation self.name = name - self.generatedName = generatedName self.namespace = namespace - self.generatedNamespace = generatedNamespace self.methods = methods } @@ -260,14 +250,8 @@ public struct CodeGenerationRequest { /// Documentation from comments above the IDL method description. public var documentation: String - /// Method name. - public var name: String - - /// The name used in the generated type names. - public var generatedName: String - - /// The function name used in the generated code in declarations of the method. - public var signatureName: String + /// Method name in different formats. + public var name: Name /// Identifies if the method is input streaming. public var isInputStreaming: Bool @@ -283,9 +267,7 @@ public struct CodeGenerationRequest { public init( documentation: String, - name: String, - generatedName: String, - signatureName: String, + name: Name, isInputStreaming: Bool, isOutputStreaming: Bool, inputType: String, @@ -293,8 +275,6 @@ public struct CodeGenerationRequest { ) { self.documentation = documentation self.name = name - self.generatedName = generatedName - self.signatureName = signatureName self.isInputStreaming = isInputStreaming self.isOutputStreaming = isOutputStreaming self.inputType = inputType @@ -302,4 +282,39 @@ public struct CodeGenerationRequest { } } } + + /// Represents the name associated with a namespace, service or a method, in three different formats, + /// which are used in specific parts of the generated code. There must be only one ``Name`` object + /// for each namespace, service or method. + /// + /// The base name, the generatedUpperCase (and the generatedLowerCase) must be unique for methods + /// from within the same service and for services within the same namespace. + public struct Name: Hashable { + /// The base name is the name used for the namespace/service/method in the IDL file, so it should follow + /// the specific casing of the IDL. + /// + /// The base name is also used in the descriptors that identify a specific method or service : + /// `..`. + public let base: String + + /// The `generatedUpperCase` name is used as part of generated type names, which begin with a capital letter + /// and are (partially) using CamelCase. It is using UpperCamelCase in order to follow the Swift naming conventions. + /// + /// It is used in the generated code as an enum name and as part of protocol names. + /// For example, in the generated server code the name of the service protocol + /// follows this pattern: + /// `_ServiceProtocol`. + public let generatedUpperCase: String + + /// The `generatedLowerCase` name is used as the function name in the method declarations or definitions. + /// It is using lowerCamelCase in order to follow the Swift naming conventions. + /// + /// It is used only for the methods, so in the case of a namespace or service, it can be an empty String. + /// For example, for a method with the base name "FooBar", the generatedLowerCase is "fooBar" and it is + /// used in: + /// ```swift + /// public func fooBar(){} + /// ``` + public let generatedLowerCase: String + } } diff --git a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift index 854431e9b..0f08fee69 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift @@ -151,7 +151,7 @@ extension ClientCodeTranslator { ) let functionSignature = FunctionSignatureDescription( kind: .function( - name: method.signatureName, + name: method.name.generatedLowerCase, isStatic: false ), generics: [.member("R")], @@ -180,7 +180,10 @@ extension ClientCodeTranslator { ) -> [CodeBlock] { let functionCall = Expression.functionCall( calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self"), right: method.signatureName) + MemberAccessDescription( + left: .identifierPattern("self"), + right: method.name.generatedLowerCase + ) ), arguments: [ FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")), @@ -380,7 +383,7 @@ extension ClientCodeTranslator { .init( label: "descriptor", expression: .identifierPattern( - "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName).descriptor" + "\(service.namespacedTypealiasGeneratedName).Methods.\(method.name.generatedUpperCase).descriptor" ) ), .init(label: "serializer", expression: .identifierPattern("serializer")), @@ -395,7 +398,7 @@ extension ClientCodeTranslator { return .function( kind: .function( - name: "\(method.signatureName)", + name: "\(method.name.generatedLowerCase)", isStatic: false ), generics: [.member("R")], @@ -419,7 +422,7 @@ extension ClientCodeTranslator { type: InputOutputType ) -> String { var components: String = - "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName)" + "\(service.namespacedTypealiasGeneratedName).Methods.\(method.name.generatedUpperCase)" switch type { case .input: diff --git a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift index a5716d156..8c58540f2 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift @@ -90,19 +90,14 @@ extension IDLToStructuredSwiftTranslator { } private func validateInput(_ codeGenerationRequest: CodeGenerationRequest) throws { - let servicesByGeneratedNamespace = Dictionary( - grouping: codeGenerationRequest.services, - by: { $0.generatedNamespace } - ) - let servicesByNamespace = Dictionary( + try self.checkServiceDescriptorsAreUnique(codeGenerationRequest.services) + + let servicesByUpperCaseNamespace = Dictionary( grouping: codeGenerationRequest.services, - by: { $0.namespace } + by: { $0.namespace.generatedUpperCase } ) - try self.checkServiceNamesAreUnique(for: servicesByGeneratedNamespace) - try self.checkServicesNamespacesAndGeneratedNamespacesCoincide( - servicesByNamespace: servicesByNamespace - ) - try checkEmptyNamespaceAndGeneratedNamespace(for: codeGenerationRequest.services) + try self.checkServiceNamesAreUnique(for: servicesByUpperCaseNamespace) + for service in codeGenerationRequest.services { try self.checkMethodNamesAreUnique(in: service) } @@ -111,61 +106,42 @@ extension IDLToStructuredSwiftTranslator { // Verify service names are unique within each namespace and that services with no namespace // don't have the same names as any of the namespaces. private func checkServiceNamesAreUnique( - for servicesByNamespace: [String: [CodeGenerationRequest.ServiceDescriptor]] + for servicesByUpperCaseNamespace: [String: [CodeGenerationRequest.ServiceDescriptor]] ) throws { - // Check that if there are services in an empty namespace, none have names which match other namespaces - if let noNamespaceServices = servicesByNamespace[""] { - let namespaces = servicesByNamespace.keys + // Check that if there are services in an empty namespace, none have names which match other namespaces, + // to ensure that there are no enums with the same name in the type aliases part of the generated code. + if let noNamespaceServices = servicesByUpperCaseNamespace[""] { + let upperCaseNamespaces = servicesByUpperCaseNamespace.keys for service in noNamespaceServices { - if namespaces.contains(service.generatedName) { + if upperCaseNamespaces.contains(service.name.generatedUpperCase) { throw CodeGenError( code: .nonUniqueServiceName, message: """ - Services with no namespace must not have the same generated names as the namespaces. \ - \(service.generatedName) is used as a generated name for a service with no namespace and a namespace. + Services with no namespace must not have the same generated upper case names as the namespaces. \ + \(service.name.generatedUpperCase) is used as a generated upper case name for a service with no namespace and a namespace. """ ) } } } - // Check that service names and service generated names are unique within each namespace. - for (namespace, services) in servicesByNamespace { - var serviceNames: Set = [] - var generatedNames: Set = [] + // Check that the generated upper case names for services are unique within each namespace, to ensure that + // the service enums from each namespace enum have unique names. + for (namespace, services) in servicesByUpperCaseNamespace { + var upperCaseNames: Set = [] for service in services { - if serviceNames.contains(service.name) { - let errorMessage: String - if namespace.isEmpty { - errorMessage = """ - Services in an empty namespace must have unique names. \ - \(service.name) is used as a name for multiple services without namespaces. - """ - } else { - errorMessage = """ - Services within the same namespace must have unique names. \ - \(service.name) is used as a name for multiple services in the \(service.namespace) namespace. - """ - } - throw CodeGenError( - code: .nonUniqueServiceName, - message: errorMessage - ) - } - serviceNames.insert(service.name) - - if generatedNames.contains(service.generatedName) { + if upperCaseNames.contains(service.name.generatedUpperCase) { let errorMessage: String if namespace.isEmpty { errorMessage = """ - Services in an empty namespace must have unique generated names. \ - \(service.generatedName) is used as a name for multiple services without namespaces. + Services in an empty namespace must have unique generated upper case names. \ + \(service.name.generatedUpperCase) is used as a generated upper case name for multiple services without namespaces. """ } else { errorMessage = """ - Services within the same namespace must have unique generated names. \ - \(service.generatedName) is used as a generated name for multiple services in the \(service.namespace) namespace. + Services within the same namespace must have unique generated upper case names. \ + \(service.name.generatedUpperCase) is used as a generated upper case name for multiple services in the \(service.namespace.base) namespace. """ } throw CodeGenError( @@ -173,138 +149,122 @@ extension IDLToStructuredSwiftTranslator { message: errorMessage ) } - generatedNames.insert(service.generatedName) + upperCaseNames.insert(service.name.generatedUpperCase) } } } - // Verify method names are unique for the service. + // Verify method names are unique within a service. private func checkMethodNamesAreUnique( in service: CodeGenerationRequest.ServiceDescriptor ) throws { - // Check the method names. - let methodNames = service.methods.map { $0.name } - var seenNames = Set() + // Check that the method descriptors are unique, by checking that the base names + // of the methods of a specific service are unique. + let baseNames = service.methods.map { $0.name.base } + var seenBaseNames = Set() - for methodName in methodNames { - if seenNames.contains(methodName) { + for baseName in baseNames { + if seenBaseNames.contains(baseName) { throw CodeGenError( code: .nonUniqueMethodName, message: """ - Methods of a service must have unique names. \ - \(methodName) is used as a name for multiple methods of the \(service.name) service. + Methods of a service must have unique base names. \ + \(baseName) is used as a base name for multiple methods of the \(service.name.base) service. """ ) } - seenNames.insert(methodName) + seenBaseNames.insert(baseName) } - // Check the method generated names. - let generatedNames = service.methods.map { $0.generatedName } - var seenGeneratedNames = Set() + // Check that generated upper case names for methods are unique within a service, to ensure that + // the enums containing type aliases for each method of a service. + let upperCaseNames = service.methods.map { $0.name.generatedUpperCase } + var seenUpperCaseNames = Set() - for generatedName in generatedNames { - if seenGeneratedNames.contains(generatedName) { + for upperCaseName in upperCaseNames { + if seenUpperCaseNames.contains(upperCaseName) { throw CodeGenError( code: .nonUniqueMethodName, message: """ - Methods of a service must have unique generated names. \ - \(generatedName) is used as a generated name for multiple methods of the \(service.name) service. + Methods of a service must have unique generated upper case names. \ + \(upperCaseName) is used as a generated upper case name for multiple methods of the \(service.name.base) service. """ ) } - seenGeneratedNames.insert(generatedName) + seenUpperCaseNames.insert(upperCaseName) } - // Check the function signature names. - let signatureNames = service.methods.map { $0.signatureName } - var seenSignatureNames = Set() + // Check that generated lower case names for methods are unique within a service, to ensure that + // the function declarations and definitions from the same protocols and extensions have unique names. + let lowerCaseNames = service.methods.map { $0.name.generatedLowerCase } + var seenLowerCaseNames = Set() - for signatureName in signatureNames { - if seenSignatureNames.contains(signatureName) { + for lowerCaseName in lowerCaseNames { + if seenLowerCaseNames.contains(lowerCaseName) { throw CodeGenError( code: .nonUniqueMethodName, message: """ - Methods of a service must have unique signature names. \ - \(signatureName) is used as a signature name for multiple methods of the \(service.name) service. + Methods of a service must have unique lower case names. \ + \(lowerCaseName) is used as a signature name for multiple methods of the \(service.name.base) service. """ ) } - seenSignatureNames.insert(signatureName) + seenLowerCaseNames.insert(lowerCaseName) } } - private func checkEmptyNamespaceAndGeneratedNamespace( - for services: [CodeGenerationRequest.ServiceDescriptor] + private func checkServiceDescriptorsAreUnique( + _ services: [CodeGenerationRequest.ServiceDescriptor] ) throws { - for service in services { - if service.namespace.isEmpty { - if !service.generatedNamespace.isEmpty { - throw CodeGenError( - code: .invalidGeneratedNamespace, - message: """ - Services with an empty namespace must have an empty generated namespace. \ - \(service.name) has an empty namespace, but a non-empty generated namespace. - """ - ) - } - } else { - if service.generatedNamespace.isEmpty { - throw CodeGenError( - code: .invalidGeneratedNamespace, - message: """ - Services with a non-empty namespace must have a non-empty generated namespace. \ - \(service.name) has a non-empty namespace, but an empty generated namespace. - """ - ) - } - } + let descriptors = services.map { + ($0.namespace.base.isEmpty ? "" : "\($0.namespace.base).") + $0.name.base } - } - - private func checkServicesNamespacesAndGeneratedNamespacesCoincide( - servicesByNamespace: [String: [CodeGenerationRequest.ServiceDescriptor]] - ) throws { - for (_, services) in servicesByNamespace { - let generatedNamespace = services[0].generatedNamespace - for service in services { - if service.generatedNamespace != generatedNamespace { - throw CodeGenError( - code: .invalidGeneratedNamespace, - message: """ - All services within a namespace must have the same generated namespace. \ - \(service.name) doesn't have the same generated namespace as other services \ - within the \(service.namespace) namespace. - """ - ) - } - } + if let duplicate = descriptors.hasDuplicates() { + throw CodeGenError( + code: .nonUniqueServiceName, + message: """ + Services must have unique descriptors. \ + \(duplicate) is the descriptor of at least two different services. + """ + ) } } } extension CodeGenerationRequest.ServiceDescriptor { var namespacedTypealiasGeneratedName: String { - if self.generatedNamespace.isEmpty { - return self.generatedName + if self.namespace.generatedUpperCase.isEmpty { + return self.name.generatedUpperCase } else { - return "\(self.generatedNamespace).\(self.generatedName)" + return "\(self.namespace.generatedUpperCase).\(self.name.generatedUpperCase)" } } var namespacedGeneratedName: String { - if self.generatedNamespace.isEmpty { - return self.generatedName + if self.namespace.generatedUpperCase.isEmpty { + return self.name.generatedUpperCase } else { - return "\(self.generatedNamespace)_\(self.generatedName)" + return "\(self.namespace.generatedUpperCase)_\(self.name.generatedUpperCase)" } } var fullyQualifiedName: String { - if self.namespace.isEmpty { - return self.name + if self.namespace.base.isEmpty { + return self.name.base } else { - return "\(self.namespace).\(self.name)" + return "\(self.namespace.base).\(self.name.base)" + } + } +} + +extension [String] { + func hasDuplicates() -> String? { + var elements = Set() + for element in self { + if elements.insert(element).inserted == false { + return element + } } + return nil } } diff --git a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift index df6263b63..125705b23 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift @@ -126,7 +126,7 @@ extension ServerCodeTranslator { in service: CodeGenerationRequest.ServiceDescriptor ) -> FunctionSignatureDescription { return FunctionSignatureDescription( - kind: .function(name: method.signatureName), + kind: .function(name: method.name.generatedLowerCase), parameters: [ .init( label: "request", @@ -244,7 +244,10 @@ extension ServerCodeTranslator { let getFunctionCall = Expression.functionCall( calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self"), right: method.signatureName) + MemberAccessDescription( + left: .identifierPattern("self"), + right: method.name.generatedLowerCase + ) ), arguments: [ FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")) @@ -304,7 +307,7 @@ extension ServerCodeTranslator { ) let functionSignature = FunctionSignatureDescription( - kind: .function(name: method.signatureName), + kind: .function(name: method.name.generatedLowerCase), parameters: [ .init( label: "request", @@ -382,7 +385,10 @@ extension ServerCodeTranslator { // Call to the corresponding ServiceProtocol method. let serviceProtocolMethod = Expression.functionCall( calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self"), right: method.signatureName) + MemberAccessDescription( + left: .identifierPattern("self"), + right: method.name.generatedLowerCase + ) ), arguments: [FunctionArgumentDescription(label: "request", expression: serverRequest)] ) @@ -431,7 +437,7 @@ extension ServerCodeTranslator { type: InputOutputType ) -> String { var components: String = - "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName)" + "\(service.namespacedTypealiasGeneratedName).Methods.\(method.name.generatedUpperCase)" switch type { case .input: @@ -448,7 +454,8 @@ extension ServerCodeTranslator { for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, service: CodeGenerationRequest.ServiceDescriptor ) -> String { - return "\(service.namespacedTypealiasGeneratedName).Methods.\(method.generatedName).descriptor" + return + "\(service.namespacedTypealiasGeneratedName).Methods.\(method.name.generatedUpperCase).descriptor" } /// Generates the fully qualified name of the type alias for a service protocol. diff --git a/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift index 47bb4cf6a..74350e71a 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift @@ -64,14 +64,17 @@ struct TypealiasTranslator: SpecializedTranslator { func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] { var codeBlocks: [CodeBlock] = [] let services = codeGenerationRequest.services - let servicesByNamespace = Dictionary(grouping: services, by: { $0.generatedNamespace }) + let servicesByNamespace = Dictionary( + grouping: services, + by: { $0.namespace.generatedUpperCase } + ) // Sorting the keys and the services in each list of the dictionary is necessary // so that the generated enums are deterministically ordered. for (generatedNamespace, services) in servicesByNamespace.sorted(by: { $0.key < $1.key }) { let namespaceCodeBlocks = try self.makeNamespaceEnum( for: generatedNamespace, - containing: services.sorted(by: { $0.generatedName < $1.generatedName }) + containing: services.sorted(by: { $0.name.generatedUpperCase < $1.name.generatedUpperCase }) ) codeBlocks.append(contentsOf: namespaceCodeBlocks) } @@ -110,7 +113,7 @@ extension TypealiasTranslator { private func makeServiceEnum( from service: CodeGenerationRequest.ServiceDescriptor ) throws -> Declaration { - var serviceEnum = EnumDescription(name: service.generatedName) + var serviceEnum = EnumDescription(name: service.name.generatedUpperCase) var methodsEnum = EnumDescription(name: "Methods") let methods = service.methods @@ -148,7 +151,7 @@ extension TypealiasTranslator { from method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, in service: CodeGenerationRequest.ServiceDescriptor ) -> Declaration { - var methodEnum = EnumDescription(name: method.generatedName) + var methodEnum = EnumDescription(name: method.name.generatedUpperCase) let inputTypealias = Declaration.typealias( name: "Input", @@ -184,7 +187,7 @@ extension TypealiasTranslator { ), FunctionArgumentDescription( label: "method", - expression: .literal(method.name) + expression: .literal(method.name.base) ), ] ) @@ -202,7 +205,7 @@ extension TypealiasTranslator { for service: CodeGenerationRequest.ServiceDescriptor ) -> Declaration { var methodDescriptors = [Expression]() - let methodNames = service.methods.map { $0.generatedName } + let methodNames = service.methods.map { $0.name.generatedUpperCase } for methodName in methodNames { let methodDescriptorPath = Expression.memberAccess( diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift index 26d910583..af7a1ce3f 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift @@ -23,13 +23,12 @@ import XCTest final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor + typealias Name = GRPCCodeGen.CodeGenerationRequest.Name func testClientCodeTranslatorUnaryMethod() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -37,10 +36,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), + namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), methods: [method] ) let expectedSwift = @@ -101,9 +98,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorClientStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -111,10 +106,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), + namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), methods: [method] ) let expectedSwift = @@ -175,9 +168,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorServerStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "methodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -185,10 +176,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), + namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), methods: [method] ) let expectedSwift = @@ -249,9 +238,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorBidirectionalStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "methodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: true, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -259,10 +246,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), + namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), methods: [method] ) let expectedSwift = @@ -323,9 +308,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorMultipleMethods() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", - name: "methodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -333,9 +316,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { ) let methodB = MethodDescriptor( documentation: "Documentation for MethodB", - name: "methodB", - generatedName: "MethodB", - signatureName: "methodB", + name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodB"), isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -343,10 +324,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), + namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), methods: [methodA, methodB] ) let expectedSwift = @@ -440,9 +419,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorNoNamespaceService() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "methodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "ServiceARequest", @@ -450,10 +427,8 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "", - generatedNamespace: "", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [method] ) let expectedSwift = @@ -514,18 +489,18 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { func testClientCodeTranslatorMultipleServices() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), + namespace: Name( + base: "nammespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "" + ), methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for ServiceB", - name: "ServiceB", - generatedName: "ServiceB", - namespace: "", - generatedNamespace: "", + name: Name(base: "ServiceB", generatedUpperCase: "ServiceB", generatedLowerCase: ""), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) let expectedSwift = diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift index c1613f6f2..413cd817d 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift @@ -23,6 +23,7 @@ import XCTest final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor + typealias Name = GRPCCodeGen.CodeGenerationRequest.Name func testImports() throws { var dependencies = [CodeGenerationRequest.Dependency]() @@ -149,10 +150,8 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { func testSameNameServicesNoNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "", - generatedNamespace: "", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) @@ -172,30 +171,26 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueServiceName, message: """ - Services in an empty namespace must have unique names. \ - AService is used as a name for multiple services without namespaces. + Services must have unique descriptors. \ + AService is the descriptor of at least two different services. """ ) ) } } - func testSameGeneratedNameServicesNoNamespaceError() throws { + func testSameDescriptorsServicesNoNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "", - generatedNamespace: "", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", - name: "BService", - generatedName: "AService", - namespace: "", - generatedNamespace: "", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) @@ -215,20 +210,21 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueServiceName, message: """ - Services in an empty namespace must have unique generated names. \ - AService is used as a name for multiple services without namespaces. + Services must have unique descriptors. AService is the descriptor of at least two different services. """ ) ) } } - func testSameNameServicesSameNamespaceError() throws { + func testSameDescriptorsSameNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "namespacea", - generatedNamespace: "NamespaceA", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name( + base: "namespacea", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespacea" + ), methods: [] ) @@ -248,8 +244,8 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueServiceName, message: """ - Services within the same namespace must have unique names. \ - AService is used as a name for multiple services in the namespacea namespace. + Services must have unique descriptors. \ + namespacea.AService is the descriptor of at least two different services. """ ) ) @@ -259,18 +255,22 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { func testSameGeneratedNameServicesSameNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "namespacea", - generatedNamespace: "NamespaceA", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name( + base: "namespacea", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespacea" + ), methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", - name: "BService", - generatedName: "AService", - namespace: "namespacea", - generatedNamespace: "NamespaceA", + name: Name(base: "BService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name( + base: "namespacea", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespacea" + ), methods: [] ) @@ -290,20 +290,18 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueServiceName, message: """ - Services within the same namespace must have unique generated names. \ - AService is used as a generated name for multiple services in the namespacea namespace. + Services within the same namespace must have unique generated upper case names. \ + AService is used as a generated upper case name for multiple services in the namespacea namespace. """ ) ) } } - func testSameNameMethodsSameServiceError() throws { + func testSameBaseNameMethodsSameServiceError() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -311,10 +309,12 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "namespacea", - generatedNamespace: "NamespaceA", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name( + base: "namespacea", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespacea" + ), methods: [methodA, methodA] ) @@ -334,20 +334,18 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueMethodName, message: """ - Methods of a service must have unique names. \ - MethodA is used as a name for multiple methods of the AService service. + Methods of a service must have unique base names. \ + MethodA is used as a base name for multiple methods of the AService service. """ ) ) } } - func testSameGeneratedNameMethodsSameServiceError() throws { + func testSameGeneratedUpperCaseNameMethodsSameServiceError() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -355,9 +353,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { ) let methodB = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodB", - generatedName: "MethodA", - signatureName: "methodB", + name: Name(base: "MethodB", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -365,10 +361,12 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "namespacea", - generatedNamespace: "NamespaceA", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name( + base: "namespacea", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespacea" + ), methods: [methodA, methodB] ) @@ -388,20 +386,18 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueMethodName, message: """ - Methods of a service must have unique generated names. \ - MethodA is used as a generated name for multiple methods of the AService service. + Methods of a service must have unique generated upper case names. \ + MethodA is used as a generated upper case name for multiple methods of the AService service. """ ) ) } } - func testSameSignatureNameMethodsSameServiceError() throws { + func testSameLowerCaseNameMethodsSameServiceError() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -409,9 +405,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { ) let methodB = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodB", - generatedName: "MethodB", - signatureName: "methodA", + name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -419,10 +413,12 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "namespacea", - generatedNamespace: "NamespaceA", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name( + base: "namespacea", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespacea" + ), methods: [methodA, methodB] ) @@ -442,7 +438,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueMethodName, message: """ - Methods of a service must have unique signature names. \ + Methods of a service must have unique lower case names. \ methodA is used as a signature name for multiple methods of the AService service. """ ) @@ -453,18 +449,18 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { func testSameGeneratedNameNoNamespaceServiceAndNamespaceError() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for SameName service with no namespace", - name: "SameName", - generatedName: "SameName", - namespace: "", - generatedNamespace: "", + name: Name(base: "SameName", generatedUpperCase: "SameName", generatedLowerCase: "sameName"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", - name: "BService", - generatedName: "BService", - namespace: "SameName", - generatedNamespace: "SameName", + name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bService"), + namespace: Name( + base: "sameName", + generatedUpperCase: "SameName", + generatedLowerCase: "sameName" + ), methods: [] ) let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) @@ -483,119 +479,8 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { CodeGenError( code: .nonUniqueServiceName, message: """ - Services with no namespace must not have the same generated names as the namespaces. \ - SameName is used as a generated name for a service with no namespace and a namespace. - """ - ) - ) - } - } - - func testEmptyNamespaceAndNonEmptyGeneratedNamespaceError() throws { - let serviceA = ServiceDescriptor( - documentation: "Documentation for SameName service with no namespace", - name: "SameName", - generatedName: "SameName", - namespace: "", - generatedNamespace: "NonEmpty", - methods: [] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .invalidGeneratedNamespace, - message: """ - Services with an empty namespace must have an empty generated namespace. \ - SameName has an empty namespace, but a non-empty generated namespace. - """ - ) - ) - } - } - - func testNonEmptyNamespaceAndEmptyGeneratedNamespaceError() throws { - let serviceA = ServiceDescriptor( - documentation: "Documentation for SameName service with no namespace", - name: "SameName", - generatedName: "SameName", - namespace: "NonEmpty", - generatedNamespace: "", - methods: [] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .invalidGeneratedNamespace, - message: """ - Services with a non-empty namespace must have a non-empty generated namespace. \ - SameName has a non-empty namespace, but an empty generated namespace. - """ - ) - ) - } - } - - func testNamespaceAndGeneratedNamespaceServicesCoincide() throws { - // Same namespaces, different generated namespaces. - let serviceA = ServiceDescriptor( - documentation: "Documentation for SameName service with no namespace", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", - methods: [] - ) - let serviceB = ServiceDescriptor( - documentation: "Documentation for SameName service with no namespace", - name: "ServiceB", - generatedName: "ServiceB", - namespace: "namespaceA", - generatedNamespace: "NamespaceB", - methods: [] - ) - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .invalidGeneratedNamespace, - message: """ - All services within a namespace must have the same generated namespace. \ - ServiceB doesn't have the same generated namespace as other services \ - within the namespaceA namespace. + Services with no namespace must not have the same generated upper case names as the namespaces. \ + SameName is used as a generated upper case name for a service with no namespace and a namespace. """ ) ) diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift index 4496a4e54..0be2230e4 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift @@ -23,13 +23,12 @@ import XCTest final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor + typealias Name = GRPCCodeGen.CodeGenerationRequest.Name func testServerCodeTranslatorUnaryMethod() throws { let method = MethodDescriptor( documentation: "Documentation for unaryMethod", - name: "UnaryMethod", - generatedName: "Unary", - signatureName: "unary", + name: Name(base: "UnaryMethod", generatedUpperCase: "Unary", generatedLowerCase: "unary"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -37,10 +36,16 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "AlongNameForServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name( + base: "AlongNameForServiceA", + generatedUpperCase: "ServiceA", + generatedLowerCase: "serviceA" + ), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [method] ) let expectedSwift = @@ -86,9 +91,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorInputStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for inputStreamingMethod", - name: "InputStreamingMethod", - generatedName: "InputStreaming", - signatureName: "inputStreaming", + name: Name( + base: "InputStreamingMethod", + generatedUpperCase: "InputStreaming", + generatedLowerCase: "inputStreaming" + ), isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -96,10 +103,12 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [method] ) let expectedSwift = @@ -145,9 +154,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorOutputStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for outputStreamingMethod", - name: "OutputStreamingMethod", - generatedName: "OutputStreaming", - signatureName: "outputStreaming", + name: Name( + base: "OutputStreamingMethod", + generatedUpperCase: "OutputStreaming", + generatedLowerCase: "outputStreaming" + ), isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -155,10 +166,16 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceATest", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name( + base: "ServiceATest", + generatedUpperCase: "ServiceA", + generatedLowerCase: "serviceA" + ), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [method] ) let expectedSwift = @@ -204,9 +221,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorBidirectionalStreamingMethod() throws { let method = MethodDescriptor( documentation: "Documentation for bidirectionalStreamingMethod", - name: "BidirectionalStreamingMethod", - generatedName: "BidirectionalStreaming", - signatureName: "bidirectionalStreaming", + name: Name( + base: "BidirectionalStreamingMethod", + generatedUpperCase: "BidirectionalStreaming", + generatedLowerCase: "bidirectionalStreaming" + ), isInputStreaming: true, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -214,10 +233,16 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceATest", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name( + base: "ServiceATest", + generatedUpperCase: "ServiceA", + generatedLowerCase: "serviceA" + ), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [method] ) let expectedSwift = @@ -259,9 +284,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorMultipleMethods() throws { let inputStreamingMethod = MethodDescriptor( documentation: "Documentation for inputStreamingMethod", - name: "InputStreamingMethod", - generatedName: "InputStreaming", - signatureName: "inputStreaming", + name: Name( + base: "InputStreamingMethod", + generatedUpperCase: "InputStreaming", + generatedLowerCase: "inputStreaming" + ), isInputStreaming: true, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -269,9 +296,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let outputStreamingMethod = MethodDescriptor( documentation: "Documentation for outputStreamingMethod", - name: "outputStreamingMethod", - generatedName: "OutputStreaming", - signatureName: "outputStreaming", + name: Name( + base: "outputStreamingMethod", + generatedUpperCase: "OutputStreaming", + generatedLowerCase: "outputStreaming" + ), isInputStreaming: false, isOutputStreaming: true, inputType: "NamespaceA_ServiceARequest", @@ -279,10 +308,16 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceATest", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name( + base: "ServiceATest", + generatedUpperCase: "ServiceA", + generatedLowerCase: "serviceA" + ), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [inputStreamingMethod, outputStreamingMethod] ) let expectedSwift = @@ -344,9 +379,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorNoNamespaceService() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "methodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "methodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -354,10 +387,12 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceATest", - generatedName: "ServiceA", - namespace: "", - generatedNamespace: "", + name: Name( + base: "ServiceATest", + generatedUpperCase: "ServiceA", + generatedLowerCase: "serviceA" + ), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [method] ) let expectedSwift = @@ -403,18 +438,22 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { func testServerCodeTranslatorMoreServicesOrder() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for ServiceB", - name: "ServiceB", - generatedName: "ServiceB", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceB", generatedUpperCase: "ServiceB", generatedLowerCase: "serviceB"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let expectedSwift = diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift index bb1759acd..87e1b3770 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift @@ -23,13 +23,12 @@ import XCTest final class TypealiasTranslatorSnippetBasedTests: XCTestCase { typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor + typealias Name = GRPCCodeGen.CodeGenerationRequest.Name func testTypealiasTranslator() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -37,10 +36,12 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [method] ) let expectedSwift = @@ -79,10 +80,12 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorNoMethodsServiceClientAndServer() throws { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let expectedSwift = @@ -110,10 +113,12 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorServer() throws { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let expectedSwift = @@ -139,10 +144,12 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorClient() throws { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let expectedSwift = @@ -168,10 +175,12 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorNoClientNoServer() throws { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let expectedSwift = @@ -195,9 +204,7 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorEmptyNamespace() throws { let method = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "ServiceARequest", @@ -205,10 +212,8 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "", - generatedNamespace: "", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [method] ) let expectedSwift = @@ -245,9 +250,7 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorCheckMethodsOrder() throws { let methodA = MethodDescriptor( documentation: "Documentation for MethodA", - name: "MethodA", - generatedName: "MethodA", - signatureName: "methodA", + name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -255,9 +258,7 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { ) let methodB = MethodDescriptor( documentation: "Documentation for MethodB", - name: "MethodB", - generatedName: "MethodB", - signatureName: "methodB", + name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodB"), isInputStreaming: false, isOutputStreaming: false, inputType: "NamespaceA_ServiceARequest", @@ -265,10 +266,12 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { ) let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [methodA, methodB] ) let expectedSwift = @@ -316,10 +319,12 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorNoMethodsService() throws { let service = ServiceDescriptor( documentation: "Documentation for ServiceA", - name: "ServiceA", - generatedName: "ServiceA", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let expectedSwift = @@ -347,19 +352,23 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorServiceAlphabeticalOrder() throws { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", - name: "BService", - generatedName: "Bservice", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "BService", generatedUpperCase: "Bservice", generatedLowerCase: "bservice"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "Aservice", - namespace: "namespaceA", - generatedNamespace: "NamespaceA", + name: Name(base: "AService", generatedUpperCase: "Aservice", generatedLowerCase: "aservice"), + namespace: Name( + base: "namespaceA", + generatedUpperCase: "NamespaceA", + generatedLowerCase: "namespaceA" + ), methods: [] ) @@ -396,19 +405,15 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorServiceAlphabeticalOrderNoNamespace() throws { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", - name: "BService", - generatedName: "BService", - namespace: "", - generatedNamespace: "", + name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bservice"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "", - generatedNamespace: "", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aservice"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) @@ -443,19 +448,23 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorNamespaceAlphabeticalOrder() throws { let serviceB = ServiceDescriptor( documentation: "Documentation for BService", - name: "BService", - generatedName: "BService", - namespace: "bnamespace", - generatedNamespace: "Bnamespace", + name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bservice"), + namespace: Name( + base: "bnamespace", + generatedUpperCase: "Bnamespace", + generatedLowerCase: "bnamespace" + ), methods: [] ) let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "anamespace", - generatedNamespace: "Anamespace", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aservice"), + namespace: Name( + base: "anamespace", + generatedUpperCase: "Anamespace", + generatedLowerCase: "anamespace" + ), methods: [] ) @@ -494,18 +503,18 @@ final class TypealiasTranslatorSnippetBasedTests: XCTestCase { func testTypealiasTranslatorNamespaceNoNamespaceOrder() throws { let serviceA = ServiceDescriptor( documentation: "Documentation for AService", - name: "AService", - generatedName: "AService", - namespace: "anamespace", - generatedNamespace: "Anamespace", + name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), + namespace: Name( + base: "anamespace", + generatedUpperCase: "Anamespace", + generatedLowerCase: "anamespace" + ), methods: [] ) let serviceB = ServiceDescriptor( documentation: "Documentation for BService", - name: "BService", - generatedName: "BService", - namespace: "", - generatedNamespace: "", + name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bService"), + namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), methods: [] ) let expectedSwift = From cd48c8860b4d957047660143ec030221a4876c33 Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Wed, 17 Jan 2024 14:41:44 +0000 Subject: [PATCH 4/4] implemented feedback --- .../GRPCCodeGen/CodeGenerationRequest.swift | 52 +++++----- .../IDLToStructuredSwiftTranslator.swift | 96 +++++++++---------- 2 files changed, 69 insertions(+), 79 deletions(-) diff --git a/Sources/GRPCCodeGen/CodeGenerationRequest.swift b/Sources/GRPCCodeGen/CodeGenerationRequest.swift index 8b24778bf..fab71b977 100644 --- a/Sources/GRPCCodeGen/CodeGenerationRequest.swift +++ b/Sources/GRPCCodeGen/CodeGenerationRequest.swift @@ -220,12 +220,16 @@ public struct CodeGenerationRequest { /// Documentation from comments above the IDL service description. public var documentation: String - /// Service name in different formats. + /// The service name in different formats. + /// + /// All properties of this object must be unique for each service from within a namespace. public var name: Name /// The service namespace in different formats. /// - /// For `.proto` files it is the package name. + /// All different services from within the same namespace must have + /// the same ``Name`` object as this property. + /// For `.proto` files the base name of this object is the package name. public var namespace: Name /// A description of each method of a service. @@ -251,6 +255,9 @@ public struct CodeGenerationRequest { public var documentation: String /// Method name in different formats. + /// + /// All properties of this object must be unique for each method + /// from within a service. public var name: Name /// Identifies if the method is input streaming. @@ -283,38 +290,31 @@ public struct CodeGenerationRequest { } } - /// Represents the name associated with a namespace, service or a method, in three different formats, - /// which are used in specific parts of the generated code. There must be only one ``Name`` object - /// for each namespace, service or method. - /// - /// The base name, the generatedUpperCase (and the generatedLowerCase) must be unique for methods - /// from within the same service and for services within the same namespace. + /// Represents the name associated with a namespace, service or a method, in three different formats. public struct Name: Hashable { /// The base name is the name used for the namespace/service/method in the IDL file, so it should follow /// the specific casing of the IDL. /// /// The base name is also used in the descriptors that identify a specific method or service : /// `..`. - public let base: String + public var base: String - /// The `generatedUpperCase` name is used as part of generated type names, which begin with a capital letter - /// and are (partially) using CamelCase. It is using UpperCamelCase in order to follow the Swift naming conventions. + /// The `generatedUpperCase` name is used in the generated code. It is expected + /// to be the UpperCamelCase version of the base name /// - /// It is used in the generated code as an enum name and as part of protocol names. - /// For example, in the generated server code the name of the service protocol - /// follows this pattern: - /// `_ServiceProtocol`. - public let generatedUpperCase: String - - /// The `generatedLowerCase` name is used as the function name in the method declarations or definitions. - /// It is using lowerCamelCase in order to follow the Swift naming conventions. + /// For example, if `base` is "fooBar", then `generatedUpperCase` is "FooBar". + public var generatedUpperCase: String + + /// The `generatedLowerCase` name is used in the generated code. It is expected + /// to be the lowerCamelCase version of the base name /// - /// It is used only for the methods, so in the case of a namespace or service, it can be an empty String. - /// For example, for a method with the base name "FooBar", the generatedLowerCase is "fooBar" and it is - /// used in: - /// ```swift - /// public func fooBar(){} - /// ``` - public let generatedLowerCase: String + /// For example, if `base` is "FooBar", then `generatedLowerCase` is "fooBar". + public var generatedLowerCase: String + + public init(base: String, generatedUpperCase: String, generatedLowerCase: String) { + self.base = base + self.generatedUpperCase = generatedUpperCase + self.generatedLowerCase = generatedLowerCase + } } } diff --git a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift index ce80d4441..2f8a81707 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift @@ -170,72 +170,61 @@ extension IDLToStructuredSwiftTranslator { // Check that the method descriptors are unique, by checking that the base names // of the methods of a specific service are unique. let baseNames = service.methods.map { $0.name.base } - var seenBaseNames = Set() - - for baseName in baseNames { - if seenBaseNames.contains(baseName) { - throw CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique base names. \ - \(baseName) is used as a base name for multiple methods of the \(service.name.base) service. - """ - ) - } - seenBaseNames.insert(baseName) + if let duplicatedBase = baseNames.getFirstDuplicate() { + throw CodeGenError( + code: .nonUniqueMethodName, + message: """ + Methods of a service must have unique base names. \ + \(duplicatedBase) is used as a base name for multiple methods of the \(service.name.base) service. + """ + ) } // Check that generated upper case names for methods are unique within a service, to ensure that // the enums containing type aliases for each method of a service. let upperCaseNames = service.methods.map { $0.name.generatedUpperCase } - var seenUpperCaseNames = Set() - - for upperCaseName in upperCaseNames { - if seenUpperCaseNames.contains(upperCaseName) { - throw CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique generated upper case names. \ - \(upperCaseName) is used as a generated upper case name for multiple methods of the \(service.name.base) service. - """ - ) - } - seenUpperCaseNames.insert(upperCaseName) + if let duplicatedGeneratedUpperCase = upperCaseNames.getFirstDuplicate() { + throw CodeGenError( + code: .nonUniqueMethodName, + message: """ + Methods of a service must have unique generated upper case names. \ + \(duplicatedGeneratedUpperCase) is used as a generated upper case name for multiple methods of the \(service.name.base) service. + """ + ) } // Check that generated lower case names for methods are unique within a service, to ensure that // the function declarations and definitions from the same protocols and extensions have unique names. let lowerCaseNames = service.methods.map { $0.name.generatedLowerCase } - var seenLowerCaseNames = Set() - - for lowerCaseName in lowerCaseNames { - if seenLowerCaseNames.contains(lowerCaseName) { - throw CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique lower case names. \ - \(lowerCaseName) is used as a signature name for multiple methods of the \(service.name.base) service. - """ - ) - } - seenLowerCaseNames.insert(lowerCaseName) + if let duplicatedLowerCase = lowerCaseNames.getFirstDuplicate() { + throw CodeGenError( + code: .nonUniqueMethodName, + message: """ + Methods of a service must have unique lower case names. \ + \(duplicatedLowerCase) is used as a signature name for multiple methods of the \(service.name.base) service. + """ + ) } } private func checkServiceDescriptorsAreUnique( _ services: [CodeGenerationRequest.ServiceDescriptor] ) throws { - let descriptors = services.map { - ($0.namespace.base.isEmpty ? "" : "\($0.namespace.base).") + $0.name.base - } - if let duplicate = descriptors.hasDuplicates() { - throw CodeGenError( - code: .nonUniqueServiceName, - message: """ - Services must have unique descriptors. \ - \(duplicate) is the descriptor of at least two different services. - """ - ) + var descriptors: Set = [] + for service in services { + let name = + service.namespace.base.isEmpty + ? service.name.base : "\(service.namespace.base).\(service.name.base)" + let (inserted, _) = descriptors.insert(name) + if !inserted { + throw CodeGenError( + code: .nonUniqueServiceName, + message: """ + Services must have unique descriptors. \ + \(name) is the descriptor of at least two different services. + """ + ) + } } } } @@ -267,12 +256,13 @@ extension CodeGenerationRequest.ServiceDescriptor { } extension [String] { - func hasDuplicates() -> String? { - var elements = Set() + internal func getFirstDuplicate() -> String? { + var seen = Set() for element in self { - if elements.insert(element).inserted == false { + if seen.contains(element) { return element } + seen.insert(element) } return nil }