From d2fd8af03e7dedfe193558787acd58a363d27f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Vu=C4=8Dica?= Date: Sun, 17 Apr 2016 02:14:10 +0100 Subject: [PATCH] Generate Swagger description for service methods using proto comments. While this is a first step in resolving gengo/grpc-gateway#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. --- examples/examplepb/echo_service.proto | 5 +++ examples/examplepb/echo_service.swagger.json | 2 + protoc-gen-swagger/genswagger/template.go | 39 +++++++++++++++++++- protoc-gen-swagger/genswagger/types.go | 1 + 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/examples/examplepb/echo_service.proto b/examples/examplepb/echo_service.proto index 5e44b2827c6..83ebe704ed3 100644 --- a/examples/examplepb/echo_service.proto +++ b/examples/examplepb/echo_service.proto @@ -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" diff --git a/examples/examplepb/echo_service.swagger.json b/examples/examplepb/echo_service.swagger.json index 76a905721d6..3db1c964c7c 100644 --- a/examples/examplepb/echo_service.swagger.json +++ b/examples/examplepb/echo_service.swagger.json @@ -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": { @@ -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": { diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index 26a272cd34e..ef2075e4730 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "regexp" + "strconv" "strings" "github.com/gengo/grpc-gateway/protoc-gen-grpc-gateway/descriptor" @@ -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?`) } @@ -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, diff --git a/protoc-gen-swagger/genswagger/types.go b/protoc-gen-swagger/genswagger/types.go index fd441683b1b..a3844420342 100644 --- a/protoc-gen-swagger/genswagger/types.go +++ b/protoc-gen-swagger/genswagger/types.go @@ -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"`