Browse Source

Generate documentation from proto comments (#743)

Motivation:

We have access to the underlying comments associated with RPCs in the
protobuf definition. These often explain what each RPC does and can have
great value to the user.

Modifications:

- Generate documentation for client stubs and server provider protocol
  based on the source comments in the proto file.
- Re-generate code.

Result:

- The generate code has more helpful documentation
George Barnett 5 years ago
parent
commit
522e5d4259

+ 8 - 4
Sources/Examples/Echo/Model/echo.grpc.swift

@@ -49,7 +49,7 @@ public final class Echo_EchoClient: GRPCClient, Echo_EchoClientProtocol {
     self.defaultCallOptions = defaultCallOptions
   }
 
-  /// Asynchronous unary call to Get.
+  /// Immediately returns an echo of a request.
   ///
   /// - Parameters:
   ///   - request: Request to send to Get.
@@ -61,7 +61,7 @@ public final class Echo_EchoClient: GRPCClient, Echo_EchoClientProtocol {
                               callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous server-streaming call to Expand.
+  /// Splits a request into words and returns each word in a stream of messages.
   ///
   /// - Parameters:
   ///   - request: Request to send to Expand.
@@ -75,7 +75,7 @@ public final class Echo_EchoClient: GRPCClient, Echo_EchoClientProtocol {
                                         handler: handler)
   }
 
-  /// Asynchronous client-streaming call to Collect.
+  /// Collects a stream of messages and returns them concatenated when the caller closes.
   ///
   /// 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.
@@ -88,7 +88,7 @@ public final class Echo_EchoClient: GRPCClient, Echo_EchoClientProtocol {
                                         callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous bidirectional-streaming call to Update.
+  /// Streams back messages as they are received in an input stream.
   ///
   /// 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.
@@ -107,9 +107,13 @@ public final class Echo_EchoClient: GRPCClient, Echo_EchoClientProtocol {
 
 /// To build a server, implement a class that conforms to this protocol.
 public protocol Echo_EchoProvider: CallHandlerProvider {
+  /// Immediately returns an echo of a request.
   func get(request: Echo_EchoRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Echo_EchoResponse>
+  /// Splits a request into words and returns each word in a stream of messages.
   func expand(request: Echo_EchoRequest, context: StreamingResponseCallContext<Echo_EchoResponse>) -> EventLoopFuture<GRPCStatus>
+  /// Collects a stream of messages and returns them concatenated when the caller closes.
   func collect(context: UnaryResponseCallContext<Echo_EchoResponse>) -> EventLoopFuture<(StreamEvent<Echo_EchoRequest>) -> Void>
+  /// Streams back messages as they are received in an input stream.
   func update(context: StreamingResponseCallContext<Echo_EchoResponse>) -> EventLoopFuture<(StreamEvent<Echo_EchoRequest>) -> Void>
 }
 

+ 2 - 1
Sources/Examples/HelloWorld/Model/helloworld.grpc.swift

@@ -46,7 +46,7 @@ public final class Helloworld_GreeterClient: GRPCClient, Helloworld_GreeterClien
     self.defaultCallOptions = defaultCallOptions
   }
 
-  /// Asynchronous unary call to SayHello.
+  /// Sends a greeting.
   ///
   /// - Parameters:
   ///   - request: Request to send to SayHello.
@@ -62,6 +62,7 @@ public final class Helloworld_GreeterClient: GRPCClient, Helloworld_GreeterClien
 
 /// To build a server, implement a class that conforms to this protocol.
 public protocol Helloworld_GreeterProvider: CallHandlerProvider {
+  /// Sends a greeting.
   func sayHello(request: Helloworld_HelloRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Helloworld_HelloReply>
 }
 

+ 40 - 4
Sources/Examples/RouteGuide/Model/route_guide.grpc.swift

@@ -49,7 +49,12 @@ public final class Routeguide_RouteGuideClient: GRPCClient, Routeguide_RouteGuid
     self.defaultCallOptions = defaultCallOptions
   }
 
-  /// Asynchronous unary call to GetFeature.
+  /// A simple RPC.
+  ///
+  /// Obtains the feature at a given position.
+  ///
+  /// A feature with an empty name is returned if there's no feature at the given
+  /// position.
   ///
   /// - Parameters:
   ///   - request: Request to send to GetFeature.
@@ -61,7 +66,12 @@ public final class Routeguide_RouteGuideClient: GRPCClient, Routeguide_RouteGuid
                               callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous server-streaming call to ListFeatures.
+  /// A server-to-client streaming RPC.
+  ///
+  /// Obtains the Features available within the given Rectangle.  Results are
+  /// streamed rather than returned at once (e.g. in a response message with a
+  /// repeated field), as the rectangle may cover a large area and contain a
+  /// huge number of features.
   ///
   /// - Parameters:
   ///   - request: Request to send to ListFeatures.
@@ -75,7 +85,10 @@ public final class Routeguide_RouteGuideClient: GRPCClient, Routeguide_RouteGuid
                                         handler: handler)
   }
 
-  /// Asynchronous client-streaming call to RecordRoute.
+  /// A client-to-server streaming RPC.
+  ///
+  /// Accepts a stream of Points on a route being traversed, returning a
+  /// RouteSummary when traversal is completed.
   ///
   /// 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.
@@ -88,7 +101,10 @@ public final class Routeguide_RouteGuideClient: GRPCClient, Routeguide_RouteGuid
                                         callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous bidirectional-streaming call to RouteChat.
+  /// A Bidirectional streaming RPC.
+  ///
+  /// Accepts a stream of RouteNotes sent while a route is being traversed,
+  /// while receiving other RouteNotes (e.g. from other users).
   ///
   /// 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.
@@ -107,9 +123,29 @@ public final class Routeguide_RouteGuideClient: GRPCClient, Routeguide_RouteGuid
 
 /// To build a server, implement a class that conforms to this protocol.
 public protocol Routeguide_RouteGuideProvider: CallHandlerProvider {
+  /// A simple RPC.
+  ///
+  /// Obtains the feature at a given position.
+  ///
+  /// A feature with an empty name is returned if there's no feature at the given
+  /// position.
   func getFeature(request: Routeguide_Point, context: StatusOnlyCallContext) -> EventLoopFuture<Routeguide_Feature>
+  /// A server-to-client streaming RPC.
+  ///
+  /// Obtains the Features available within the given Rectangle.  Results are
+  /// streamed rather than returned at once (e.g. in a response message with a
+  /// repeated field), as the rectangle may cover a large area and contain a
+  /// huge number of features.
   func listFeatures(request: Routeguide_Rectangle, context: StreamingResponseCallContext<Routeguide_Feature>) -> EventLoopFuture<GRPCStatus>
+  /// A client-to-server streaming RPC.
+  ///
+  /// Accepts a stream of Points on a route being traversed, returning a
+  /// RouteSummary when traversal is completed.
   func recordRoute(context: UnaryResponseCallContext<Routeguide_RouteSummary>) -> EventLoopFuture<(StreamEvent<Routeguide_Point>) -> Void>
+  /// A Bidirectional streaming RPC.
+  ///
+  /// Accepts a stream of RouteNotes sent while a route is being traversed,
+  /// while receiving other RouteNotes (e.g. from other users).
   func routeChat(context: StreamingResponseCallContext<Routeguide_RouteNote>) -> EventLoopFuture<(StreamEvent<Routeguide_RouteNote>) -> Void>
 }
 

+ 39 - 12
Sources/GRPCInteroperabilityTestModels/Generated/test.grpc.swift

@@ -53,7 +53,7 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
     self.defaultCallOptions = defaultCallOptions
   }
 
