Skip to content

Commit

Permalink
Fully-templatized code generation for clients and servers.
Browse files Browse the repository at this point in the history
  • Loading branch information
timburks committed Jan 7, 2017
1 parent 7ed9c08 commit 97efdba
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 216 deletions.
9 changes: 1 addition & 8 deletions Examples/Echo/Swift/Echo/echo.server.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public protocol Echo_EchoProvider {
func collect(session : Echo_EchoCollectSession) throws
func update(session : Echo_EchoUpdateSession) throws
}

// unary
public class Echo_EchoGetSession {
var handler : gRPC.Handler
Expand Down Expand Up @@ -81,7 +80,6 @@ public class Echo_EchoGetSession {
}
}
}

// server streaming
public class Echo_EchoExpandSession {
var handler : gRPC.Handler
Expand Down Expand Up @@ -117,7 +115,6 @@ public class Echo_EchoExpandSession {
}
}
}

// client streaming
public class Echo_EchoCollectSession {
var handler : gRPC.Handler
Expand Down Expand Up @@ -157,7 +154,6 @@ public class Echo_EchoCollectSession {

fileprivate func run(queue:DispatchQueue) {
do {
print("EchoCollectSession run")
try self.handler.sendMetadata(initialMetadata:Metadata()) {
queue.async {
try! self.provider.collect(session:self)
Expand All @@ -168,7 +164,6 @@ public class Echo_EchoCollectSession {
}
}
}

// fully streaming
public class Echo_EchoUpdateSession {
var handler : gRPC.Handler
Expand Down Expand Up @@ -229,7 +224,6 @@ public class Echo_EchoUpdateSession {
}
}
}

//
// main server for generated service
//
Expand Down Expand Up @@ -285,5 +279,4 @@ public class Echo_EchoServer {
}
}
}
}

}
212 changes: 93 additions & 119 deletions Plugin/Sources/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,78 @@ func protoMessageName(_ name :String?) -> String {
}
}

func pathName(_ arguments: [Any?]) throws -> String {
if arguments.count != 3 {
throw TemplateSyntaxError("path expects 3 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_MethodDescriptorProto" +
" argument, received \(arguments[2])")
}
return "/" + protoFile.package! + "." + service.name! + "/" + method.name!
}

func packageServiceMethodName(_ arguments: [Any?]) throws -> String {
if arguments.count != 3 {
throw TemplateSyntaxError("tag expects 3 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_MethodDescriptorProto" +
" argument, received \(arguments[2])")
}
return protoFile.package!.capitalized + "_" + service.name! + method.name!
}

