Browse Source

Add a codegen option to allow stub names to not have their first character lowercased (#1123)

Motivation:

Protobuf suggests that RPC methods should always start with an uppercase
character. Function names in Swift typically start with a lowercase
character. As such, by default, protoc-gen-grpc-swift will lowercase the
first character.

Having an uppercase RPC method is by no means a requirement and since
routing is not case sensitive, defining two methods 'Foo' and 'foo'
within the same service will lead to compilation errors since both
generated Swift function names will be 'foo'.

Modifications:

- Add an escape hatch to the plugin to allow for method casing to be
  kept, this defaults to false
- Add a test service using these options
- Update docs

Result:

Users can generate code which dos not alter the casing of the RPC name
George Barnett 5 years ago
parent
commit
c5578d48df

+ 16 - 0
Makefile

@@ -101,6 +101,22 @@ ROUTE_GUIDE_GRPC=$(ROUTE_GUIDE_PROTO:.proto=.grpc.swift)
 .PHONY:
 generate-route-guide: ${ROUTE_GUIDE_PB} ${ROUTE_GUIDE_GRPC}
 
+NORMALIZATION_PROTO=Tests/GRPCTests/Codegen/Normalization/normalization.proto
+NORMALIZATION_PB=$(NORMALIZATION_PROTO:.proto=.pb.swift)
+NORMALIZATION_GRPC=$(NORMALIZATION_PROTO:.proto=.grpc.swift)
+
+# For normalization we'll explicitly keep the method casing.
+${NORMALIZATION_GRPC}: ${NORMALIZATION_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
+	protoc $< \
+		--proto_path=$(dir $<) \
+		--plugin=${PROTOC_GEN_GRPC_SWIFT} \
+		--grpc-swift_opt=KeepMethodCasing=true \
+		--grpc-swift_out=$(dir $<)
+
+# Generates protobufs and gRPC client and server for the Route Guide example
+.PHONY:
+generate-normalization: ${NORMALIZATION_PB} ${NORMALIZATION_GRPC}
+
 ### Testing ####################################################################
 
 # Normal test suite.

+ 5 - 1
Sources/protoc-gen-grpc-swift/Generator-Names.swift

@@ -71,7 +71,11 @@ extension Generator {
 
   internal var methodFunctionName: String {
     let name = method.name
-    return name.prefix(1).lowercased() + name.dropFirst()
+    if self.options.keepMethodCasing {
+      return name
+    } else {
+      return name.prefix(1).lowercased() + name.dropFirst()
+    }
   }
 
   internal var methodInputName: String {

+ 8 - 0
Sources/protoc-gen-grpc-swift/options.swift

@@ -55,6 +55,7 @@ final class GeneratorOptions {
   private(set) var generateServer = true
   private(set) var generateClient = true
   private(set) var generateTestClient = false
+  private(set) var keepMethodCasing = false
   private(set) var protoToModuleMappings = ProtoFileToModuleMappings()
   private(set) var fileNaming = FileNaming.FullPath
   private(set) var extraModuleImports: [String] = []
@@ -91,6 +92,13 @@ final class GeneratorOptions {
           throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
         }
 
+      case "KeepMethodCasing":
+        if let value = Bool(pair.value) {
+          self.keepMethodCasing = value
+        } else {
+          throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value)
+        }
+
       case "ProtoPathModuleMappings":
         if !pair.value.isEmpty {
           do {

+ 131 - 0
Tests/GRPCTests/Codegen/Normalization/NormalizationProvider.swift

@@ -0,0 +1,131 @@
+/*
+ * Copyright 2021, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import GRPC
+import NIO
+import SwiftProtobuf
+
+final class NormalizationProvider: Normalization_NormalizationProvider {
+  let interceptors: Normalization_NormalizationServerInterceptorFactoryProtocol? = nil
+
+  // MARK: Unary
+
+  private func unary(
+    context: StatusOnlyCallContext,
+    function: String = #function
+  ) -> EventLoopFuture<Normalization_FunctionName> {
+    return context.eventLoop.makeSucceededFuture(.with { $0.functionName = function })
+  }
+
+  func Unary(
+    request: Google_Protobuf_Empty,
+    context: StatusOnlyCallContext
+  ) -> EventLoopFuture<Normalization_FunctionName> {
+    return self.unary(context: context)
+  }
+
+  func unary(
+    request: Google_Protobuf_Empty,
+    context: StatusOnlyCallContext
+  ) -> EventLoopFuture<Normalization_FunctionName> {
+    return self.unary(context: context)
+  }
+
+  // MARK: Server Streaming
+
+  private func serverStreaming(
+    context: StreamingResponseCallContext<Normalization_FunctionName>,
+    function: String = #function
+  ) -> EventLoopFuture<GRPCStatus> {
+    context.sendResponse(.with { $0.functionName = function }, promise: nil)
+    return context.eventLoop.makeSucceededFuture(.ok)
+  }
+
+  func ServerStreaming(
+    request: Google_Protobuf_Empty,
+    context: StreamingResponseCallContext<Normalization_FunctionName>
+  ) -> EventLoopFuture<GRPCStatus> {
+    return self.serverStreaming(context: context)
+  }
+
+  func serverStreaming(
+    request: Google_Protobuf_Empty,
+    context: StreamingResponseCallContext<Normalization_FunctionName>
+  ) -> EventLoopFuture<GRPCStatus> {
+    return self.serverStreaming(context: context)
+  }
+
+  // MARK: Client Streaming
+
+  private func _clientStreaming(
+    context: UnaryResponseCallContext<Normalization_FunctionName>,
+    function: String = #function
+  ) -> EventLoopFuture<(StreamEvent<Google_Protobuf_Empty>) -> Void> {
+    func handle(_ event: StreamEvent<Google_Protobuf_Empty>) {
+      switch event {
+      case .message:
+        ()
+      case .end:
+        context.responsePromise.succeed(.with { $0.functionName = function })
+      }
+    }
+
+    return context.eventLoop.makeSucceededFuture(handle(_:))
+  }
+
+  func ClientStreaming(
+    context: UnaryResponseCallContext<Normalization_FunctionName>
+  ) -> EventLoopFuture<(StreamEvent<Google_Protobuf_Empty>) -> Void> {
+    return self._clientStreaming(context: context)
+  }
+
+  func clientStreaming(
+    context: UnaryResponseCallContext<Normalization_FunctionName>
+  ) -> EventLoopFuture<(StreamEvent<Google_Protobuf_Empty>) -> Void> {
+    return self._clientStreaming(context: context)
+  }
+
+  // MARK: Bidirectional Streaming
+
+  private func _bidirectionalStreaming(
+    context: StreamingResponseCallContext<Normalization_FunctionName>,
+    function: String = #function
+  ) -> EventLoopFuture<(StreamEvent<Google_Protobuf_Empty>) -> Void> {
+    func handle(_ event: StreamEvent<Google_Protobuf_Empty>) {
+      switch event {
+      case .message:
+        ()
+      case .end:
+        context.sendResponse(.with { $0.functionName = function }, promise: nil)
+        context.statusPromise.succeed(.ok)
+      }
+    }
+
+    return context.eventLoop.makeSucceededFuture(handle(_:))
+  }
+
+  func BidirectionalStreaming(
+    context: StreamingResponseCallContext<Normalization_FunctionName>
+  ) -> EventLoopFuture<(StreamEvent<Google_Protobuf_Empty>) -> Void> {
+    return self._bidirectionalStreaming(context: context)
+  }
+
+  func bidirectionalStreaming(
+    context: StreamingResponseCallContext<Normalization_FunctionName>
+  ) -> EventLoopFuture<(StreamEvent<Google_Protobuf_Empty>) -> Void> {
+    return self._bidirectionalStreaming(context: context)
+  }
+}

+ 108 - 0
Tests/GRPCTests/Codegen/Normalization/NormalizationTests.swift

@@ -0,0 +1,108 @@
+/*
+ * Copyright 2021, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import GRPC
+import NIO
+import XCTest
+
+/// These tests validate that:
+/// - we can compile generated code for functions with same (case-insensitive) name (providing they
+///   are generated with 'KeepMethodCasing=true')
+/// - the right client function calls the server function with the expected casing.
+final class NormalizationTests: GRPCTestCase {
+  var group: EventLoopGroup!
+  var server: Server!
+  var channel: ClientConnection!
+
+  override func setUp() {
+    super.setUp()
+
+    self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+
+    self.server = try! Server.insecure(group: self.group)
+      .withLogger(self.serverLogger)
+      .withServiceProviders([NormalizationProvider()])
+      .bind(host: "localhost", port: 0)
+      .wait()
+
+    self.channel = ClientConnection.insecure(group: self.group)
+      .withBackgroundActivityLogger(self.clientLogger)
+      .connect(host: "localhost", port: self.server.channel.localAddress!.port!)
+  }
+
+  override func tearDown() {
+    XCTAssertNoThrow(try self.channel.close().wait())
+    XCTAssertNoThrow(try self.server.initiateGracefulShutdown().wait())
+    XCTAssertNoThrow(try self.group.syncShutdownGracefully())
+    super.tearDown()
+  }
+
+  func testUnary() throws {
+    let client = Normalization_NormalizationClient(channel: channel)
+
+    let unary1 = client.unary(.init())
+    let response1 = try unary1.response.wait()
+    XCTAssert(response1.functionName.starts(with: "unary"))
+
+    let unary2 = client.Unary(.init())
+    let response2 = try unary2.response.wait()
+    XCTAssert(response2.functionName.starts(with: "Unary"))
+  }
+
+  func testClientStreaming() throws {
+    let client = Normalization_NormalizationClient(channel: channel)
+
+    let clientStreaming1 = client.clientStreaming()
+    clientStreaming1.sendEnd(promise: nil)
+    let response1 = try clientStreaming1.response.wait()
+    XCTAssert(response1.functionName.starts(with: "clientStreaming"))
+
+    let clientStreaming2 = client.ClientStreaming()
+    clientStreaming2.sendEnd(promise: nil)
+    let response2 = try clientStreaming2.response.wait()
+    XCTAssert(response2.functionName.starts(with: "ClientStreaming"))
+  }
+
+  func testServerStreaming() throws {
+    let client = Normalization_NormalizationClient(channel: channel)
+
+    let serverStreaming1 = client.serverStreaming(.init()) {
+      XCTAssert($0.functionName.starts(with: "serverStreaming"))
+    }
+    XCTAssertEqual(try serverStreaming1.status.wait(), .ok)
+
+    let serverStreaming2 = client.ServerStreaming(.init()) {
+      XCTAssert($0.functionName.starts(with: "ServerStreaming"))
+    }
+    XCTAssertEqual(try serverStreaming2.status.wait(), .ok)
+  }
+
+  func testBidirectionalStreaming() throws {
+    let client = Normalization_NormalizationClient(channel: channel)
+
+    let bidirectionalStreaming1 = client.bidirectionalStreaming {
+      XCTAssert($0.functionName.starts(with: "bidirectionalStreaming"))
+    }
+    bidirectionalStreaming1.sendEnd(promise: nil)
+    XCTAssertEqual(try bidirectionalStreaming1.status.wait(), .ok)
+
+    let bidirectionalStreaming2 = client.BidirectionalStreaming {
+      XCTAssert($0.functionName.starts(with: "BidirectionalStreaming"))
+    }
+    bidirectionalStreaming2.sendEnd(promise: nil)
+    XCTAssertEqual(try bidirectionalStreaming2.status.wait(), .ok)
+  }
+}

+ 427 - 0
Tests/GRPCTests/Codegen/Normalization/normalization.grpc.swift

@@ -0,0 +1,427 @@
+//
+// DO NOT EDIT.
+//
+// Generated by the protocol buffer compiler.
+// Source: normalization.proto
+//
+
+//
+// Copyright 2018, gRPC Authors All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+import GRPC
+import NIO
+import SwiftProtobuf
+
+
+/// Usage: instantiate `Normalization_NormalizationClient`, then call methods of this protocol to make API calls.
+internal protocol Normalization_NormalizationClientProtocol: GRPCClient {
+  var serviceName: String { get }
+  var interceptors: Normalization_NormalizationClientInterceptorFactoryProtocol? { get }
+
+  func Unary(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions?
+  ) -> UnaryCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+
+  func unary(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions?
+  ) -> UnaryCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+
+  func ServerStreaming(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions?,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> ServerStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+
+  func serverStreaming(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions?,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> ServerStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+
+  func ClientStreaming(
+    callOptions: CallOptions?
+  ) -> ClientStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+
+  func clientStreaming(
+    callOptions: CallOptions?
+  ) -> ClientStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+
+  func BidirectionalStreaming(
+    callOptions: CallOptions?,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> BidirectionalStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+
+  func bidirectionalStreaming(
+    callOptions: CallOptions?,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> BidirectionalStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>
+}
+
+extension Normalization_NormalizationClientProtocol {
+  internal var serviceName: String {
+    return "normalization.Normalization"
+  }
+
+  /// Unary call to Unary
+  ///
+  /// - Parameters:
+  ///   - request: Request to send to Unary.
+  ///   - callOptions: Call options.
+  /// - Returns: A `UnaryCall` with futures for the metadata, status and response.
+  internal func Unary(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions? = nil
+  ) -> UnaryCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeUnaryCall(
+      path: "/normalization.Normalization/Unary",
+      request: request,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makeUnaryInterceptors() ?? []
+    )
+  }
+
+  /// Unary call to unary
+  ///
+  /// - Parameters:
+  ///   - request: Request to send to unary.
+  ///   - callOptions: Call options.
+  /// - Returns: A `UnaryCall` with futures for the metadata, status and response.
+  internal func unary(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions? = nil
+  ) -> UnaryCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeUnaryCall(
+      path: "/normalization.Normalization/unary",
+      request: request,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makeunaryInterceptors() ?? []
+    )
+  }
+
+  /// Server streaming call to ServerStreaming
+  ///
+  /// - Parameters:
+  ///   - request: Request to send to ServerStreaming.
+  ///   - callOptions: Call options.
+  ///   - handler: A closure called when each response is received from the server.
+  /// - Returns: A `ServerStreamingCall` with futures for the metadata and status.
+  internal func ServerStreaming(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions? = nil,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> ServerStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeServerStreamingCall(
+      path: "/normalization.Normalization/ServerStreaming",
+      request: request,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makeServerStreamingInterceptors() ?? [],
+      handler: handler
+    )
+  }
+
+  /// Server streaming call to serverStreaming
+  ///
+  /// - Parameters:
+  ///   - request: Request to send to serverStreaming.
+  ///   - callOptions: Call options.
+  ///   - handler: A closure called when each response is received from the server.
+  /// - Returns: A `ServerStreamingCall` with futures for the metadata and status.
+  internal func serverStreaming(
+    _ request: SwiftProtobuf.Google_Protobuf_Empty,
+    callOptions: CallOptions? = nil,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> ServerStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeServerStreamingCall(
+      path: "/normalization.Normalization/serverStreaming",
+      request: request,
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makeserverStreamingInterceptors() ?? [],
+      handler: handler
+    )
+  }
+
+  /// Client streaming call to ClientStreaming
+  ///
+  /// Callers should use the `send` method on the returned object to send messages
+  /// to the server. The caller should send an `.end` after the final message has been sent.
+  ///
+  /// - Parameters:
+  ///   - callOptions: Call options.
+  /// - Returns: A `ClientStreamingCall` with futures for the metadata, status and response.
+  internal func ClientStreaming(
+    callOptions: CallOptions? = nil
+  ) -> ClientStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeClientStreamingCall(
+      path: "/normalization.Normalization/ClientStreaming",
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makeClientStreamingInterceptors() ?? []
+    )
+  }
+
+  /// Client streaming call to clientStreaming
+  ///
+  /// Callers should use the `send` method on the returned object to send messages
+  /// to the server. The caller should send an `.end` after the final message has been sent.
+  ///
+  /// - Parameters:
+  ///   - callOptions: Call options.
+  /// - Returns: A `ClientStreamingCall` with futures for the metadata, status and response.
+  internal func clientStreaming(
+    callOptions: CallOptions? = nil
+  ) -> ClientStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeClientStreamingCall(
+      path: "/normalization.Normalization/clientStreaming",
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makeclientStreamingInterceptors() ?? []
+    )
+  }
+
+  /// Bidirectional streaming call to BidirectionalStreaming
+  ///
+  /// Callers should use the `send` method on the returned object to send messages
+  /// to the server. The caller should send an `.end` after the final message has been sent.
+  ///
+  /// - Parameters:
+  ///   - callOptions: Call options.
+  ///   - handler: A closure called when each response is received from the server.
+  /// - Returns: A `ClientStreamingCall` with futures for the metadata and status.
+  internal func BidirectionalStreaming(
+    callOptions: CallOptions? = nil,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> BidirectionalStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeBidirectionalStreamingCall(
+      path: "/normalization.Normalization/BidirectionalStreaming",
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makeBidirectionalStreamingInterceptors() ?? [],
+      handler: handler
+    )
+  }
+
+  /// Bidirectional streaming call to bidirectionalStreaming
+  ///
+  /// Callers should use the `send` method on the returned object to send messages
+  /// to the server. The caller should send an `.end` after the final message has been sent.
+  ///
+  /// - Parameters:
+  ///   - callOptions: Call options.
+  ///   - handler: A closure called when each response is received from the server.
+  /// - Returns: A `ClientStreamingCall` with futures for the metadata and status.
+  internal func bidirectionalStreaming(
+    callOptions: CallOptions? = nil,
+    handler: @escaping (Normalization_FunctionName) -> Void
+  ) -> BidirectionalStreamingCall<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName> {
+    return self.makeBidirectionalStreamingCall(
+      path: "/normalization.Normalization/bidirectionalStreaming",
+      callOptions: callOptions ?? self.defaultCallOptions,
+      interceptors: self.interceptors?.makebidirectionalStreamingInterceptors() ?? [],
+      handler: handler
+    )
+  }
+}
+
+internal protocol Normalization_NormalizationClientInterceptorFactoryProtocol {
+
+  /// - Returns: Interceptors to use when invoking 'Unary'.
+  func makeUnaryInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when invoking 'unary'.
+  func makeunaryInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when invoking 'ServerStreaming'.
+  func makeServerStreamingInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when invoking 'serverStreaming'.
+  func makeserverStreamingInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when invoking 'ClientStreaming'.
+  func makeClientStreamingInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when invoking 'clientStreaming'.
+  func makeclientStreamingInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when invoking 'BidirectionalStreaming'.
+  func makeBidirectionalStreamingInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when invoking 'bidirectionalStreaming'.
+  func makebidirectionalStreamingInterceptors() -> [ClientInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+}
+
+internal final class Normalization_NormalizationClient: Normalization_NormalizationClientProtocol {
+  internal let channel: GRPCChannel
+  internal var defaultCallOptions: CallOptions
+  internal var interceptors: Normalization_NormalizationClientInterceptorFactoryProtocol?
+
+  /// Creates a client for the normalization.Normalization service.
+  ///
+  /// - Parameters:
+  ///   - channel: `GRPCChannel` to the service host.
+  ///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
+  ///   - interceptors: A factory providing interceptors for each RPC.
+  internal init(
+    channel: GRPCChannel,
+    defaultCallOptions: CallOptions = CallOptions(),
+    interceptors: Normalization_NormalizationClientInterceptorFactoryProtocol? = nil
+  ) {
+    self.channel = channel
+    self.defaultCallOptions = defaultCallOptions
+    self.interceptors = interceptors
+  }
+}
+
+/// To build a server, implement a class that conforms to this protocol.
+internal protocol Normalization_NormalizationProvider: CallHandlerProvider {
+  var interceptors: Normalization_NormalizationServerInterceptorFactoryProtocol? { get }
+
+  func Unary(request: SwiftProtobuf.Google_Protobuf_Empty, context: StatusOnlyCallContext) -> EventLoopFuture<Normalization_FunctionName>
+
+  func unary(request: SwiftProtobuf.Google_Protobuf_Empty, context: StatusOnlyCallContext) -> EventLoopFuture<Normalization_FunctionName>
+
+  func ServerStreaming(request: SwiftProtobuf.Google_Protobuf_Empty, context: StreamingResponseCallContext<Normalization_FunctionName>) -> EventLoopFuture<GRPCStatus>
+
+  func serverStreaming(request: SwiftProtobuf.Google_Protobuf_Empty, context: StreamingResponseCallContext<Normalization_FunctionName>) -> EventLoopFuture<GRPCStatus>
+
+  func ClientStreaming(context: UnaryResponseCallContext<Normalization_FunctionName>) -> EventLoopFuture<(StreamEvent<SwiftProtobuf.Google_Protobuf_Empty>) -> Void>
+
+  func clientStreaming(context: UnaryResponseCallContext<Normalization_FunctionName>) -> EventLoopFuture<(StreamEvent<SwiftProtobuf.Google_Protobuf_Empty>) -> Void>
+
+  func BidirectionalStreaming(context: StreamingResponseCallContext<Normalization_FunctionName>) -> EventLoopFuture<(StreamEvent<SwiftProtobuf.Google_Protobuf_Empty>) -> Void>
+
+  func bidirectionalStreaming(context: StreamingResponseCallContext<Normalization_FunctionName>) -> EventLoopFuture<(StreamEvent<SwiftProtobuf.Google_Protobuf_Empty>) -> Void>
+}
+
+extension Normalization_NormalizationProvider {
+  internal var serviceName: Substring { return "normalization.Normalization" }
+
+  /// Determines, calls and returns the appropriate request handler, depending on the request's method.
+  /// Returns nil for methods not handled by this service.
+  internal func handle(
+    method name: Substring,
+    context: CallHandlerContext
+  ) -> GRPCServerHandlerProtocol? {
+    switch name {
+    case "Unary":
+      return UnaryServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makeUnaryInterceptors() ?? [],
+        userFunction: self.Unary(request:context:)
+      )
+
+    case "unary":
+      return UnaryServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makeunaryInterceptors() ?? [],
+        userFunction: self.unary(request:context:)
+      )
+
+    case "ServerStreaming":
+      return ServerStreamingServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makeServerStreamingInterceptors() ?? [],
+        userFunction: self.ServerStreaming(request:context:)
+      )
+
+    case "serverStreaming":
+      return ServerStreamingServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makeserverStreamingInterceptors() ?? [],
+        userFunction: self.serverStreaming(request:context:)
+      )
+
+    case "ClientStreaming":
+      return ClientStreamingServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makeClientStreamingInterceptors() ?? [],
+        observerFactory: self.ClientStreaming(context:)
+      )
+
+    case "clientStreaming":
+      return ClientStreamingServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makeclientStreamingInterceptors() ?? [],
+        observerFactory: self.clientStreaming(context:)
+      )
+
+    case "BidirectionalStreaming":
+      return BidirectionalStreamingServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makeBidirectionalStreamingInterceptors() ?? [],
+        observerFactory: self.BidirectionalStreaming(context:)
+      )
+
+    case "bidirectionalStreaming":
+      return BidirectionalStreamingServerHandler(
+        context: context,
+        requestDeserializer: ProtobufDeserializer<SwiftProtobuf.Google_Protobuf_Empty>(),
+        responseSerializer: ProtobufSerializer<Normalization_FunctionName>(),
+        interceptors: self.interceptors?.makebidirectionalStreamingInterceptors() ?? [],
+        observerFactory: self.bidirectionalStreaming(context:)
+      )
+
+    default:
+      return nil
+    }
+  }
+}
+
+internal protocol Normalization_NormalizationServerInterceptorFactoryProtocol {
+
+  /// - Returns: Interceptors to use when handling 'Unary'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makeUnaryInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when handling 'unary'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makeunaryInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when handling 'ServerStreaming'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makeServerStreamingInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when handling 'serverStreaming'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makeserverStreamingInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when handling 'ClientStreaming'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makeClientStreamingInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when handling 'clientStreaming'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makeclientStreamingInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when handling 'BidirectionalStreaming'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makeBidirectionalStreamingInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+
+  /// - Returns: Interceptors to use when handling 'bidirectionalStreaming'.
+  ///   Defaults to calling `self.makeInterceptors()`.
+  func makebidirectionalStreamingInterceptors() -> [ServerInterceptor<SwiftProtobuf.Google_Protobuf_Empty, Normalization_FunctionName>]
+}

+ 84 - 0
Tests/GRPCTests/Codegen/Normalization/normalization.pb.swift

@@ -0,0 +1,84 @@
+// DO NOT EDIT.
+// swift-format-ignore-file
+//
+// Generated by the Swift generator plugin for the protocol buffer compiler.
+// Source: normalization.proto
+//
+// For information on using the generated types, please see the documentation:
+//   https://github.com/apple/swift-protobuf/
+
+// Copyright 2021 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Foundation
+import SwiftProtobuf
+
+// If the compiler emits an error on this type, it is because this file
+// was generated by a version of the `protoc` Swift plug-in that is
+// incompatible with the version of SwiftProtobuf to which you are linking.
+// Please ensure that you are building against the same version of the API
+// that was used to generate this file.
+fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
+  struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
+  typealias Version = _2
+}
+
+public struct Normalization_FunctionName {
+  // SwiftProtobuf.Message conformance is added in an extension below. See the
+  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
+  // methods supported on all messages.
+
+  /// The name of the invoked function.
+  public var functionName: String = String()
+
+  public var unknownFields = SwiftProtobuf.UnknownStorage()
+
+  public init() {}
+}
+
+// MARK: - Code below here is support for the SwiftProtobuf runtime.
+
+fileprivate let _protobuf_package = "normalization"
+
+extension Normalization_FunctionName: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
+  public static let protoMessageName: String = _protobuf_package + ".FunctionName"
+  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
+    1: .same(proto: "functionName"),
+  ]
+
+  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
+    while let fieldNumber = try decoder.nextFieldNumber() {
+      // The use of inline closures is to circumvent an issue where the compiler
+      // allocates stack space for every case branch when no optimizations are
+      // enabled. https://github.com/apple/swift-protobuf/issues/1034
+      switch fieldNumber {
+      case 1: try { try decoder.decodeSingularStringField(value: &self.functionName) }()
+      default: break
+      }
+    }
+  }
+
+  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
+    if !self.functionName.isEmpty {
+      try visitor.visitSingularStringField(value: self.functionName, fieldNumber: 1)
+    }
+    try unknownFields.traverse(visitor: &visitor)
+  }
+
+  public static func ==(lhs: Normalization_FunctionName, rhs: Normalization_FunctionName) -> Bool {
+    if lhs.functionName != rhs.functionName {return false}
+    if lhs.unknownFields != rhs.unknownFields {return false}
+    return true
+  }
+}

+ 38 - 0
Tests/GRPCTests/Codegen/Normalization/normalization.proto

@@ -0,0 +1,38 @@
+// Copyright 2021 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package normalization;
+
+import "google/protobuf/empty.proto";
+
+service Normalization {
+  rpc Unary(google.protobuf.Empty) returns (FunctionName) {}
+  rpc unary(google.protobuf.Empty) returns (FunctionName) {}
+
+  rpc ServerStreaming(google.protobuf.Empty) returns (stream FunctionName) {}
+  rpc serverStreaming(google.protobuf.Empty) returns (stream FunctionName) {}
+
+  rpc ClientStreaming(stream google.protobuf.Empty) returns (FunctionName) {}
+  rpc clientStreaming(stream google.protobuf.Empty) returns (FunctionName) {}
+
+  rpc BidirectionalStreaming(stream google.protobuf.Empty) returns (stream FunctionName) {}
+  rpc bidirectionalStreaming(stream google.protobuf.Empty) returns (stream FunctionName) {}
+}
+
+message FunctionName {
+  // The name of the invoked function.
+  string functionName = 1;
+}

+ 17 - 0
docs/plugin.md

@@ -74,6 +74,23 @@ The **FileNaming** option determines how generated source files should be named.
 The **ProtoPathModuleMappings** option allows module mappings to be specified.
 See the [SwiftProtobuf documentation][swift-protobuf-module-mappings].
 
+### KeepMethodCasing
+
+The **KeepMethodCasing** determines whether the casing of generated function
+names is kept.
+
+For example, for the following RPC definition:
+
+```proto
+rpc Foo(FooRequest) returns (FooRequest) {}
+```
+
+Will generate stubs named `foo` by default. However, in some cases this is not
+desired, and setting `KeepMethodCasing=true` will yield stubs named `Foo`.
+
+- **Possible values:** true, false
+- **Default value:** false
+
 ### GRPCModuleName
 
 The **GRPCModuleName** option allows the name of the gRPC Swift runtime module