-  /// Asynchronous unary call to EmptyCall.
+  /// One empty request followed by one empty response.
   ///
   /// - Parameters:
   ///   - request: Request to send to EmptyCall.
@@ -65,7 +65,7 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
                               callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous unary call to UnaryCall.
+  /// One request followed by one response.
   ///
   /// - Parameters:
   ///   - request: Request to send to UnaryCall.
@@ -77,7 +77,9 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
                               callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous unary call to CacheableUnaryCall.
+  /// One request followed by one response. Response has cache control
+  /// headers set such that a caching HTTP proxy (such as GFE) can
+  /// satisfy subsequent requests.
   ///
   /// - Parameters:
   ///   - request: Request to send to CacheableUnaryCall.
@@ -89,7 +91,8 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
                               callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous server-streaming call to StreamingOutputCall.
+  /// One request followed by a sequence of responses (streamed download).
+  /// The server returns the payload with client desired type and sizes.
   ///
   /// - Parameters:
   ///   - request: Request to send to StreamingOutputCall.
@@ -103,7 +106,8 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
                                         handler: handler)
   }
 
-  /// Asynchronous client-streaming call to StreamingInputCall.
+  /// A sequence of requests followed by one response (streamed upload).
+  /// The server returns the aggregated size of client payload as the result.
   ///
   /// 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.
@@ -116,7 +120,9 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
                                         callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous bidirectional-streaming call to FullDuplexCall.
+  /// A sequence of requests with each request served by the server immediately.
+  /// As one request could lead to multiple responses, this interface
+  /// demonstrates the idea of full duplexing.
   ///
   /// 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.
@@ -131,7 +137,10 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
                                                handler: handler)
   }
 
