Skip to content

Commit

Permalink
Generate Swagger description for service methods using proto comments.
Browse files Browse the repository at this point in the history
While this is a first step in resolving grpc-ecosystem#128, this
needs to be cleaned up, and the same approach needs to be used for
messages, message fields, et al.

echo_service.proto has been annotated with extra comments in order to
demo the new descriptions.

Only the Swagger example has been regenerated, as my local generator
does not output all the expected fields in proto struct tags.
  • Loading branch information
ivucica committed Apr 18, 2016
1 parent c080463 commit d2fd8af
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 2 deletions.
5 changes: 5 additions & 0 deletions examples/examplepb/echo_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ package gengo.grpc.gateway.examples.examplepb;

import "google/api/annotations.proto";

// SimpleMessage represents a simple message sent to the Echo service.
message SimpleMessage {
// Id represents the message identifier.
string id = 1;
}

// Echo service responds to incoming echo requests.
service EchoService {
// Echo method receives a simple message and returns it.
rpc Echo(SimpleMessage) returns (SimpleMessage) {
option (google.api.http) = {
post: "/v1/example/echo/{id}"
};
}
// EchoBody method receives a simple message and returns it.
rpc EchoBody(SimpleMessage) returns (SimpleMessage) {
option (google.api.http) = {
post: "/v1/example/echo_body"
Expand Down
2 changes: 2 additions & 0 deletions examples/examplepb/echo_service.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"/v1/example/echo/{id}": {
"post": {
"summary": "EchoService.Echo",
"description": "Echo method receives a simple message and returns it.",
"operationId": "Echo",
"responses": {
"default": {
Expand All @@ -44,6 +45,7 @@
"/v1/example/echo_body": {
"post": {
"summary": "EchoService.EchoBody",
"description": "EchoBody method receives a simple message and returns it.",
"operationId": "EchoBody",
"responses": {
"default": {
Expand Down
39 changes: 37 additions & 2 deletions protoc-gen-swagger/genswagger/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"

"github.com/gengo/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
Expand Down Expand Up @@ -304,8 +305,9 @@ func templateToSwaggerPath(path string) string {
}

func renderServices(services []*descriptor.Service, paths swaggerPathsObject, reg *descriptor.Registry) error {
for _, svc := range services {
for _, meth := range svc.Methods {
// Correctness of svcIdx and methIdx depends on 'services' containing the services in the same order as the 'file.Service' array.
for svcIdx, svc := range services {
for methIdx, meth := range svc.Methods {
if meth.GetClientStreaming() || meth.GetServerStreaming() {
return fmt.Errorf(`service uses streaming, which is not currently supported. Maybe you would like to implement it? It wouldn't be that hard and we don't bite. Why don't you send a pull request to https://github.com/gengo/grpc-gateway?`)
}
Expand Down Expand Up @@ -415,8 +417,41 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re
if !ok {
pathItemObject = swaggerPathItemObject{}
}

// TODO(ivucica): make this a module-level function and use elsewhere.
protoPath := func(descriptorType reflect.Type, what string) int32 {
// TODO(ivucica): handle errors obtaining any of the following.
field, ok := descriptorType.Elem().FieldByName(what)
if !ok {
// TODO(ivucica): consider being more graceful.
panic(fmt.Errorf("Could not find type id for %s.", what))
}
pbtag := field.Tag.Get("protobuf")
if pbtag == "" {
// TODO(ivucica): consider being more graceful.
panic(fmt.Errorf("No protobuf tag on %s.", what))
}
// TODO(ivucica): handle error
path, _ := strconv.Atoi(strings.Split(pbtag, ",")[1])

return int32(path)
}
methDescription := ""
for _, loc := range svc.File.SourceCodeInfo.Location {
if len(loc.Path) < 4 {
continue
}
if loc.Path[0] == protoPath(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), "Service") && loc.Path[1] == int32(svcIdx) && loc.Path[2] == protoPath(reflect.TypeOf((*pbdescriptor.ServiceDescriptorProto)(nil)), "Method") && loc.Path[3] == int32(methIdx) {
if loc.LeadingComments != nil {
methDescription = strings.TrimRight(*loc.LeadingComments, "\n")
methDescription = strings.TrimLeft(methDescription, " ")
}
break
}
}
operationObject := &swaggerOperationObject{
Summary: fmt.Sprintf("%s.%s", svc.GetName(), meth.GetName()),
Description: methDescription,
Tags: []string{svc.GetName()},
OperationId: fmt.Sprintf("%s", meth.GetName()),
Parameters: parameters,
Expand Down
1 change: 1 addition & 0 deletions protoc-gen-swagger/genswagger/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type swaggerPathItemObject struct {
// http://swagger.io/specification/#operationObject
type swaggerOperationObject struct {
Summary string `json:"summary"`
Description string `json:"description,omitempty"`
OperationId string `json:"operationId"`
Responses swaggerResponsesObject `json:"responses"`
Parameters swaggerParametersObject `json:"parameters,omitempty"`
Expand Down

0 comments on commit d2fd8af

Please sign in to comment.