func packageServiceName(_ arguments: [Any?]) throws -> String {
if arguments.count != 2 {
throw TemplateSyntaxError("tag expects 2 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
return protoFile.package!.capitalized + "_" + service.name!
}

// 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")

Expand All @@ -48,138 +120,42 @@ func stripMarkers(_ code:String) -> String {

func main() throws {

// initialize template engine
// initialize template engine and add custom filters
let fileSystemLoader = FileSystemLoader(paths: ["templates/"])
let ext = Extension()

ext.registerFilter("callname") { (value: Any?, arguments: [Any?]) in
if arguments.count != 3 {
throw TemplateSyntaxError("expects 3 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_MethodDescriptorProto" +
" argument, received \(arguments[2])")
}
return protoFile.package!.capitalized + "_" + service.name! + method.name! + "Call"
ext.registerFilter("call") { (value: Any?, arguments: [Any?]) in
return try packageServiceMethodName(arguments) + "Call"
}

ext.registerFilter("callpath") { (value: Any?, arguments: [Any?]) in
if arguments.count != 3 {
throw TemplateSyntaxError("expects 3 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_MethodDescriptorProto" +
" argument, received \(arguments[2])")
}
return "/" + protoFile.package! + "." + service.name! + "/" + method.name!
ext.registerFilter("session") { (value: Any?, arguments: [Any?]) in
return try packageServiceMethodName(arguments) + "Session"
}

ext.registerFilter("sessionname") { (value: Any?, arguments: [Any?]) in
if arguments.count != 3 {
throw TemplateSyntaxError("expects 3 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
guard let method = arguments[2] as? Google_Protobuf_MethodDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_MethodDescriptorProto" +
" argument, received \(arguments[2])")
}
return protoFile.package!.capitalized + "_" + service.name! + method.name! + "Session"
ext.registerFilter("path") { (value: Any?, arguments: [Any?]) in
return try pathName(arguments)
}

ext.registerFilter("errorname") { (value: Any?, arguments: [Any?]) in
if arguments.count != 2 {
throw TemplateSyntaxError("expects 2 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
return protoFile.package!.capitalized + "_" + service.name! + "ClientError"
ext.registerFilter("provider") { (value: Any?, arguments: [Any?]) in
return try packageServiceName(arguments) + "Provider"
}

ext.registerFilter("inputType") { (value: Any?) in
ext.registerFilter("clienterror") { (value: Any?, arguments: [Any?]) in
return try packageServiceName(arguments) + "ClientError"
}
ext.registerFilter("servererror") { (value: Any?, arguments: [Any?]) in
return try packageServiceName(arguments) + "ServerError"
}
ext.registerFilter("server") { (value: Any?, arguments: [Any?]) in
return try packageServiceName(arguments) + "Server"
}
ext.registerFilter("input") { (value: Any?) in
if let value = value as? Google_Protobuf_MethodDescriptorProto {
return protoMessageName(value.inputType)
}
throw TemplateSyntaxError("message: invalid argument \(value)")
}

ext.registerFilter("outputType") { (value: Any?) in
ext.registerFilter("output") { (value: Any?) in
if let value = value as? Google_Protobuf_MethodDescriptorProto {
return protoMessageName(value.outputType)
}
throw TemplateSyntaxError("message: invalid argument \(value)")
}

ext.registerFilter("servererrorname") { (value: Any?, arguments: [Any?]) in
if arguments.count != 2 {
throw TemplateSyntaxError("expects 2 arguments")
}
guard let protoFile = arguments[0] as? Google_Protobuf_FileDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_FileDescriptorProto" +
" argument, received \(arguments[0])")
}
guard let service = arguments[1] as? Google_Protobuf_ServiceDescriptorProto
else {
throw TemplateSyntaxError("tag must be called with a " +
"Google_Protobuf_ServiceDescriptorProto" +
" argument, received \(arguments[1])")
}
return protoFile.package!.capitalized + "_" + service.name! + "ServerError"
}


let templateEnvironment = Environment(loader: fileSystemLoader,
extensions:[ext])

Expand All @@ -196,7 +172,7 @@ func main() throws {

// a package declaration is required
guard let package = protoFile.package else {
print("ERROR: no package")
print("ERROR: no package for \(protoFile.name)")
continue
}

Expand Down Expand Up @@ -236,8 +212,6 @@ func main() throws {
}
}

// log the entire request proto
log += "\n\n\n\(request)"
// add the logfile to the code generation response
var logfile = Google_Protobuf_Compiler_CodeGeneratorResponse.File()
logfile.name = "swiftgrpc.log"
Expand Down
24 changes: 12 additions & 12 deletions Plugin/templates/client-call-bidistreaming.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
//
// {{ method.name }} (Bidirectional streaming)
//
public class {{ .|callname:protoFile,service,method }} {
public class {{ .|call:protoFile,service,method }} {
var call : Call

fileprivate init(_ channel: Channel) {
self.call = channel.makeCall("{{ .|callpath:protoFile,service,method }}")
self.call = channel.makeCall("{{ .|path:protoFile,service,method }}")
}

fileprivate func run(metadata:Metadata) throws -> {{ .|callname:protoFile,service,method }} {
fileprivate func run(metadata:Metadata) throws -> {{ .|call:protoFile,service,method }} {
try self.call.start(metadata: metadata, completion:{})
return self
}

fileprivate func receiveMessage(callback:@escaping ({{ method|outputType }}?) throws -> Void) throws {
fileprivate func receiveMessage(callback:@escaping ({{ method|output }}?) throws -> Void) throws {
try call.receiveMessage() {(data) in
if let data = data {
if let responseMessage = try? {{ method|outputType }}(protobuf:data) {
if let responseMessage = try? {{ method|output }}(protobuf:data) {
try callback(responseMessage)
} else {
try callback(nil) // error, bad data
Expand All @@ -27,19 +27,19 @@ public class {{ .|callname:protoFile,service,method }} {
}
}

public func Receive() throws -> {{ method|outputType }} {
var returnError : {{ .|errorname:protoFile,service }}?
var returnMessage : {{ method|outputType }}!
public func Receive() throws -> {{ method|output }} {
var returnError : {{ .|clienterror:protoFile,service }}?
var returnMessage : {{ method|output }}!
let done = NSCondition()
do {
try call.receiveMessage() {(data) in
if let data = data {
returnMessage = try? {{ method|outputType }}(protobuf:data)
returnMessage = try? {{ method|output }}(protobuf:data)
if returnMessage == nil {
returnError = {{ .|errorname:protoFile,service }}.invalidMessageReceived
returnError = {{ .|clienterror:protoFile,service }}.invalidMessageReceived
}
} else {
returnError = {{ .|errorname:protoFile,service }}.endOfStream
returnError = {{ .|clienterror:protoFile,service }}.endOfStream
}
done.lock()
done.signal()
Expand All @@ -55,7 +55,7 @@ public class {{ .|callname:protoFile,service,method }} {
return returnMessage
}

public func Send(_ message:{{ method|inputType }}) {
public func Send(_ message:{{ method|input }}) {
let messageData = try! message.serializeProtobuf()
_ = call.sendMessage(data:messageData)
}
Expand Down
Loading

0 comments on commit 97efdba

Please sign in to comment.