-  /// Asynchronous bidirectional-streaming call to HalfDuplexCall.
+  /// A sequence of requests followed by a sequence of responses.
+  /// The server buffers all the client requests and then serves them in order. A
+  /// stream of responses are returned to the client when the server starts with
+  /// first request.
   ///
   /// 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.
@@ -146,7 +155,8 @@ public final class Grpc_Testing_TestServiceClient: GRPCClient, Grpc_Testing_Test
                                                handler: handler)
   }
 
-  /// Asynchronous unary call to UnimplementedCall.
+  /// The test server will not implement this method. It will be used
+  /// to test the behavior when clients call unimplemented methods.
   ///
   /// - Parameters:
   ///   - request: Request to send to UnimplementedCall.
@@ -179,7 +189,7 @@ public final class Grpc_Testing_UnimplementedServiceClient: GRPCClient, Grpc_Tes
     self.defaultCallOptions = defaultCallOptions
   }
 
-  /// Asynchronous unary call to UnimplementedCall.
+  /// A call that no server should implement
   ///
   /// - Parameters:
   ///   - request: Request to send to UnimplementedCall.
@@ -213,7 +223,7 @@ public final class Grpc_Testing_ReconnectServiceClient: GRPCClient, Grpc_Testing
     self.defaultCallOptions = defaultCallOptions
   }
 
-  /// Asynchronous unary call to Start.
+  /// Unary call to Start
   ///
   /// - Parameters:
   ///   - request: Request to send to Start.
@@ -225,7 +235,7 @@ public final class Grpc_Testing_ReconnectServiceClient: GRPCClient, Grpc_Testing
                               callOptions: callOptions ?? self.defaultCallOptions)
   }
 
-  /// Asynchronous unary call to Stop.
+  /// Unary call to Stop
   ///
   /// - Parameters:
   ///   - request: Request to send to Stop.
