diff --git a/Examples/Echo/Generated/echo.grpc.swift b/Examples/Echo/Generated/echo.grpc.swift index 2c8f6b15d..dbc3fdca5 100644 --- a/Examples/Echo/Generated/echo.grpc.swift +++ b/Examples/Echo/Generated/echo.grpc.swift @@ -1,26 +1,25 @@ -/* - * DO NOT EDIT. - * - * Generated by the protocol buffer compiler. - * Source: echo.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. - */ +// +// DO NOT EDIT. +// +// Generated by the protocol buffer compiler. +// Source: echo.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 Foundation import Dispatch import gRPC @@ -187,7 +186,6 @@ class Echo_EchoServiceTestStub: ServiceClientTestStubBase, Echo_EchoService { } - /// To build a server, implement a class that conforms to this protocol. internal protocol Echo_EchoProvider { func get(request: Echo_EchoRequest, session: Echo_EchoGetSession) throws -> Echo_EchoResponse diff --git a/Plugin/Makefile b/Plugin/Makefile index 4ddc7b892..fb3bbd3bf 100644 --- a/Plugin/Makefile +++ b/Plugin/Makefile @@ -2,9 +2,7 @@ default: build build: clear - swift build --product TemplateEncoder - .build/debug/TemplateEncoder > Sources/protoc-gen-swiftgrpc/templates.swift - swift build --product protoc-gen-swiftgrpc + swift build cp .build/debug/protoc-gen-swiftgrpc . cp .build/debug/protoc-gen-swift . diff --git a/Plugin/Package.swift b/Plugin/Package.swift index 7bab4a37b..46b5e3c8f 100644 --- a/Plugin/Package.swift +++ b/Plugin/Package.swift @@ -19,9 +19,7 @@ let package = Package( name: "SwiftGRPCPlugin", targets: [ Target(name: "protoc-gen-swiftgrpc"), - Target(name: "TemplateEncoder") ], dependencies: [ .Package(url: "https://github.com/apple/swift-protobuf.git", Version(1, 0, 2)), - .Package(url: "https://github.com/kylef/Stencil.git", Version(0, 10, 1)) ]) diff --git a/Plugin/README.md b/Plugin/README.md index 28847f7de..53837ae44 100644 --- a/Plugin/README.md +++ b/Plugin/README.md @@ -11,9 +11,6 @@ invocation from the Makefile: protoc ../Examples/Echo/echo.proto --proto_path=../Examples/Echo --plugin=./protoc-gen-swiftgrpc --swiftgrpc_out=. -The plugin uses template files in the [Templates](Templates) directory. -These files are compiled into the `protoc-gen-swiftgrpc` plugin executable. - The Swift gRPC plugin can be installed by placing the `protoc-gen-swiftgrpc` binary into one of the directories in your path. Specifying `--swiftgrpc_out` to `protoc` will automatically diff --git a/Plugin/Sources/TemplateEncoder/main.swift b/Plugin/Sources/TemplateEncoder/main.swift deleted file mode 100644 index 4cc7d081f..000000000 --- a/Plugin/Sources/TemplateEncoder/main.swift +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017, 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 Foundation - -let TEMPLATES = "Templates" - -var s = "" -s += "/*\n" -s += " *\n" -s += " * Copyright 2017, Google Inc.\n" -s += " * All rights reserved.\n" -s += " *\n" -s += " * Redistribution and use in source and binary forms, with or without\n" -s += " * modification, are permitted provided that the following conditions are\n" -s += " * met:\n" -s += " *\n" -s += " * * Redistributions of source code must retain the above copyright\n" -s += " * notice, this list of conditions and the following disclaimer.\n" -s += " * * Redistributions in binary form must reproduce the above\n" -s += " * copyright notice, this list of conditions and the following disclaimer\n" -s += " * in the documentation and/or other materials provided with the\n" -s += " * distribution.\n" -s += " * * Neither the name of Google Inc. nor the names of its\n" -s += " * contributors may be used to endorse or promote products derived from\n" -s += " * this software without specific prior written permission.\n" -s += " *\n" -s += " * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n" -s += " * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n" -s += " * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n" -s += " * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n" -s += " * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n" -s += " * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n" -s += " * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" -s += " * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" -s += " * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" -s += " * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" -s += " * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" -s += " *\n" -s += " */\n" -s += "// GENERATED: DO NOT EDIT\n" -s += "//\n" -s += "// This file contains base64 encodings of templates used for Swift GRPC code generation.\n" -s += "//\n" -s += "func loadTemplates() -> [String:String] {\n" -s += " var templates : [String:String] = [:]\n" - -let filenames = try FileManager.default.contentsOfDirectory(atPath: TEMPLATES) -for filename in filenames { - if filename.hasSuffix(".swift") { - let fileURL = URL(fileURLWithPath: TEMPLATES + "/" + filename) - let filedata = try Data(contentsOf: fileURL) - let encoding = filedata.base64EncodedString() - s += "\n" - s += " templates[\"" + filename + "\"] = \"" + encoding + "\"\n" - } -} - -s += " return templates\n" -s += "}\n" -print(s) diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Client.swift b/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Client.swift new file mode 100644 index 000000000..fb59b4c63 --- /dev/null +++ b/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Client.swift @@ -0,0 +1,303 @@ +/* + * 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 Foundation +import SwiftProtobuf +import SwiftProtobufPluginLibrary + +extension Generator { + + internal func printClient() { + for method in service.methods { + self.method = method + switch streamingType(method) { + case .unary: + printServiceClientMethodCallUnary() + case .serverStreaming: + printServiceClientMethodCallServerStreaming() + case .clientStreaming: + printServiceClientMethodCallClientStreaming() + case .bidirectionalStreaming: + printServiceClientMethodCallBidiStreaming() + } + } + println() + printServiceClientProtocol() + println() + printServiceClientImplementation() + if options.generateTestStubs { + println() + printServiceClientTestStubs() + } + } + + private func printServiceClientMethodCallUnary() { + println("\(access) protocol \(callName): ClientCallUnary {}") + println() + println("fileprivate final class \(callName)Base: ClientCallUnaryBase<\(methodInputName), \(methodOutputName)>, \(callName) {") + indent() + println("override class var method: String { return \(methodPath) }") + outdent() + println("}") + println() + } + + private func printServiceClientMethodCallServerStreaming() { + println("\(access) protocol \(callName): ClientCallServerStreaming {") + indent() + println("/// Call this to wait for a result. Blocking.") + println("func receive() throws -> \(methodOutputName)") + println("/// Call this to wait for a result. Nonblocking.") + println("func receive(completion: @escaping (\(methodOutputName)?, ClientError?) -> Void) throws") + outdent() + println("}") + println() + println("fileprivate final class \(callName)Base: ClientCallServerStreamingBase<\(methodInputName), \(methodOutputName)>, \(callName) {") + indent() + println("override class var method: String { return \(methodPath) }") + outdent() + println("}") + if options.generateTestStubs { + println() + println("class \(callName)TestStub: ClientCallServerStreamingTestStub<\(methodOutputName)>, \(callName) {") + indent() + println("override class var method: String { return \(methodPath) }") + outdent() + println("}") + } + println() + } + + private func printServiceClientMethodCallClientStreaming() { + println("\(options.visibility.sourceSnippet) protocol \(callName): ClientCallClientStreaming {") + indent() + println("/// Call this to send each message in the request stream. Nonblocking.") + println("func send(_ message: \(methodInputName), completion: @escaping (Error?) -> Void) throws") + println() + println("/// Call this to close the connection and wait for a response. Blocking.") + println("func closeAndReceive() throws -> \(methodOutputName)") + println("/// Call this to close the connection and wait for a response. Nonblocking.") + println("func closeAndReceive(completion: @escaping (\(methodOutputName)?, ClientError?) -> Void) throws") + outdent() + println("}") + println() + println("fileprivate final class \(callName)Base: ClientCallClientStreamingBase<\(methodInputName), \(methodOutputName)>, \(callName) {") + indent() + println("override class var method: String { return \(methodPath) }") + outdent() + println("}") + if options.generateTestStubs { + println() + println("/// Simple fake implementation of \(callName)") + println("/// stores sent values for later verification and finall returns a previously-defined result.") + println("class \(callName)TestStub: ClientCallClientStreamingTestStub<\(methodInputName), \(methodOutputName)>, \(callName) {") + indent() + println("override class var method: String { return \(methodPath) }") + outdent() + println("}") + } + println() + } + + private func printServiceClientMethodCallBidiStreaming() { + println("\(access) protocol \(callName): ClientCallBidirectionalStreaming {") + indent() + println("/// Call this to wait for a result. Blocking.") + println("func receive() throws -> \(methodOutputName)") + println("/// Call this to wait for a result. Nonblocking.") + println("func receive(completion: @escaping (\(methodOutputName)?, ClientError?) -> Void) throws") + println() + println("/// Call this to send each message in the request stream.") + println("func send(_ message: \(methodInputName), completion: @escaping (Error?) -> Void) throws") + println() + println("/// Call this to close the sending connection. Blocking.") + println("func closeSend() throws") + println("/// Call this to close the sending connection. Nonblocking.") + println("func closeSend(completion: (() -> Void)?) throws") + outdent() + println("}") + println() + println("fileprivate final class \(callName)Base: ClientCallBidirectionalStreamingBase<\(methodInputName), \(methodOutputName)>, \(callName) {") + indent() + println("override class var method: String { return \(methodPath) }") + outdent() + println("}") + if options.generateTestStubs { + println() + println("class \(callName)TestStub: ClientCallBidirectionalStreamingTestStub<\(methodInputName), \(methodOutputName)>, \(callName) {") + indent() + println("override class var method: String { return \(methodPath) }") + outdent() + println("}") + } + println() + } + + private func printServiceClientProtocol() { + println("/// Instantiate \(serviceClassName)Client, then call methods of this protocol to make API calls.") + println("\(options.visibility.sourceSnippet) protocol \(serviceClassName): ServiceClient {") + indent() + for method in service.methods { + self.method = method + switch streamingType(method) { + case .unary: + println("/// Synchronous. Unary.") + println("func \(methodFunctionName)(_ request: \(methodInputName)) throws -> \(methodOutputName)") + println("/// Asynchronous. Unary.") + println("func \(methodFunctionName)(_ request: \(methodInputName), completion: @escaping (\(methodOutputName)?, CallResult) -> Void) throws -> \(callName)") + case .serverStreaming: + println("/// Asynchronous. Server-streaming.") + println("/// Send the initial message.") + println("/// Use methods on the returned object to get streamed responses.") + println("func \(methodFunctionName)(_ request: \(methodInputName), completion: ((CallResult) -> Void)?) throws -> \(callName)") + case .clientStreaming: + println("/// Asynchronous. Client-streaming.") + println("/// Use methods on the returned object to stream messages and") + println("/// to close the connection and wait for a final response.") + println("func \(methodFunctionName)(completion: ((CallResult) -> Void)?) throws -> \(callName)") + case .bidirectionalStreaming: + println("/// Asynchronous. Bidirectional-streaming.") + println("/// Use methods on the returned object to stream messages,") + println("/// to wait for replies, and to close the connection.") + println("func \(methodFunctionName)(completion: ((CallResult) -> Void)?) throws -> \(callName)") + } + println() + } + outdent() + println("}") + } + + private func printServiceClientImplementation() { + println("\(access) final class \(serviceClassName)Client: ServiceClientBase, \(serviceClassName) {") + indent() + for method in service.methods { + self.method = method + switch streamingType(method) { + case .unary: + println("/// Synchronous. Unary.") + println("\(access) func \(methodFunctionName)(_ request: \(methodInputName)) throws -> \(methodOutputName) {") + indent() + println("return try \(callName)Base(channel)") + indent() + println(".run(request: request, metadata: metadata)") + outdent() + outdent() + println("}") + println("/// Asynchronous. Unary.") + println("\(access) func \(methodFunctionName)(_ request: \(methodInputName), completion: @escaping (\(methodOutputName)?, CallResult) -> Void) throws -> \(callName) {") + indent() + println("return try \(callName)Base(channel)") + indent() + println(".start(request: request, metadata: metadata, completion: completion)") + outdent() + outdent() + println("}") + case .serverStreaming: + println("/// Asynchronous. Server-streaming.") + println("/// Send the initial message.") + println("/// Use methods on the returned object to get streamed responses.") + println("\(access) func \(methodFunctionName)(_ request: \(methodInputName), completion: ((CallResult) -> Void)?) throws -> \(callName) {") + indent() + println("return try \(callName)Base(channel)") + indent() + println(".start(request: request, metadata: metadata, completion: completion)") + outdent() + outdent() + println("}") + case .clientStreaming: + println("/// Asynchronous. Client-streaming.") + println("/// Use methods on the returned object to stream messages and") + println("/// to close the connection and wait for a final response.") + println("\(access) func \(methodFunctionName)(completion: ((CallResult) -> Void)?) throws -> \(callName) {") + indent() + println("return try \(callName)Base(channel)") + indent() + println(".start(metadata: metadata, completion: completion)") + outdent() + outdent() + println("}") + case .bidirectionalStreaming: + println("/// Asynchronous. Bidirectional-streaming.") + println("/// Use methods on the returned object to stream messages,") + println("/// to wait for replies, and to close the connection.") + println("\(access) func \(methodFunctionName)(completion: ((CallResult) -> Void)?) throws -> \(callName) {") + indent() + println("return try \(callName)Base(channel)") + indent() + println(".start(metadata: metadata, completion: completion)") + outdent() + outdent() + println("}") + } + println() + } + outdent() + println("}") + } + + private func printServiceClientTestStubs() { + println("class \(serviceClassName)TestStub: ServiceClientTestStubBase, \(serviceClassName) {") + indent() + for method in service.methods { + self.method = method + switch streamingType(method) { + case .unary: + println("var \(methodFunctionName)Requests: [\(methodInputName)] = []") + println("var \(methodFunctionName)Responses: [\(methodOutputName)] = []") + println("func \(methodFunctionName)(_ request: \(methodInputName)) throws -> \(methodOutputName) {") + indent() + println("\(methodFunctionName)Requests.append(request)") + println("defer { \(methodFunctionName)Responses.removeFirst() }") + println("return \(methodFunctionName)Responses.first!") + outdent() + println("}") + println("func \(methodFunctionName)(_ request: \(methodInputName), completion: @escaping (\(methodOutputName)?, CallResult) -> Void) throws -> \(callName) {") + indent() + println("fatalError(\"not implemented\")") + outdent() + println("}") + case .serverStreaming: + println("var \(methodFunctionName)Requests: [\(methodInputName)] = []") + println("var \(methodFunctionName)Calls: [\(callName)] = []") + println("func \(methodFunctionName)(_ request: \(methodInputName), completion: ((CallResult) -> Void)?) throws -> \(callName) {") + indent() + println("\(methodFunctionName)Requests.append(request)") + println("defer { \(methodFunctionName)Calls.removeFirst() }") + println("return \(methodFunctionName)Calls.first!") + outdent() + println("}") + case .clientStreaming: + println("var \(methodFunctionName)Calls: [\(callName)] = []") + println("func \(methodFunctionName)(completion: ((CallResult) -> Void)?) throws -> \(callName) {") + indent() + println("defer { \(methodFunctionName)Calls.removeFirst() }") + println("return \(methodFunctionName)Calls.first!") + outdent() + println("}") + case .bidirectionalStreaming: + println("var \(methodFunctionName)Calls: [\(callName)] = []") + println("func \(methodFunctionName)(completion: ((CallResult) -> Void)?) throws -> \(callName) {") + indent() + println("defer { \(methodFunctionName)Calls.removeFirst() }") + println("return \(methodFunctionName)Calls.first!") + outdent() + println("}") + } + println() + } + outdent() + println("}") + } +} diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Names.swift b/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Names.swift new file mode 100644 index 000000000..33c714ad6 --- /dev/null +++ b/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Names.swift @@ -0,0 +1,85 @@ +/* + * 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 Foundation +import SwiftProtobuf +import SwiftProtobufPluginLibrary + +// Transform .some.package_name.FooBarRequest -> Some_PackageName_FooBarRequest +internal func protoMessageName(_ descriptor: SwiftProtobufPluginLibrary.Descriptor) -> String { + return SwiftProtobufNamer().fullName(message: descriptor) +} + +internal func nameForPackageService(_ file: FileDescriptor, + _ service: ServiceDescriptor) -> String { + if !file.package.isEmpty { + return SwiftProtobufNamer().typePrefix(forFile:file) + service.name + } else { + return service.name + } +} + +internal func nameForPackageServiceMethod(_ file: FileDescriptor, + _ service: ServiceDescriptor, + _ method: MethodDescriptor) -> String { + return nameForPackageService(file, service) + method.name +} + +extension Generator { + + internal var access : String { + return options.visibility.sourceSnippet + } + + internal var serviceClassName: String { + return nameForPackageService(file, service) + "Service" + } + + internal var providerName: String { + return nameForPackageService(file, service) + "Provider" + } + + internal var serverName: String { + return nameForPackageService(file, service) + "Server" + } + + internal var callName: String { + return nameForPackageServiceMethod(file, service, method) + "Call" + } + + internal var methodFunctionName: String { + return method.name.lowercased() + } + + internal var methodSessionName: String { + return nameForPackageServiceMethod(file, service, method) + "Session" + } + + internal var methodInputName: String { + return protoMessageName(method.inputType) + } + + internal var methodOutputName: String { + return protoMessageName(method.outputType) + } + + internal var methodPath: String { + if !file.package.isEmpty { + return "\"/" + file.package + "." + service.name + "/" + method.name + "\"" + } else { + return "\"/" + service.name + "/" + method.name + "\"" + } + } +} diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Server.swift b/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Server.swift new file mode 100644 index 000000000..6907f81fa --- /dev/null +++ b/Plugin/Sources/protoc-gen-swiftgrpc/Generator-Server.swift @@ -0,0 +1,191 @@ +/* + * 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 Foundation +import SwiftProtobuf +import SwiftProtobufPluginLibrary + +extension Generator { + + internal func printServer() { + printServerProtocol() + for method in service.methods { + self.method = method + switch streamingType(method) { + case .unary: + printServerMethodUnary() + case .clientStreaming: + printServerMethodClientStreaming() + case .serverStreaming: + printServerMethodServerStreaming() + case .bidirectionalStreaming: + printServerMethodBidirectional() + } + println() + } + println() + printServerMainClass() + } + + private func printServerProtocol() { + println("/// To build a server, implement a class that conforms to this protocol.") + println("\(access) protocol \(providerName) {") + indent() + for method in service.methods { + self.method = method + switch streamingType(method) { + case .unary: + println("func \(methodFunctionName)(request: \(methodInputName), session: \(methodSessionName)) throws -> \(methodOutputName)") + case .serverStreaming: + println("func \(methodFunctionName)(request: \(methodInputName), session: \(methodSessionName)) throws") + case .clientStreaming: + println("func \(methodFunctionName)(session: \(methodSessionName)) throws") + case .bidirectionalStreaming: + println("func \(methodFunctionName)(session: \(methodSessionName)) throws") + } + } + outdent() + println("}") + println() + } + + private func printServerMainClass() { + println("/// Main server for generated service") + println("\(access) final class \(serverName): ServiceServer {") + indent() + println("private let provider: \(providerName)") + println() + println("\(access) init(address: String, provider: \(providerName)) {") + indent() + println("self.provider = provider") + println("super.init(address: address)") + outdent() + println("}") + println() + println("\(access) init?(address: String, certificateURL: URL, keyURL: URL, provider: \(providerName)) {") + indent() + println("self.provider = provider") + println("super.init(address: address, certificateURL: certificateURL, keyURL: keyURL)") + outdent() + println("}") + println() + println("/// Start the server.") + println("\(access) override func handleMethod(_ method: String, handler: Handler, queue: DispatchQueue) throws -> Bool {") + indent() + println("let provider = self.provider") + println("switch method {") + for method in service.methods { + self.method = method + println("case \(methodPath):") + indent() + switch streamingType(method) { + case .unary, .serverStreaming: + println("try \(methodSessionName)Base(") + indent() + println("handler: handler,") + println("providerBlock: { try provider.\(methodFunctionName)(request: $0, session: $1 as! \(methodSessionName)Base) })") + indent() + println(".run(queue: queue)") + outdent() + outdent() + default: + println("try \(methodSessionName)Base(") + indent() + println("handler: handler,") + println("providerBlock: { try provider.\(methodFunctionName)(session: $0 as! \(methodSessionName)Base) })") + indent() + println(".run(queue: queue)") + outdent() + outdent() + } + println("return true") + outdent() + } + println("default:") + indent() + println("return false") + outdent() + println("}") + outdent() + println("}") + outdent() + println("}") + println() + } + + private func printServerMethodUnary() { + println("\(access) protocol \(methodSessionName): ServerSessionUnary {}") + println() + println("fileprivate final class \(methodSessionName)Base: ServerSessionUnaryBase<\(methodInputName), \(methodOutputName)>, \(methodSessionName) {}") + if options.generateTestStubs { + println() + println("class \(methodSessionName)TestStub: ServerSessionUnaryTestStub, \(methodSessionName) {}") + } + } + + private func printServerMethodClientStreaming() { + println("\(access) protocol \(methodSessionName): ServerSessionClientStreaming {") + indent() + println("/// Receive a message. Blocks until a message is received or the client closes the connection.") + println("func receive() throws -> \(methodInputName)") + println() + println("/// Send a response and close the connection.") + println("func sendAndClose(_ response: \(methodOutputName)) throws") + outdent() + println("}") + println() + println("fileprivate final class \(methodSessionName)Base: ServerSessionClientStreamingBase<\(methodInputName), \(methodOutputName)>, \(methodSessionName) {}") + if options.generateTestStubs { + println() + println("class \(methodSessionName)TestStub: ServerSessionClientStreamingTestStub<\(methodInputName), \(methodOutputName)>, \(methodSessionName) {}") + } + } + + private func printServerMethodServerStreaming() { + println("\(access) protocol \(methodSessionName): ServerSessionServerStreaming {") + indent() + println("/// Send a message. Nonblocking.") + println("func send(_ response: \(methodOutputName), completion: ((Bool) -> Void)?) throws") + outdent() + println("}") + println() + println("fileprivate final class \(methodSessionName)Base: ServerSessionServerStreamingBase<\(methodInputName), \(methodOutputName)>, \(methodSessionName) {}") + if options.generateTestStubs { + println() + println("class \(methodSessionName)TestStub: ServerSessionServerStreamingTestStub<\(methodOutputName)>, \(methodSessionName) {}") + } + } + + private func printServerMethodBidirectional() { + println("\(access) protocol \(methodSessionName): ServerSessionBidirectionalStreaming {") + indent() + println("/// Receive a message. Blocks until a message is received or the client closes the connection.") + println("func receive() throws -> \(methodInputName)") + println() + println("/// Send a message. Nonblocking.") + println("func send(_ response: \(methodOutputName), completion: ((Bool) -> Void)?) throws") + println() + println("/// Close a connection. Blocks until the connection is closed.") + println("func close() throws") + outdent() + println("}") + println() + println("fileprivate final class \(methodSessionName)Base: ServerSessionBidirectionalStreamingBase<\(methodInputName), \(methodOutputName)>, \(methodSessionName) {}") + if options.generateTestStubs { + println() + println("class \(methodSessionName)TestStub: ServerSessionBidirectionalStreamingTestStub<\(methodInputName), \(methodOutputName)>, \(methodSessionName) {}") + } + } +} diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/Generator.swift b/Plugin/Sources/protoc-gen-swiftgrpc/Generator.swift new file mode 100644 index 000000000..6897f7ca0 --- /dev/null +++ b/Plugin/Sources/protoc-gen-swiftgrpc/Generator.swift @@ -0,0 +1,96 @@ +/* + * 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 SwiftProtobufPluginLibrary + +class Generator { + internal var options: GeneratorOptions + private var printer: CodePrinter + + internal var file: FileDescriptor + internal var service: ServiceDescriptor! // context during generation + internal var method: MethodDescriptor! // context during generation + + init(_ file:FileDescriptor, options:GeneratorOptions) { + self.file = file + self.options = options + self.printer = CodePrinter() + printMain() + } + + public var code: String { + return printer.content + } + + internal func println(_ text: String = "") { + printer.print(text) + printer.print("\n") + } + + internal func indent() { + printer.indent() + } + + internal func outdent() { + printer.outdent() + } + + private func printMain() { + printer.print(""" + // + // DO NOT EDIT. + // + // Generated by the protocol buffer compiler. + // Source: \(file.name) + // + + // + // 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. + //\n + """) + + for moduleName in ["Foundation", "Dispatch", "gRPC", "SwiftProtobuf"] { + println("import \(moduleName)") + } + println() + + if options.generateClient { + for service in file.services { + self.service = service + printClient() + } + } + println() + + if options.generateServer { + for service in file.services { + self.service = service + printServer() + } + } + } +} diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/InternalLoader.swift b/Plugin/Sources/protoc-gen-swiftgrpc/InternalLoader.swift deleted file mode 100644 index 7dd496a81..000000000 --- a/Plugin/Sources/protoc-gen-swiftgrpc/InternalLoader.swift +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2017, 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 Stencil -import Foundation - -// A class for loading Stencil templates from compiled-in representations - -public class InternalLoader: Loader { - private var templates: [String: String] - - public init() { - templates = loadTemplates() - } - - public func loadTemplate(name: String, environment: Environment) throws -> Template { - if let encoding = templates[name], - let data = Data(base64Encoded: encoding, options: []), - let template = String(data: data, encoding: .utf8) { - return environment.templateClass.init(templateString: template, - environment: environment, - name: name) - } else { - throw TemplateDoesNotExist(templateNames: [name], loader: self) - } - } - - public func loadTemplate(names: [String], environment: Environment) throws -> Template { - for name in names { - if let encoding = templates[name], - let data = Data(base64Encoded: encoding, options: []), - let template = String(data: data, encoding: .utf8) { - return environment.templateClass.init(templateString: template, - environment: environment, - name: name) - } - } - throw TemplateDoesNotExist(templateNames: names, loader: self) - } -} diff --git a/Plugin/Templates/main.swift b/Plugin/Sources/protoc-gen-swiftgrpc/StreamingType.swift similarity index 54% rename from Plugin/Templates/main.swift rename to Plugin/Sources/protoc-gen-swiftgrpc/StreamingType.swift index 4eb43d17a..6ab3160d3 100644 --- a/Plugin/Templates/main.swift +++ b/Plugin/Sources/protoc-gen-swiftgrpc/StreamingType.swift @@ -1,11 +1,3 @@ -/* - * DO NOT EDIT. - * - * Generated by the protocol buffer compiler. - * Source: {{ file|fileDescriptorName }} - * - */ - /* * Copyright 2018, gRPC Authors All rights reserved. * @@ -21,13 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Foundation -import Dispatch -import gRPC -import SwiftProtobuf -//-{% if client %} -//-{% include "client.swift" %} -//-{% endif %} -//-{% if server %} -//-{% include "server.swift" %} -//-{% endif %} +import SwiftProtobufPluginLibrary + +internal enum StreamingType { + case unary + case clientStreaming + case serverStreaming + case bidirectionalStreaming +} + +internal func streamingType(_ method: MethodDescriptor) -> StreamingType { + if method.proto.clientStreaming { + if method.proto.serverStreaming { + return .bidirectionalStreaming + } else { + return .clientStreaming + } + } else { + if method.proto.serverStreaming { + return .serverStreaming + } else { + return .unary + } + } +} diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/filters.swift b/Plugin/Sources/protoc-gen-swiftgrpc/filters.swift deleted file mode 100644 index f4b24e807..000000000 --- a/Plugin/Sources/protoc-gen-swiftgrpc/filters.swift +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2017, 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 Foundation -import PathKit -import Stencil -import SwiftProtobuf -import SwiftProtobufPluginLibrary - -let namer = SwiftProtobufNamer() - -// internal helpers -extension String { - var undotted: String { - return replacingOccurrences(of: ".", with: "_") - } - - var uppercasedFirst: String { - var out = Substring(self) - if let first = out.popFirst() { - return String(first).uppercased() + String(out) - } else { - return self - } - } -} - -// error-generating helpers - -func invalidArgumentCount(filter: String, expected: Int) -> TemplateSyntaxError { - return TemplateSyntaxError("\(filter): expects \(expected) arguments") -} - -func invalidArgument(filter: String, value: Any?) -> TemplateSyntaxError { - return TemplateSyntaxError("\(filter): invalid argument \(String(describing: value))") -} - -func invalidArgumentType(filter: String, required: String, received: Any?) -> TemplateSyntaxError { - return TemplateSyntaxError("\(filter): invalid argument type: required \(required) received \(String(describing: received))") -} - -// functions for use in templates - -// Transform .some.package_name.FooBarRequest -> Some_PackageName_FooBarRequest -func protoMessageName(_ descriptor: SwiftProtobufPluginLibrary.Descriptor) -> String { - return namer.fullName(message: descriptor) -} - -func pathName(arguments: [Any?]) throws -> String { - if arguments.count != 3 { - throw invalidArgumentCount(filter: "path", expected: 3) - } - guard let protoFile = arguments[0] as? SwiftProtobufPluginLibrary.FileDescriptor - else { - throw invalidArgumentType(filter: "path", required: "FileDescriptor", received: arguments[0]) - } - guard let service = arguments[1] as? SwiftProtobufPluginLibrary.ServiceDescriptor - else { - throw invalidArgumentType(filter: "path", required: "ServiceDescriptor", received: arguments[1]) - } - guard let method = arguments[2] as? SwiftProtobufPluginLibrary.MethodDescriptor - else { - throw invalidArgumentType(filter: "path", required: "MethodDescriptor", received: arguments[2]) - } - if !protoFile.package.isEmpty { - return "/" + protoFile.package + "." + service.name + "/" + method.name - } else { - return "/" + service.name + "/" + method.name - } -} - -func packageServiceMethodName(filter: String, arguments: [Any?]) throws -> String { - if arguments.count != 3 { - throw invalidArgumentCount(filter: "packageServiceMethodName", expected: 3) - } - guard let protoFile = arguments[0] as? SwiftProtobufPluginLibrary.FileDescriptor - else { - throw invalidArgumentType(filter: filter, required: "FileDescriptor", received: arguments[0]) - } - guard let service = arguments[1] as? SwiftProtobufPluginLibrary.ServiceDescriptor - else { - throw invalidArgumentType(filter: filter, required: "ServiceDescriptor", received: arguments[0]) - } - guard let method = arguments[2] as? SwiftProtobufPluginLibrary.MethodDescriptor - else { - throw invalidArgumentType(filter: filter, required: "MethodDescriptor", received: arguments[0]) - } - if !protoFile.package.isEmpty { - return protoFile.package.capitalized.undotted + "_" + service.name + method.name - } else { - return service.name + method.name - } -} - -func packageServiceName(filter: String, arguments: [Any?]) throws -> String { - if arguments.count != 2 { - throw invalidArgumentCount(filter: "packageServiceName", expected: 2) - } - guard let protoFile = arguments[0] as? SwiftProtobufPluginLibrary.FileDescriptor - else { - throw invalidArgumentType(filter: filter, required: "FileDescriptor", received: arguments[0]) - } - guard let service = arguments[1] as? SwiftProtobufPluginLibrary.ServiceDescriptor - else { - throw invalidArgumentType(filter: filter, required: "ServiceDescriptor", received: arguments[0]) - } - if !protoFile.package.isEmpty { - return protoFile.package.capitalized.undotted + "_" + service.name - } else { - return service.name - } -} - -class GRPCFilterExtension: Extension { - override init() { - super.init() - // initialize template engine and add custom filters - let ext = self - ext.registerFilter("call") { (_, arguments: [Any?]) in - return try packageServiceMethodName(filter: "call", arguments: arguments) + "Call" - } - ext.registerFilter("session") { (_, arguments: [Any?]) in - return try packageServiceMethodName(filter: "session", arguments: arguments) + "Session" - } - ext.registerFilter("path") { (_, arguments: [Any?]) in - return try pathName(arguments: arguments) - } - ext.registerFilter("provider") { (_, arguments: [Any?]) in - return try packageServiceName(filter: "provider", arguments: arguments) + "Provider" - } - ext.registerFilter("clienterror") { (_, arguments: [Any?]) in - return try packageServiceName(filter: "clienterror", arguments: arguments) + "ClientError" - } - ext.registerFilter("serviceclass") { (_, arguments: [Any?]) in - return try packageServiceName(filter: "serviceclass", arguments: arguments) + "Service" - } - ext.registerFilter("servererror") { (_, arguments: [Any?]) in - return try packageServiceName(filter: "servererror", arguments: arguments) + "ServerError" - } - ext.registerFilter("server") { (_, arguments: [Any?]) in - return try packageServiceName(filter: "server", arguments: arguments) + "Server" - } - ext.registerFilter("service") { (_, arguments: [Any?]) in - return try packageServiceName(filter: "server", arguments: arguments) - } - ext.registerFilter("input") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.MethodDescriptor { - return protoMessageName(value.inputType) - } - throw invalidArgumentType(filter: "input", required: "MethodDescriptor", received: value) - } - ext.registerFilter("output") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.MethodDescriptor { - return protoMessageName(value.outputType) - } - throw invalidArgumentType(filter: "output", required: "MethodDescriptor", received: value) - } - ext.registerFilter("fileDescriptorName") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.FileDescriptor { - return value.name - } - throw invalidArgumentType(filter: "fileDescriptorName", required: "FileDescriptor", received: value) - } - ext.registerFilter("methodDescriptorName") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.MethodDescriptor { - return value.name - } - throw invalidArgumentType(filter: "methodDescriptorName", required: "MethodDescriptor", received: value) - } - ext.registerFilter("methodIsUnary") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.MethodDescriptor { - return !value.proto.clientStreaming && !value.proto.serverStreaming - } - throw invalidArgumentType(filter: "methodIsUnary", required: "MethodDescriptor", received: value) - } - ext.registerFilter("methodIsServerStreaming") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.MethodDescriptor { - return !value.proto.clientStreaming && value.proto.serverStreaming - } - throw invalidArgumentType(filter: "methodIsServerStreaming", required: "MethodDescriptor", received: value) - } - ext.registerFilter("methodIsClientStreaming") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.MethodDescriptor { - return value.proto.clientStreaming && !value.proto.serverStreaming - } - throw invalidArgumentType(filter: "methodIsClientStreaming", required: "MethodDescriptor", received: value) - } - ext.registerFilter("methodIsBidiStreaming") { (value: Any?) in - if let value = value as? SwiftProtobufPluginLibrary.MethodDescriptor { - return value.proto.clientStreaming && value.proto.serverStreaming - } - throw invalidArgumentType(filter: "methodIsBidiStreaming", required: "MethodDescriptor", received: value) - } - } -} diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/main.swift b/Plugin/Sources/protoc-gen-swiftgrpc/main.swift index 22235d2a7..120135efb 100644 --- a/Plugin/Sources/protoc-gen-swiftgrpc/main.swift +++ b/Plugin/Sources/protoc-gen-swiftgrpc/main.swift @@ -14,8 +14,6 @@ * limitations under the License. */ import Foundation -import PathKit -import Stencil import SwiftProtobuf import SwiftProtobufPluginLibrary @@ -23,26 +21,6 @@ func Log(_ message: String) { FileHandle.standardError.write((message + "\n").data(using: .utf8)!) } -// Code templates use "//-" prefixes to comment-out template operators -// to keep them from interfering with Swift code formatting tools. -// Use this to remove them after templates have been expanded. -func stripMarkers(_ code: String) -> String { - let inputLines = code.components(separatedBy: "\n") - - var outputLines: [String] = [] - for line in inputLines { - if line.contains("//-") { - let removed = line.replacingOccurrences(of: "//-", with: "") - if removed.trimmingCharacters(in: CharacterSet.whitespaces) != "" { - outputLines.append(removed) - } - } else { - outputLines.append(line) - } - } - return outputLines.joined(separator: "\n") -} - // from apple/swift-protobuf/Sources/protoc-gen-swift/StringUtils.swift func splitPath(pathname: String) -> (dir: String, base: String, suffix: String) { var dir = "" @@ -114,9 +92,6 @@ func uniqueOutputFileName(component: String, fileDescriptor: FileDescriptor) -> } func main() throws { - // initialize template engine and add custom filters - let templateEnvironment = Environment(loader: InternalLoader(), - extensions: [GRPCFilterExtension()]) // initialize responses var response = Google_Protobuf_Compiler_CodeGeneratorResponse() @@ -133,29 +108,12 @@ func main() throws { // process each .proto file separately for fileDescriptor in descriptorSet.files { if fileDescriptor.services.count > 0 { - // a package declaration is required for file containing service(s) - let package = fileDescriptor.package - - // generate separate implementation files for client and server - let context: [String: Any] = [ - "file": fileDescriptor, - "client": true, - "server": true, - "access": options.visibility.sourceSnippet, - "generateTestStubs": options.generateTestStubs - ] - - do { - let grpcFileName = uniqueOutputFileName(component: "grpc", fileDescriptor: fileDescriptor) - let grpcCode = try templateEnvironment.renderTemplate(name: "main.swift", context: context) - var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File() - grpcFile.name = grpcFileName - grpcFile.content = stripMarkers(grpcCode) - response.file.append(grpcFile) - - } catch (let error) { - Log("ERROR \(error)") - } + let grpcFileName = uniqueOutputFileName(component: "grpc", fileDescriptor: fileDescriptor) + let grpcGenerator = Generator(fileDescriptor, options: options) + var grpcFile = Google_Protobuf_Compiler_CodeGeneratorResponse.File() + grpcFile.name = grpcFileName + grpcFile.content = grpcGenerator.code + response.file.append(grpcFile) } } diff --git a/Plugin/Sources/protoc-gen-swiftgrpc/options.swift b/Plugin/Sources/protoc-gen-swiftgrpc/options.swift index 152dfd749..e515f1a21 100644 --- a/Plugin/Sources/protoc-gen-swiftgrpc/options.swift +++ b/Plugin/Sources/protoc-gen-swiftgrpc/options.swift @@ -48,9 +48,12 @@ class GeneratorOptions { let visibility: Visibility let generateTestStubs: Bool + let generateClient: Bool + let generateServer: Bool init(parameter: String?) throws { var visibility: Visibility = .Internal + var generateTestStubs = false for pair in GeneratorOptions.parseParameter(string: parameter) { @@ -69,6 +72,7 @@ class GeneratorOptions { default: throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) } + default: throw GenerationError.unknownParameter(name: pair.key) } @@ -76,6 +80,8 @@ class GeneratorOptions { self.visibility = visibility self.generateTestStubs = generateTestStubs + self.generateClient = true + self.generateServer = true } static func parseParameter(string: String?) -> [(key: String, value: String)] { diff --git a/Plugin/Templates/client-call-bidistreaming.swift b/Plugin/Templates/client-call-bidistreaming.swift deleted file mode 100644 index 4d74be685..000000000 --- a/Plugin/Templates/client-call-bidistreaming.swift +++ /dev/null @@ -1,24 +0,0 @@ -{{ access }} protocol {{ .|call:file,service,method }}: ClientCallBidirectionalStreaming { - /// Call this to wait for a result. Blocking. - func receive() throws -> {{ method|output }} - /// Call this to wait for a result. Nonblocking. - func receive(completion: @escaping ({{ method|output }}?, ClientError?) -> Void) throws - - /// Call this to send each message in the request stream. - func send(_ message: {{ method|input }}, completion: @escaping (Error?) -> Void) throws - - /// Call this to close the sending connection. Blocking. - func closeSend() throws - /// Call this to close the sending connection. Nonblocking. - func closeSend(completion: (() -> Void)?) throws -} - -fileprivate final class {{ .|call:file,service,method }}Base: ClientCallBidirectionalStreamingBase<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} { - override class var method: String { return "{{ .|path:file,service,method }}" } -} - -//-{% if generateTestStubs %} -class {{ .|call:file,service,method }}TestStub: ClientCallBidirectionalStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} { - override class var method: String { return "{{ .|path:file,service,method }}" } -} -//-{% endif %} diff --git a/Plugin/Templates/client-call-clientstreaming.swift b/Plugin/Templates/client-call-clientstreaming.swift deleted file mode 100644 index 4de7b760e..000000000 --- a/Plugin/Templates/client-call-clientstreaming.swift +++ /dev/null @@ -1,21 +0,0 @@ -{{ access }} protocol {{ .|call:file,service,method }}: ClientCallClientStreaming { - /// Call this to send each message in the request stream. Nonblocking. - func send(_ message: {{ method|input }}, completion: @escaping (Error?) -> Void) throws - - /// Call this to close the connection and wait for a response. Blocking. - func closeAndReceive() throws -> {{ method|output }} - /// Call this to close the connection and wait for a response. Nonblocking. - func closeAndReceive(completion: @escaping ({{ method|output }}?, ClientError?) -> Void) throws -} - -fileprivate final class {{ .|call:file,service,method }}Base: ClientCallClientStreamingBase<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} { - override class var method: String { return "{{ .|path:file,service,method }}" } -} - -//-{% if generateTestStubs %} -/// Simple fake implementation of {{ .|call:file,service,method }} -/// stores sent values for later verification and finall returns a previously-defined result. -class {{ .|call:file,service,method }}TestStub: ClientCallClientStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} { - override class var method: String { return "{{ .|path:file,service,method }}" } -} -//-{% endif %} diff --git a/Plugin/Templates/client-call-serverstreaming.swift b/Plugin/Templates/client-call-serverstreaming.swift deleted file mode 100644 index 88104298e..000000000 --- a/Plugin/Templates/client-call-serverstreaming.swift +++ /dev/null @@ -1,16 +0,0 @@ -{{ access }} protocol {{ .|call:file,service,method }}: ClientCallServerStreaming { - /// Call this to wait for a result. Blocking. - func receive() throws -> {{ method|output }} - /// Call this to wait for a result. Nonblocking. - func receive(completion: @escaping ({{ method|output }}?, ClientError?) -> Void) throws -} - -fileprivate final class {{ .|call:file,service,method }}Base: ClientCallServerStreamingBase<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} { - override class var method: String { return "{{ .|path:file,service,method }}" } -} - -//-{% if generateTestStubs %} -class {{ .|call:file,service,method }}TestStub: ClientCallServerStreamingTestStub<{{ method|output }}>, {{ .|call:file,service,method }} { - override class var method: String { return "{{ .|path:file,service,method }}" } -} -//-{% endif %} diff --git a/Plugin/Templates/client-call-unary.swift b/Plugin/Templates/client-call-unary.swift deleted file mode 100644 index cc8eef12a..000000000 --- a/Plugin/Templates/client-call-unary.swift +++ /dev/null @@ -1,5 +0,0 @@ -{{ access }} protocol {{ .|call:file,service,method }}: ClientCallUnary {} - -fileprivate final class {{ .|call:file,service,method }}Base: ClientCallUnaryBase<{{ method|input }}, {{ method|output }}>, {{ .|call:file,service,method }} { - override class var method: String { return "{{ .|path:file,service,method }}" } -} diff --git a/Plugin/Templates/client.swift b/Plugin/Templates/client.swift deleted file mode 100644 index 9ce134105..000000000 --- a/Plugin/Templates/client.swift +++ /dev/null @@ -1,137 +0,0 @@ -//-{% for service in file.services %} - -//-{% for method in service.methods %} -//-{% if method|methodIsUnary %} -//-{% include "client-call-unary.swift" %} -//-{% endif %} -//-{% if method|methodIsServerStreaming %} -//-{% include "client-call-serverstreaming.swift" %} -//-{% endif %} -//-{% if method|methodIsClientStreaming %} -//-{% include "client-call-clientstreaming.swift" %} -//-{% endif %} -//-{% if method|methodIsBidiStreaming %} -//-{% include "client-call-bidistreaming.swift" %} -//-{% endif %} -//-{% endfor %} - -/// Instantiate {{ .|serviceclass:file,service }}Client, then call methods of this protocol to make API calls. -{{ access }} protocol {{ .|serviceclass:file,service }}: ServiceClient { - //-{% for method in service.methods %} - //-{% if method|methodIsUnary %} - /// Synchronous. Unary. - func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}) throws -> {{ method|output }} - /// Asynchronous. Unary. - func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}, completion: @escaping ({{ method|output }}?, CallResult) -> Void) throws -> {{ .|call:file,service,method }} - //-{% endif %} - //-{% if method|methodIsServerStreaming %} - /// Asynchronous. Server-streaming. - /// Send the initial message. - /// Use methods on the returned object to get streamed responses. - func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}, completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} - //-{% endif %} - //-{% if method|methodIsClientStreaming %} - /// Asynchronous. Client-streaming. - /// Use methods on the returned object to stream messages and - /// to close the connection and wait for a final response. - func {{ method|methodDescriptorName|lowercase }}(completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} - //-{% endif %} - //-{% if method|methodIsBidiStreaming %} - /// Asynchronous. Bidirectional-streaming. - /// Use methods on the returned object to stream messages, - /// to wait for replies, and to close the connection. - func {{ method|methodDescriptorName|lowercase }}(completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} - //-{% endif %} - - //-{% endfor %} -} - -{{ access }} final class {{ .|serviceclass:file,service }}Client: ServiceClientBase, {{ .|serviceclass:file,service }} { - //-{% for method in service.methods %} - //-{% if method|methodIsUnary %} - /// Synchronous. Unary. - {{ access }} func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}) throws -> {{ method|output }} { - return try {{ .|call:file,service,method }}Base(channel) - .run(request: request, metadata: metadata) - } - /// Asynchronous. Unary. - {{ access }} func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}, completion: @escaping ({{ method|output }}?, CallResult) -> Void) throws -> {{ .|call:file,service,method }} { - return try {{ .|call:file,service,method }}Base(channel) - .start(request: request, metadata: metadata, completion: completion) - } - //-{% endif %} - //-{% if method|methodIsServerStreaming %} - /// Asynchronous. Server-streaming. - /// Send the initial message. - /// Use methods on the returned object to get streamed responses. - {{ access }} func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}, completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} { - return try {{ .|call:file,service,method }}Base(channel) - .start(request: request, metadata: metadata, completion: completion) - } - //-{% endif %} - //-{% if method|methodIsClientStreaming %} - /// Asynchronous. Client-streaming. - /// Use methods on the returned object to stream messages and - /// to close the connection and wait for a final response. - {{ access }} func {{ method|methodDescriptorName|lowercase }}(completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} { - return try {{ .|call:file,service,method }}Base(channel) - .start(metadata: metadata, completion: completion) - } - //-{% endif %} - //-{% if method|methodIsBidiStreaming %} - /// Asynchronous. Bidirectional-streaming. - /// Use methods on the returned object to stream messages, - /// to wait for replies, and to close the connection. - {{ access }} func {{ method|methodDescriptorName|lowercase }}(completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} { - return try {{ .|call:file,service,method }}Base(channel) - .start(metadata: metadata, completion: completion) - } - //-{% endif %} - - //-{% endfor %} -} - -//-{% if generateTestStubs %} -class {{ .|serviceclass:file,service }}TestStub: ServiceClientTestStubBase, {{ .|serviceclass:file,service }} { - //-{% for method in service.methods %} - //-{% if method|methodIsUnary %} - var {{ method|methodDescriptorName|lowercase }}Requests: [{{ method|input }}] = [] - var {{ method|methodDescriptorName|lowercase }}Responses: [{{ method|output }}] = [] - func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}) throws -> {{ method|output }} { - {{ method|methodDescriptorName|lowercase }}Requests.append(request) - defer { {{ method|methodDescriptorName|lowercase }}Responses.removeFirst() } - return {{ method|methodDescriptorName|lowercase }}Responses.first! - } - func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}, completion: @escaping ({{ method|output }}?, CallResult) -> Void) throws -> {{ .|call:file,service,method }} { - fatalError("not implemented") - } - //-{% endif %} - //-{% if method|methodIsServerStreaming %} - var {{ method|methodDescriptorName|lowercase }}Requests: [{{ method|input }}] = [] - var {{ method|methodDescriptorName|lowercase }}Calls: [{{ .|call:file,service,method }}] = [] - func {{ method|methodDescriptorName|lowercase }}(_ request: {{ method|input }}, completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} { - {{ method|methodDescriptorName|lowercase }}Requests.append(request) - defer { {{ method|methodDescriptorName|lowercase }}Calls.removeFirst() } - return {{ method|methodDescriptorName|lowercase }}Calls.first! - } - //-{% endif %} - //-{% if method|methodIsClientStreaming %} - var {{ method|methodDescriptorName|lowercase }}Calls: [{{ .|call:file,service,method }}] = [] - func {{ method|methodDescriptorName|lowercase }}(completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} { - defer { {{ method|methodDescriptorName|lowercase }}Calls.removeFirst() } - return {{ method|methodDescriptorName|lowercase }}Calls.first! - } - //-{% endif %} - //-{% if method|methodIsBidiStreaming %} - var {{ method|methodDescriptorName|lowercase }}Calls: [{{ .|call:file,service,method }}] = [] - func {{ method|methodDescriptorName|lowercase }}(completion: ((CallResult) -> Void)?) throws -> {{ .|call:file,service,method }} { - defer { {{ method|methodDescriptorName|lowercase }}Calls.removeFirst() } - return {{ method|methodDescriptorName|lowercase }}Calls.first! - } - //-{% endif %} - - //-{% endfor %} -} -//-{% endif %} - -//-{% endfor %} diff --git a/Plugin/Templates/server-session-bidistreaming.swift b/Plugin/Templates/server-session-bidistreaming.swift deleted file mode 100644 index fdf7abe60..000000000 --- a/Plugin/Templates/server-session-bidistreaming.swift +++ /dev/null @@ -1,16 +0,0 @@ -{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionBidirectionalStreaming { - /// Receive a message. Blocks until a message is received or the client closes the connection. - func receive() throws -> {{ method|input }} - - /// Send a message. Nonblocking. - func send(_ response: {{ method|output }}, completion: ((Bool) -> Void)?) throws - - /// Close a connection. Blocks until the connection is closed. - func close() throws -} - -fileprivate final class {{ .|session:file,service,method }}Base: ServerSessionBidirectionalStreamingBase<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} {} - -//-{% if generateTestStubs %} -class {{ .|session:file,service,method }}TestStub: ServerSessionBidirectionalStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} {} -//-{% endif %} diff --git a/Plugin/Templates/server-session-clientstreaming.swift b/Plugin/Templates/server-session-clientstreaming.swift deleted file mode 100644 index 4ccf8548e..000000000 --- a/Plugin/Templates/server-session-clientstreaming.swift +++ /dev/null @@ -1,13 +0,0 @@ -{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionClientStreaming { - /// Receive a message. Blocks until a message is received or the client closes the connection. - func receive() throws -> {{ method|input }} - - /// Send a response and close the connection. - func sendAndClose(_ response: {{ method|output }}) throws -} - -fileprivate final class {{ .|session:file,service,method }}Base: ServerSessionClientStreamingBase<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} {} - -//-{% if generateTestStubs %} -class {{ .|session:file,service,method }}TestStub: ServerSessionClientStreamingTestStub<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} {} -//-{% endif %} diff --git a/Plugin/Templates/server-session-serverstreaming.swift b/Plugin/Templates/server-session-serverstreaming.swift deleted file mode 100644 index f75c5f425..000000000 --- a/Plugin/Templates/server-session-serverstreaming.swift +++ /dev/null @@ -1,10 +0,0 @@ -{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionServerStreaming { - /// Send a message. Nonblocking. - func send(_ response: {{ method|output }}, completion: ((Bool) -> Void)?) throws -} - -fileprivate final class {{ .|session:file,service,method }}Base: ServerSessionServerStreamingBase<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} {} - -//-{% if generateTestStubs %} -class {{ .|session:file,service,method }}TestStub: ServerSessionServerStreamingTestStub<{{ method|output }}>, {{ .|session:file,service,method }} {} -//-{% endif %} diff --git a/Plugin/Templates/server-session-unary.swift b/Plugin/Templates/server-session-unary.swift deleted file mode 100644 index ddc8c3a3a..000000000 --- a/Plugin/Templates/server-session-unary.swift +++ /dev/null @@ -1,7 +0,0 @@ -{{ access }} protocol {{ .|session:file,service,method }}: ServerSessionUnary {} - -fileprivate final class {{ .|session:file,service,method }}Base: ServerSessionUnaryBase<{{ method|input }}, {{ method|output }}>, {{ .|session:file,service,method }} {} - -//-{% if generateTestStubs %} -class {{ .|session:file,service,method }}TestStub: ServerSessionUnaryTestStub, {{ .|session:file,service,method }} {} -//-{% endif %} diff --git a/Plugin/Templates/server.swift b/Plugin/Templates/server.swift deleted file mode 100644 index 12eaa1607..000000000 --- a/Plugin/Templates/server.swift +++ /dev/null @@ -1,73 +0,0 @@ -//-{% for service in file.services %} -/// To build a server, implement a class that conforms to this protocol. -{{ access }} protocol {{ .|provider:file,service }} { - //-{% for method in service.methods %} - //-{% if method|methodIsUnary %} - func {{ method|methodDescriptorName|lowercase }}(request: {{ method|input }}, session: {{ .|session:file,service,method }}) throws -> {{ method|output }} - //-{% endif %} - //-{% if method|methodIsServerStreaming %} - func {{ method|methodDescriptorName|lowercase }}(request: {{ method|input }}, session: {{ .|session:file,service,method }}) throws - //-{% endif %} - //-{% if method|methodIsClientStreaming %} - func {{ method|methodDescriptorName|lowercase }}(session: {{ .|session:file,service,method }}) throws - //-{% endif %} - //-{% if method|methodIsBidiStreaming %} - func {{ method|methodDescriptorName|lowercase }}(session: {{ .|session:file,service,method }}) throws - //-{% endif %} - //-{% endfor %} -} - -//-{% for method in service.methods %} -//-{% if method|methodIsUnary %} -//-{% include "server-session-unary.swift" %} -//-{% endif %} -//-{% if method|methodIsServerStreaming %} -//-{% include "server-session-serverstreaming.swift" %} -//-{% endif %} -//-{% if method|methodIsClientStreaming %} -//-{% include "server-session-clientstreaming.swift" %} -//-{% endif %} -//-{% if method|methodIsBidiStreaming %} -//-{% include "server-session-bidistreaming.swift" %} -//-{% endif %} -//-{% endfor %} - -/// Main server for generated service -{{ access }} final class {{ .|server:file,service }}: ServiceServer { - private let provider: {{ .|provider:file,service }} - - {{ access }} init(address: String, provider: {{ .|provider:file,service }}) { - self.provider = provider - super.init(address: address) - } - - {{ access }} init?(address: String, certificateURL: URL, keyURL: URL, provider: {{ .|provider:file,service }}) { - self.provider = provider - super.init(address: address, certificateURL: certificateURL, keyURL: keyURL) - } - - /// Start the server. - {{ access }} override func handleMethod(_ method: String, handler: Handler, queue: DispatchQueue) throws -> Bool { - let provider = self.provider - switch method { - //-{% for method in service.methods %} - case "{{ .|path:file,service,method }}": - //-{% if method|methodIsUnary or method|methodIsServerStreaming %} - try {{ .|session:file,service,method }}Base( - handler: handler, - providerBlock: { try provider.{{ method|methodDescriptorName|lowercase }}(request: $0, session: $1 as! {{ .|session:file,service,method }}Base) }) - .run(queue: queue) - //-{% else %} - try {{ .|session:file,service,method }}Base( - handler: handler, - providerBlock: { try provider.{{ method|methodDescriptorName|lowercase }}(session: $0 as! {{ .|session:file,service,method }}Base) }) - .run(queue: queue) - //-{% endif %} - return true - //-{% endfor %} - default: - return false - } - } -} -//-{% endfor %}