Skip to content

Commit

Permalink
Generate Swagger description for enum types, enum values and nested m…
Browse files Browse the repository at this point in the history
…essages using proto comments
  • Loading branch information
t-yuki committed Apr 27, 2016
1 parent 06b3e56 commit 97558d4
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 37 deletions.
3 changes: 2 additions & 1 deletion examples/examplepb/echo_service.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@
"format": "string",
"description": "Id represents the message identifier."
}
}
},
"description": "SimpleMessage represents a simple message sent to the Echo service."
}
}
}
8 changes: 8 additions & 0 deletions examples/examplepb/streamless_everything.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import "google/api/annotations.proto";
import "examples/sub/message.proto";

message ABitOfEverything {
// Nested is nested type.
message Nested {
// name is nested field.
string name = 1;
uint32 amount = 2;
// DeepEnum is one or zero.
enum DeepEnum {
// FALSE is false.
FALSE = 0;
// TRUE is true.
TRUE = 1;
}
DeepEnum ok = 3;
Expand Down Expand Up @@ -44,8 +49,11 @@ message IdMessage {
string uuid = 1;
}

// NumericEnum is one or zero.
enum NumericEnum {
// ZERO means 0
ZERO = 0;
// ONE means 1
ONE = 1;
}

Expand Down
12 changes: 8 additions & 4 deletions examples/examplepb/streamless_everything.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -346,20 +346,23 @@
},
"name": {
"type": "string",
"format": "string"
"format": "string",
"description": "name is nested field."
},
"ok": {
"$ref": "#/definitions/NestedDeepEnum"
}
}
},
"description": "Nested is nested type."
},
"NestedDeepEnum": {
"type": "string",
"enum": [
"FALSE",
"TRUE"
],
"default": "FALSE"
"default": "FALSE",
"description": "DeepEnum is one or zero. FALSE: FALSE is false. TRUE: TRUE is true."
},
"examplepbABitOfEverything": {
"properties": {
Expand Down Expand Up @@ -457,7 +460,8 @@
"ZERO",
"ONE"
],
"default": "ZERO"
"default": "ZERO",
"description": "NumericEnum is one or zero. ZERO: ZERO means 0 ONE: ONE means 1"
},
"subStringMessage": {
"properties": {
Expand Down
3 changes: 2 additions & 1 deletion protoc-gen-grpc-gateway/descriptor/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,12 @@ func (r *Registry) registerMsg(file *File, outerPath []string, msgs []*descripto
}

func (r *Registry) registerEnum(file *File, outerPath []string, enums []*descriptor.EnumDescriptorProto) {
for _, ed := range enums {
for i, ed := range enums {
e := &Enum{
File: file,
Outers: outerPath,
EnumDescriptorProto: ed,
Index: i,
}
file.Enums = append(file.Enums, e)
r.enums[e.FQEN()] = e
Expand Down
2 changes: 2 additions & 0 deletions protoc-gen-grpc-gateway/descriptor/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ type Enum struct {
// Outers is a list of outer messages if this enum is a nested type.
Outers []string
*descriptor.EnumDescriptorProto

Index int
}

// FQEN returns a fully qualified enum name of this enum.
Expand Down
118 changes: 87 additions & 31 deletions protoc-gen-swagger/genswagger/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ func findNestedMessagesAndEnumerations(message *descriptor.Message, reg *descrip

func renderMessagesAsDefinition(messages messageMap, d swaggerDefinitionsObject, reg *descriptor.Registry) {
for _, msg := range messages {
msgDescription := protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index))
object := swaggerSchemaObject{
Properties: map[string]swaggerSchemaObject{},
Properties: map[string]swaggerSchemaObject{},
Description: msgDescription,
}
for fieldIdx, field := range msg.Fields {
var fieldType, fieldFormat string
Expand Down Expand Up @@ -141,19 +143,8 @@ func renderMessagesAsDefinition(messages messageMap, d swaggerDefinitionsObject,
fieldFormat = "UNKNOWN"
}

fieldDescription := ""
for _, loc := range msg.File.SourceCodeInfo.Location {
if len(loc.Path) < 4 {
continue
}
if loc.Path[0] == protoPath(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), "MessageType") && loc.Path[1] == int32(msg.Index) && loc.Path[2] == protoPath(reflect.TypeOf((*pbdescriptor.DescriptorProto)(nil)), "Field") && loc.Path[3] == int32(fieldIdx) {
if loc.LeadingComments != nil {
fieldDescription = strings.TrimRight(*loc.LeadingComments, "\n")
fieldDescription = strings.TrimLeft(fieldDescription, " ")
}
break
}
}
fieldProtoPath := protoPath(reflect.TypeOf((*pbdescriptor.DescriptorProto)(nil)), "Field")
fieldDescription := protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index), fieldProtoPath, int32(fieldIdx))

if primitive {
// If repeated render as an array of items.
Expand Down Expand Up @@ -196,21 +187,30 @@ func renderMessagesAsDefinition(messages messageMap, d swaggerDefinitionsObject,

// renderEnumerationsAsDefinition inserts enums into the definitions object.
func renderEnumerationsAsDefinition(enums enumMap, d swaggerDefinitionsObject, reg *descriptor.Registry) {
valueProtoPath := protoPath(reflect.TypeOf((*pbdescriptor.EnumDescriptorProto)(nil)), "Value")
for _, enum := range enums {
enumDescription := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index))

var enumNames []string
// it may be necessary to sort the result of the GetValue function.
var defaultValue string
for _, value := range enum.GetValue() {
for valueIdx, value := range enum.GetValue() {
enumNames = append(enumNames, value.GetName())
if defaultValue == "" && value.GetNumber() == 0 {
defaultValue = value.GetName()
}

valueDescription := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index), valueProtoPath, int32(valueIdx))
if valueDescription != "" {
enumDescription += " " + value.GetName() + ": " + valueDescription
}
}

d[fullyQualifiedNameToSwaggerName(enum.FQEN(), reg)] = swaggerSchemaObject{
Type: "string",
Enum: enumNames,
Default: defaultValue,
Type: "string",
Enum: enumNames,
Default: defaultValue,
Description: enumDescription,
}
}
}
Expand Down Expand Up @@ -436,19 +436,8 @@ func renderServices(services []*descriptor.Service, paths swaggerPathsObject, re
pathItemObject = swaggerPathItemObject{}
}

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
}
}
methProtoPath := protoPath(reflect.TypeOf((*pbdescriptor.ServiceDescriptorProto)(nil)), "Method")
methDescription := protoComments(reg, svc.File, nil, "Service", int32(svcIdx), methProtoPath, int32(methIdx))
operationObject := &swaggerOperationObject{
Summary: fmt.Sprintf("%s.%s", svc.GetName(), meth.GetName()),
Description: methDescription,
Expand Down Expand Up @@ -523,6 +512,73 @@ func applyTemplate(p param) (string, error) {
return w.String(), nil
}

func protoComments(reg *descriptor.Registry, file *descriptor.File, outers []string, typeName string, typeIndex int32, fieldPathes ...int32) string {
outerPathes := make([]int32, len(outers))
for i := range outers {
location := ""
if file.Package != nil {
location = file.GetPackage()
}

msg, err := reg.LookupMsg(location, strings.Join(outers[:i+1], "."))
if err != nil {
panic(err)
}
outerPathes[i] = int32(msg.Index)
}

messageProtoPath := protoPath(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), "MessageType")
nestedProtoPath := protoPath(reflect.TypeOf((*pbdescriptor.DescriptorProto)(nil)), "NestedType")
L1:
for _, loc := range file.SourceCodeInfo.Location {
if len(loc.Path) < len(outerPathes)*2+2+len(fieldPathes) {
continue
}

for i, v := range outerPathes {
if i == 0 && loc.Path[i*2+0] != messageProtoPath {
continue L1
}
if i != 0 && loc.Path[i*2+0] != nestedProtoPath {
continue L1
}
if loc.Path[i*2+1] != v {
continue L1
}
}

outerOffset := len(outerPathes) * 2
if outerOffset == 0 && loc.Path[outerOffset] != protoPath(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), typeName) {
continue
}
if outerOffset != 0 {
if typeName == "MessageType" {
typeName = "NestedType"
}
if loc.Path[outerOffset] != protoPath(reflect.TypeOf((*pbdescriptor.DescriptorProto)(nil)), typeName) {
continue
}
}
if loc.Path[outerOffset+1] != typeIndex {
continue
}

for i, v := range fieldPathes {
if loc.Path[outerOffset+2+i] != v {
continue L1
}
}

comments := ""
if loc.LeadingComments != nil {
comments = strings.TrimRight(*loc.LeadingComments, "\n")
comments = strings.TrimSpace(comments)
}
return comments
}
return ""
}

func protoPath(descriptorType reflect.Type, what string) int32 {
// TODO(ivucica): handle errors obtaining any of the following.
field, ok := descriptorType.Elem().FieldByName(what)
Expand Down

0 comments on commit 97558d4

Please sign in to comment.