@@ -241,12 +251,28 @@ public final class Grpc_Testing_ReconnectServiceClient: GRPCClient, Grpc_Testing
 
 /// To build a server, implement a class that conforms to this protocol.
 public protocol Grpc_Testing_TestServiceProvider: CallHandlerProvider {
+  /// One empty request followed by one empty response.
   func emptyCall(request: Grpc_Testing_Empty, context: StatusOnlyCallContext) -> EventLoopFuture<Grpc_Testing_Empty>
+  /// One request followed by one response.
   func unaryCall(request: Grpc_Testing_SimpleRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Grpc_Testing_SimpleResponse>
+  /// One request followed by one response. Response has cache control
+  /// headers set such that a caching HTTP proxy (such as GFE) can
+  /// satisfy subsequent requests.
   func cacheableUnaryCall(request: Grpc_Testing_SimpleRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Grpc_Testing_SimpleResponse>
+  /// One request followed by a sequence of responses (streamed download).
+  /// The server returns the payload with client desired type and sizes.
   func streamingOutputCall(request: Grpc_Testing_StreamingOutputCallRequest, context: StreamingResponseCallContext<Grpc_Testing_StreamingOutputCallResponse>) -> EventLoopFuture<GRPCStatus>
+  /// A sequence of requests followed by one response (streamed upload).
+  /// The server returns the aggregated size of client payload as the result.
   func streamingInputCall(context: UnaryResponseCallContext<Grpc_Testing_StreamingInputCallResponse>) -> EventLoopFuture<(StreamEvent<Grpc_Testing_StreamingInputCallRequest>) -> Void>
+  /// A sequence of requests with each request served by the server immediately.
+  /// As one request could lead to multiple responses, this interface
+  /// demonstrates the idea of full duplexing.
   func fullDuplexCall(context: StreamingResponseCallContext<Grpc_Testing_StreamingOutputCallResponse>) -> EventLoopFuture<(StreamEvent<Grpc_Testing_StreamingOutputCallRequest>) -> Void>
+  /// A sequence of requests followed by a sequence of responses.
+  /// The server buffers all the client requests and then serves them in order. A
+  /// stream of responses are returned to the client when the server starts with
+  /// first request.
   func halfDuplexCall(context: StreamingResponseCallContext<Grpc_Testing_StreamingOutputCallResponse>) -> EventLoopFuture<(StreamEvent<Grpc_Testing_StreamingOutputCallRequest>) -> Void>
 }
 
@@ -307,6 +333,7 @@ extension Grpc_Testing_TestServiceProvider {
 
 /// To build a server, implement a class that conforms to this protocol.
 public protocol Grpc_Testing_UnimplementedServiceProvider: CallHandlerProvider {
+  /// A call that no server should implement
   func unimplementedCall(request: Grpc_Testing_Empty, context: StatusOnlyCallContext) -> EventLoopFuture<Grpc_Testing_Empty>
 }
 
@@ -362,7 +389,7 @@ extension Grpc_Testing_ReconnectServiceProvider {
 }
 
 
-/// Provides conformance to `GRPCPayload` for the request and response messages
+// Provides conformance to `GRPCPayload` for request and response messages
 extension Grpc_Testing_Empty: GRPCProtobufPayload {}
 extension Grpc_Testing_SimpleRequest: GRPCProtobufPayload {}
 extension Grpc_Testing_SimpleResponse: GRPCProtobufPayload {}

+ 38 - 5
Sources/protoc-gen-grpc-swift/Generator-Client.swift

@@ -70,9 +70,10 @@ extension Generator {
 
     for method in service.methods {
       self.method = method
-      switch streamingType(method) {
+      let streamType = streamingType(self.method)
+      switch streamType {
       case .unary:
-        println("/// Asynchronous unary call to \(method.name).")
+        println(self.method.documentation(streamingType: streamType), newline: false)
         println("///")
         printParameters()
         printRequestParameter()
@@ -87,7 +88,7 @@ extension Generator {
         println("}")
 
       case .serverStreaming:
-        println("/// Asynchronous server-streaming call to \(method.name).")
+        println(self.method.documentation(streamingType: streamType), newline: false)
         println("///")
         printParameters()
         printRequestParameter()
@@ -104,7 +105,7 @@ extension Generator {
         println("}")
 
       case .clientStreaming:
-        println("/// Asynchronous client-streaming call to \(method.name).")
+        println(self.method.documentation(streamingType: streamType), newline: false)
         println("///")
         printClientStreamingDetails()
         println("///")
@@ -119,7 +120,7 @@ extension Generator {
         println("}")
 
       case .bidirectionalStreaming:
-        println("/// Asynchronous bidirectional-streaming call to \(method.name).")
+        println(self.method.documentation(streamingType: streamType), newline: false)
         println("///")
         printClientStreamingDetails()
         println("///")
@@ -162,3 +163,35 @@ extension Generator {
     println("///   - handler: A closure called when each response is received from the server.")
   }
 }
+
+fileprivate extension StreamingType {
+  var name: String {
+    switch self {
+    case .unary:
+      return "Unary"
+    case .clientStreaming:
+      return "Client streaming"
+    case .serverStreaming:
+      return "Server streaming"
+    case .bidirectionalStreaming:
+      return "Bidirectional streaming"
+    }
+  }
+}
+
+extension MethodDescriptor {
+  var documentation: String? {
+    let comments = self.protoSourceComments(commentPrefix: "")
+    return comments.isEmpty ? nil : comments
+  }
+
+  fileprivate func documentation(streamingType: StreamingType) -> String {
+    let sourceComments = self.protoSourceComments()
+
+    if sourceComments.isEmpty {
+      return "/// \(streamingType.name) call to \(self.name)\n"  // comments end with "\n" already.
+    } else {
+      return sourceComments  // already prefixed with "///"
+    }
+  }
+}

+ 4 - 0
Sources/protoc-gen-grpc-swift/Generator-Server.swift

@@ -31,12 +31,16 @@ extension Generator {
       
       switch streamingType(method) {
       case .unary:
+        println(self.method.protoSourceComments(), newline: false)
         println("func \(methodFunctionName)(request: \(methodInputName), context: StatusOnlyCallContext) -> EventLoopFuture<\(methodOutputName)>")
       case .serverStreaming:
+        println(self.method.protoSourceComments(), newline: false)
         println("func \(methodFunctionName)(request: \(methodInputName), context: StreamingResponseCallContext<\(methodOutputName)>) -> EventLoopFuture<GRPCStatus>")
       case .clientStreaming:
+        println(self.method.protoSourceComments(), newline: false)
         println("func \(methodFunctionName)(context: UnaryResponseCallContext<\(methodOutputName)>) -> EventLoopFuture<(StreamEvent<\(methodInputName)>) -> Void>")
       case .bidirectionalStreaming:
+        println(self.method.protoSourceComments(), newline: false)
         println("func \(methodFunctionName)(context: StreamingResponseCallContext<\(methodOutputName)>) -> EventLoopFuture<(StreamEvent<\(methodInputName)>) -> Void>")
       }
     }

+ 4 - 3
Sources/protoc-gen-grpc-swift/Generator.swift

@@ -41,9 +41,11 @@ class Generator {
     return printer.content
   }
 
-  internal func println(_ text: String = "") {
+  internal func println(_ text: String = "", newline: Bool = true) {
     printer.print(text)
-    printer.print("\n")
+    if newline {
+      printer.print("\n")
+    }
   }
 
   internal func indent() {
@@ -122,5 +124,4 @@ class Generator {
     self.println()
     self.printProtobufExtensions()
   }
-
 }