diff --git a/examples/examplepb/echo_service.swagger.json b/examples/examplepb/echo_service.swagger.json index 6fc2437f051..84c0d128f4c 100644 --- a/examples/examplepb/echo_service.swagger.json +++ b/examples/examplepb/echo_service.swagger.json @@ -79,7 +79,8 @@ "format": "string", "description": "Id represents the message identifier." } - } + }, + "description": "SimpleMessage represents a simple message sent to the Echo service." } } } diff --git a/examples/examplepb/streamless_everything.proto b/examples/examplepb/streamless_everything.proto index a6d2145d8ab..6fc822915b3 100644 --- a/examples/examplepb/streamless_everything.proto +++ b/examples/examplepb/streamless_everything.proto @@ -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; @@ -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; } diff --git a/examples/examplepb/streamless_everything.swagger.json b/examples/examplepb/streamless_everything.swagger.json index 847e9e867df..b9130c8f11c 100644 --- a/examples/examplepb/streamless_everything.swagger.json +++ b/examples/examplepb/streamless_everything.swagger.json @@ -346,12 +346,14 @@ }, "name": { "type": "string", - "format": "string" + "format": "string", + "description": "name is nested field." }, "ok": { "$ref": "#/definitions/NestedDeepEnum" } - } + }, + "description": "Nested is nested type." }, "NestedDeepEnum": { "type": "string", @@ -359,7 +361,8 @@ "FALSE", "TRUE" ], - "default": "FALSE" + "default": "FALSE", + "description": "DeepEnum is one or zero. FALSE: FALSE is false. TRUE: TRUE is true." }, "examplepbABitOfEverything": { "properties": { @@ -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": { diff --git a/protoc-gen-grpc-gateway/descriptor/registry.go b/protoc-gen-grpc-gateway/descriptor/registry.go index ee032f1edbb..f293f555824 100644 --- a/protoc-gen-grpc-gateway/descriptor/registry.go +++ b/protoc-gen-grpc-gateway/descriptor/registry.go @@ -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 diff --git a/protoc-gen-grpc-gateway/descriptor/types.go b/protoc-gen-grpc-gateway/descriptor/types.go index cea2db84516..1a3caf96220 100644 --- a/protoc-gen-grpc-gateway/descriptor/types.go +++ b/protoc-gen-grpc-gateway/descriptor/types.go @@ -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. diff --git a/protoc-gen-swagger/genswagger/template.go b/protoc-gen-swagger/genswagger/template.go index 8c2dd5fcb2c..f0b37850f1a 100644 --- a/protoc-gen-swagger/genswagger/template.go +++ b/protoc-gen-swagger/genswagger/template.go @@ -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 @@ -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. @@ -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, } } } @@ -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, @@ -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)