...

Source file src/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/genswagger/template.go

Documentation: github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/genswagger

     1  package genswagger
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"math"
     9  	"net/textproto"
    10  	"os"
    11  	"reflect"
    12  	"regexp"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"text/template"
    18  	"time"
    19  
    20  	"github.com/golang/glog"
    21  	"github.com/golang/protobuf/jsonpb"
    22  	"github.com/golang/protobuf/proto"
    23  	pbdescriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
    24  	structpb "github.com/golang/protobuf/ptypes/struct"
    25  	"github.com/grpc-ecosystem/grpc-gateway/internal/casing"
    26  	"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
    27  	swagger_options "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options"
    28  )
    29  
    30  var wktSchemas = map[string]schemaCore{
    31  	".google.protobuf.Timestamp": schemaCore{
    32  		Type:   "string",
    33  		Format: "date-time",
    34  	},
    35  	".google.protobuf.Duration": schemaCore{
    36  		Type: "string",
    37  	},
    38  	".google.protobuf.StringValue": schemaCore{
    39  		Type: "string",
    40  	},
    41  	".google.protobuf.BytesValue": schemaCore{
    42  		Type:   "string",
    43  		Format: "byte",
    44  	},
    45  	".google.protobuf.Int32Value": schemaCore{
    46  		Type:   "integer",
    47  		Format: "int32",
    48  	},
    49  	".google.protobuf.UInt32Value": schemaCore{
    50  		Type:   "integer",
    51  		Format: "int64",
    52  	},
    53  	".google.protobuf.Int64Value": schemaCore{
    54  		Type:   "string",
    55  		Format: "int64",
    56  	},
    57  	".google.protobuf.UInt64Value": schemaCore{
    58  		Type:   "string",
    59  		Format: "uint64",
    60  	},
    61  	".google.protobuf.FloatValue": schemaCore{
    62  		Type:   "number",
    63  		Format: "float",
    64  	},
    65  	".google.protobuf.DoubleValue": schemaCore{
    66  		Type:   "number",
    67  		Format: "double",
    68  	},
    69  	".google.protobuf.BoolValue": schemaCore{
    70  		Type: "boolean",
    71  	},
    72  	".google.protobuf.Empty": schemaCore{},
    73  	".google.protobuf.Struct": schemaCore{
    74  		Type: "object",
    75  	},
    76  	".google.protobuf.Value": schemaCore{
    77  		Type: "object",
    78  	},
    79  	".google.protobuf.ListValue": schemaCore{
    80  		Type: "array",
    81  		Items: (*swaggerItemsObject)(&schemaCore{
    82  			Type: "object",
    83  		}),
    84  	},
    85  	".google.protobuf.NullValue": schemaCore{
    86  		Type: "string",
    87  	},
    88  }
    89  
    90  func listEnumNames(enum *descriptor.Enum) (names []string) {
    91  	for _, value := range enum.GetValue() {
    92  		names = append(names, value.GetName())
    93  	}
    94  	return names
    95  }
    96  
    97  func listEnumNumbers(enum *descriptor.Enum) (numbers []string) {
    98  	for _, value := range enum.GetValue() {
    99  		numbers = append(numbers, strconv.Itoa(int(value.GetNumber())))
   100  	}
   101  	return
   102  }
   103  
   104  func getEnumDefault(enum *descriptor.Enum) string {
   105  	for _, value := range enum.GetValue() {
   106  		if value.GetNumber() == 0 {
   107  			return value.GetName()
   108  		}
   109  	}
   110  	return ""
   111  }
   112  
   113  // messageToQueryParameters converts a message to a list of swagger query parameters.
   114  func messageToQueryParameters(message *descriptor.Message, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body) (params []swaggerParameterObject, err error) {
   115  	for _, field := range message.Fields {
   116  		p, err := queryParams(message, field, "", reg, pathParams, body)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		params = append(params, p...)
   121  	}
   122  	return params, nil
   123  }
   124  
   125  // queryParams converts a field to a list of swagger query parameters recursively through the use of nestedQueryParams.
   126  func queryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body) (params []swaggerParameterObject, err error) {
   127  	return nestedQueryParams(message, field, prefix, reg, pathParams, body, map[string]bool{})
   128  }
   129  
   130  // nestedQueryParams converts a field to a list of swagger query parameters recursively.
   131  // This function is a helper function for queryParams, that keeps track of cyclical message references
   132  //  through the use of
   133  //      touched map[string]bool
   134  // If a cycle is discovered, an error is returned, as cyclical data structures aren't allowed
   135  //  in query parameters.
   136  func nestedQueryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, touchedIn map[string]bool) (params []swaggerParameterObject, err error) {
   137  	// make sure the parameter is not already listed as a path parameter
   138  	for _, pathParam := range pathParams {
   139  		if pathParam.Target == field {
   140  			return nil, nil
   141  		}
   142  	}
   143  	// make sure the parameter is not already listed as a body parameter
   144  	if body != nil {
   145  		if body.FieldPath == nil {
   146  			return nil, nil
   147  		}
   148  		for _, fieldPath := range body.FieldPath {
   149  			if fieldPath.Target == field {
   150  				return nil, nil
   151  			}
   152  		}
   153  	}
   154  	schema := schemaOfField(field, reg, nil)
   155  	fieldType := field.GetTypeName()
   156  	if message.File != nil {
   157  		comments := fieldProtoComments(reg, message, field)
   158  		if err := updateSwaggerDataFromComments(reg, &schema, message, comments, false); err != nil {
   159  			return nil, err
   160  		}
   161  	}
   162  
   163  	isEnum := field.GetType() == pbdescriptor.FieldDescriptorProto_TYPE_ENUM
   164  	items := schema.Items
   165  	if schema.Type != "" || isEnum {
   166  		if schema.Type == "object" {
   167  			return nil, nil // TODO: currently, mapping object in query parameter is not supported
   168  		}
   169  		if items != nil && (items.Type == "" || items.Type == "object") && !isEnum {
   170  			return nil, nil // TODO: currently, mapping object in query parameter is not supported
   171  		}
   172  		desc := schema.Description
   173  		if schema.Title != "" { // merge title because title of parameter object will be ignored
   174  			desc = strings.TrimSpace(schema.Title + ". " + schema.Description)
   175  		}
   176  
   177  		// verify if the field is required
   178  		required := false
   179  		for _, fieldName := range schema.Required {
   180  			if fieldName == field.GetName() {
   181  				required = true
   182  				break
   183  			}
   184  		}
   185  
   186  		param := swaggerParameterObject{
   187  			Description: desc,
   188  			In:          "query",
   189  			Default:     schema.Default,
   190  			Type:        schema.Type,
   191  			Items:       schema.Items,
   192  			Format:      schema.Format,
   193  			Required:    required,
   194  		}
   195  		if param.Type == "array" {
   196  			param.CollectionFormat = "multi"
   197  		}
   198  
   199  		if reg.GetUseJSONNamesForFields() {
   200  			param.Name = prefix + field.GetJsonName()
   201  		} else {
   202  			param.Name = prefix + field.GetName()
   203  		}
   204  
   205  		if isEnum {
   206  			enum, err := reg.LookupEnum("", fieldType)
   207  			if err != nil {
   208  				return nil, fmt.Errorf("unknown enum type %s", fieldType)
   209  			}
   210  			if items != nil { // array
   211  				param.Items = &swaggerItemsObject{
   212  					Type: "string",
   213  					Enum: listEnumNames(enum),
   214  				}
   215  				if reg.GetEnumsAsInts() {
   216  					param.Items.Type = "integer"
   217  					param.Items.Enum = listEnumNumbers(enum)
   218  				}
   219  			} else {
   220  				param.Type = "string"
   221  				param.Enum = listEnumNames(enum)
   222  				param.Default = getEnumDefault(enum)
   223  				if reg.GetEnumsAsInts() {
   224  					param.Type = "integer"
   225  					param.Enum = listEnumNumbers(enum)
   226  					param.Default = "0"
   227  				}
   228  			}
   229  			valueComments := enumValueProtoComments(reg, enum)
   230  			if valueComments != "" {
   231  				param.Description = strings.TrimLeft(param.Description+"\n\n "+valueComments, "\n")
   232  			}
   233  		}
   234  		return []swaggerParameterObject{param}, nil
   235  	}
   236  
   237  	// nested type, recurse
   238  	msg, err := reg.LookupMsg("", fieldType)
   239  	if err != nil {
   240  		return nil, fmt.Errorf("unknown message type %s", fieldType)
   241  	}
   242  
   243  	// Check for cyclical message reference:
   244  	isCycle := touchedIn[*msg.Name]
   245  	if isCycle {
   246  		return nil, fmt.Errorf("Recursive types are not allowed for query parameters, cycle found on %q", fieldType)
   247  	}
   248  
   249  	// Construct a new map with the message name so a cycle further down the recursive path can be detected.
   250  	// Do not keep anything in the original touched reference and do not pass that reference along.  This will
   251  	// prevent clobbering adjacent records while recursing.
   252  	touchedOut := make(map[string]bool)
   253  	for k, v := range touchedIn {
   254  		touchedOut[k] = v
   255  	}
   256  	touchedOut[*msg.Name] = true
   257  
   258  	for _, nestedField := range msg.Fields {
   259  		var fieldName string
   260  		if reg.GetUseJSONNamesForFields() {
   261  			fieldName = field.GetJsonName()
   262  		} else {
   263  			fieldName = field.GetName()
   264  		}
   265  		p, err := nestedQueryParams(msg, nestedField, prefix+fieldName+".", reg, pathParams, body, touchedOut)
   266  		if err != nil {
   267  			return nil, err
   268  		}
   269  		params = append(params, p...)
   270  	}
   271  	return params, nil
   272  }
   273  
   274  // findServicesMessagesAndEnumerations discovers all messages and enums defined in the RPC methods of the service.
   275  func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descriptor.Registry, m messageMap, ms messageMap, e enumMap, refs refMap) {
   276  	for _, svc := range s {
   277  		for _, meth := range svc.Methods {
   278  			// Request may be fully included in query
   279  			{
   280  				swgReqName, ok := fullyQualifiedNameToSwaggerName(meth.RequestType.FQMN(), reg)
   281  				if !ok {
   282  					glog.Errorf("couldn't resolve swagger name for FQMN '%v'", meth.RequestType.FQMN())
   283  					continue
   284  				}
   285  				if _, ok := refs[fmt.Sprintf("#/definitions/%s", swgReqName)]; ok {
   286  					if !skipRenderingRef(meth.RequestType.FQMN()) {
   287  						m[swgReqName] = meth.RequestType
   288  					}
   289  				}
   290  			}
   291  
   292  			swgRspName, ok := fullyQualifiedNameToSwaggerName(meth.ResponseType.FQMN(), reg)
   293  			if !ok && !skipRenderingRef(meth.ResponseType.FQMN()) {
   294  				glog.Errorf("couldn't resolve swagger name for FQMN '%v'", meth.ResponseType.FQMN())
   295  				continue
   296  			}
   297  
   298  			findNestedMessagesAndEnumerations(meth.RequestType, reg, m, e)
   299  
   300  			if !skipRenderingRef(meth.ResponseType.FQMN()) {
   301  				m[swgRspName] = meth.ResponseType
   302  				if meth.GetServerStreaming() {
   303  					streamError, runtimeStreamError, err := lookupMsgAndSwaggerName(".grpc.gateway.runtime", "StreamError", reg)
   304  					if err != nil {
   305  						glog.Error(err)
   306  					} else {
   307  						glog.V(1).Infof("StreamError: %v", streamError)
   308  						glog.V(1).Infof("StreamError FQMN: %s", runtimeStreamError)
   309  						m[runtimeStreamError] = streamError
   310  						findNestedMessagesAndEnumerations(streamError, reg, m, e)
   311  					}
   312  					ms[swgRspName] = meth.ResponseType
   313  				}
   314  			}
   315  			findNestedMessagesAndEnumerations(meth.ResponseType, reg, m, e)
   316  		}
   317  	}
   318  }
   319  
   320  // findNestedMessagesAndEnumerations those can be generated by the services.
   321  func findNestedMessagesAndEnumerations(message *descriptor.Message, reg *descriptor.Registry, m messageMap, e enumMap) {
   322  	// Iterate over all the fields that
   323  	for _, t := range message.Fields {
   324  		fieldType := t.GetTypeName()
   325  		// If the type is an empty string then it is a proto primitive
   326  		if fieldType != "" {
   327  			if _, ok := m[fieldType]; !ok {
   328  				msg, err := reg.LookupMsg("", fieldType)
   329  				if err != nil {
   330  					enum, err := reg.LookupEnum("", fieldType)
   331  					if err != nil {
   332  						panic(err)
   333  					}
   334  					e[fieldType] = enum
   335  					continue
   336  				}
   337  				m[fieldType] = msg
   338  				findNestedMessagesAndEnumerations(msg, reg, m, e)
   339  			}
   340  		}
   341  	}
   342  }
   343  
   344  func skipRenderingRef(refName string) bool {
   345  	_, ok := wktSchemas[refName]
   346  	return ok
   347  }
   348  
   349  func renderMessagesAsDefinition(messages messageMap, d swaggerDefinitionsObject, reg *descriptor.Registry, customRefs refMap) {
   350  	for name, msg := range messages {
   351  		swgName, ok := fullyQualifiedNameToSwaggerName(msg.FQMN(), reg)
   352  		if !ok {
   353  			panic(fmt.Sprintf("can't resolve swagger name from '%v'", msg.FQMN()))
   354  		}
   355  		if skipRenderingRef(name) {
   356  			continue
   357  		}
   358  
   359  		if opt := msg.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
   360  			continue
   361  		}
   362  		schema := swaggerSchemaObject{
   363  			schemaCore: schemaCore{
   364  				Type: "object",
   365  			},
   366  		}
   367  		msgComments := protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index))
   368  		if err := updateSwaggerDataFromComments(reg, &schema, msg, msgComments, false); err != nil {
   369  			panic(err)
   370  		}
   371  		opts, err := extractSchemaOptionFromMessageDescriptor(msg.DescriptorProto)
   372  		if err != nil {
   373  			panic(err)
   374  		}
   375  		if opts != nil {
   376  			protoSchema := swaggerSchemaFromProtoSchema(opts, reg, customRefs, msg)
   377  
   378  			// Warning: Make sure not to overwrite any fields already set on the schema type.
   379  			schema.ExternalDocs = protoSchema.ExternalDocs
   380  			schema.ReadOnly = protoSchema.ReadOnly
   381  			schema.MultipleOf = protoSchema.MultipleOf
   382  			schema.Maximum = protoSchema.Maximum
   383  			schema.ExclusiveMaximum = protoSchema.ExclusiveMaximum
   384  			schema.Minimum = protoSchema.Minimum
   385  			schema.ExclusiveMinimum = protoSchema.ExclusiveMinimum
   386  			schema.MaxLength = protoSchema.MaxLength
   387  			schema.MinLength = protoSchema.MinLength
   388  			schema.Pattern = protoSchema.Pattern
   389  			schema.Default = protoSchema.Default
   390  			schema.MaxItems = protoSchema.MaxItems
   391  			schema.MinItems = protoSchema.MinItems
   392  			schema.UniqueItems = protoSchema.UniqueItems
   393  			schema.MaxProperties = protoSchema.MaxProperties
   394  			schema.MinProperties = protoSchema.MinProperties
   395  			schema.Required = protoSchema.Required
   396  			if protoSchema.schemaCore.Type != "" || protoSchema.schemaCore.Ref != "" {
   397  				schema.schemaCore = protoSchema.schemaCore
   398  			}
   399  			if protoSchema.Title != "" {
   400  				schema.Title = protoSchema.Title
   401  			}
   402  			if protoSchema.Description != "" {
   403  				schema.Description = protoSchema.Description
   404  			}
   405  			if protoSchema.Example != nil {
   406  				schema.Example = protoSchema.Example
   407  			}
   408  		}
   409  
   410  		for _, f := range msg.Fields {
   411  			fieldValue := schemaOfField(f, reg, customRefs)
   412  			comments := fieldProtoComments(reg, msg, f)
   413  			if err := updateSwaggerDataFromComments(reg, &fieldValue, f, comments, false); err != nil {
   414  				panic(err)
   415  			}
   416  
   417  			kv := keyVal{Value: fieldValue}
   418  			if reg.GetUseJSONNamesForFields() {
   419  				kv.Key = f.GetJsonName()
   420  			} else {
   421  				kv.Key = f.GetName()
   422  			}
   423  			if schema.Properties == nil {
   424  				schema.Properties = &swaggerSchemaObjectProperties{}
   425  			}
   426  			*schema.Properties = append(*schema.Properties, kv)
   427  		}
   428  		d[swgName] = schema
   429  	}
   430  }
   431  
   432  // schemaOfField returns a swagger Schema Object for a protobuf field.
   433  func schemaOfField(f *descriptor.Field, reg *descriptor.Registry, refs refMap) swaggerSchemaObject {
   434  	const (
   435  		singular = 0
   436  		array    = 1
   437  		object   = 2
   438  	)
   439  	var (
   440  		core      schemaCore
   441  		aggregate int
   442  	)
   443  
   444  	fd := f.FieldDescriptorProto
   445  	if m, err := reg.LookupMsg("", f.GetTypeName()); err == nil {
   446  		if opt := m.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
   447  			fd = m.GetField()[1]
   448  			aggregate = object
   449  		}
   450  	}
   451  	if fd.GetLabel() == pbdescriptor.FieldDescriptorProto_LABEL_REPEATED {
   452  		aggregate = array
   453  	}
   454  
   455  	var props *swaggerSchemaObjectProperties
   456  
   457  	switch ft := fd.GetType(); ft {
   458  	case pbdescriptor.FieldDescriptorProto_TYPE_ENUM, pbdescriptor.FieldDescriptorProto_TYPE_MESSAGE, pbdescriptor.FieldDescriptorProto_TYPE_GROUP:
   459  		if wktSchema, ok := wktSchemas[fd.GetTypeName()]; ok {
   460  			core = wktSchema
   461  
   462  			if fd.GetTypeName() == ".google.protobuf.Empty" {
   463  				props = &swaggerSchemaObjectProperties{}
   464  			}
   465  		} else {
   466  			swgRef, ok := fullyQualifiedNameToSwaggerName(fd.GetTypeName(), reg)
   467  			if !ok {
   468  				panic(fmt.Sprintf("can't resolve swagger ref from typename '%v'", fd.GetTypeName()))
   469  			}
   470  			core = schemaCore{
   471  				Ref: "#/definitions/" + swgRef,
   472  			}
   473  			if refs != nil {
   474  				refs[fd.GetTypeName()] = struct{}{}
   475  			}
   476  		}
   477  	default:
   478  		ftype, format, ok := primitiveSchema(ft)
   479  		if ok {
   480  			core = schemaCore{Type: ftype, Format: format}
   481  		} else {
   482  			core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
   483  		}
   484  	}
   485  
   486  	ret := swaggerSchemaObject{}
   487  
   488  	switch aggregate {
   489  	case array:
   490  		ret = swaggerSchemaObject{
   491  			schemaCore: schemaCore{
   492  				Type:  "array",
   493  				Items: (*swaggerItemsObject)(&core),
   494  			},
   495  		}
   496  	case object:
   497  		ret = swaggerSchemaObject{
   498  			schemaCore: schemaCore{
   499  				Type: "object",
   500  			},
   501  			AdditionalProperties: &swaggerSchemaObject{Properties: props, schemaCore: core},
   502  		}
   503  	default:
   504  		ret = swaggerSchemaObject{
   505  			schemaCore: core,
   506  			Properties: props,
   507  		}
   508  	}
   509  
   510  	if j, err := extractJSONSchemaFromFieldDescriptor(f.FieldDescriptorProto); err == nil {
   511  		updateSwaggerObjectFromJSONSchema(&ret, j, reg, f)
   512  	}
   513  
   514  	return ret
   515  }
   516  
   517  // primitiveSchema returns a pair of "Type" and "Format" in JSON Schema for
   518  // the given primitive field type.
   519  // The last return parameter is true iff the field type is actually primitive.
   520  func primitiveSchema(t pbdescriptor.FieldDescriptorProto_Type) (ftype, format string, ok bool) {
   521  	switch t {
   522  	case pbdescriptor.FieldDescriptorProto_TYPE_DOUBLE:
   523  		return "number", "double", true
   524  	case pbdescriptor.FieldDescriptorProto_TYPE_FLOAT:
   525  		return "number", "float", true
   526  	case pbdescriptor.FieldDescriptorProto_TYPE_INT64:
   527  		return "string", "int64", true
   528  	case pbdescriptor.FieldDescriptorProto_TYPE_UINT64:
   529  		// 64bit integer types are marshaled as string in the default JSONPb marshaler.
   530  		// TODO(yugui) Add an option to declare 64bit integers as int64.
   531  		//
   532  		// NOTE: uint64 is not a predefined format of integer type in Swagger spec.
   533  		// So we cannot expect that uint64 is commonly supported by swagger processor.
   534  		return "string", "uint64", true
   535  	case pbdescriptor.FieldDescriptorProto_TYPE_INT32:
   536  		return "integer", "int32", true
   537  	case pbdescriptor.FieldDescriptorProto_TYPE_FIXED64:
   538  		// Ditto.
   539  		return "string", "uint64", true
   540  	case pbdescriptor.FieldDescriptorProto_TYPE_FIXED32:
   541  		// Ditto.
   542  		return "integer", "int64", true
   543  	case pbdescriptor.FieldDescriptorProto_TYPE_BOOL:
   544  		// NOTE: in swagger specification, format should be empty on boolean type
   545  		return "boolean", "", true
   546  	case pbdescriptor.FieldDescriptorProto_TYPE_STRING:
   547  		// NOTE: in swagger specification, format should be empty on string type
   548  		return "string", "", true
   549  	case pbdescriptor.FieldDescriptorProto_TYPE_BYTES:
   550  		return "string", "byte", true
   551  	case pbdescriptor.FieldDescriptorProto_TYPE_UINT32:
   552  		// Ditto.
   553  		return "integer", "int64", true
   554  	case pbdescriptor.FieldDescriptorProto_TYPE_SFIXED32:
   555  		return "integer", "int32", true
   556  	case pbdescriptor.FieldDescriptorProto_TYPE_SFIXED64:
   557  		return "string", "int64", true
   558  	case pbdescriptor.FieldDescriptorProto_TYPE_SINT32:
   559  		return "integer", "int32", true
   560  	case pbdescriptor.FieldDescriptorProto_TYPE_SINT64:
   561  		return "string", "int64", true
   562  	default:
   563  		return "", "", false
   564  	}
   565  }
   566  
   567  // renderEnumerationsAsDefinition inserts enums into the definitions object.
   568  func renderEnumerationsAsDefinition(enums enumMap, d swaggerDefinitionsObject, reg *descriptor.Registry) {
   569  	for _, enum := range enums {
   570  		swgName, ok := fullyQualifiedNameToSwaggerName(enum.FQEN(), reg)
   571  		if !ok {
   572  			panic(fmt.Sprintf("can't resolve swagger name from FQEN '%v'", enum.FQEN()))
   573  		}
   574  		enumComments := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index))
   575  
   576  		// it may be necessary to sort the result of the GetValue function.
   577  		enumNames := listEnumNames(enum)
   578  		defaultValue := getEnumDefault(enum)
   579  		valueComments := enumValueProtoComments(reg, enum)
   580  		if valueComments != "" {
   581  			enumComments = strings.TrimLeft(enumComments+"\n\n "+valueComments, "\n")
   582  		}
   583  		enumSchemaObject := swaggerSchemaObject{
   584  			schemaCore: schemaCore{
   585  				Type:    "string",
   586  				Enum:    enumNames,
   587  				Default: defaultValue,
   588  			},
   589  		}
   590  		if reg.GetEnumsAsInts() {
   591  			enumSchemaObject.Type = "integer"
   592  			enumSchemaObject.Format = "int32"
   593  			enumSchemaObject.Default = "0"
   594  			enumSchemaObject.Enum = listEnumNumbers(enum)
   595  		}
   596  		if err := updateSwaggerDataFromComments(reg, &enumSchemaObject, enum, enumComments, false); err != nil {
   597  			panic(err)
   598  		}
   599  
   600  		d[swgName] = enumSchemaObject
   601  	}
   602  }
   603  
   604  // Take in a FQMN or FQEN and return a swagger safe version of the FQMN and
   605  // a boolean indicating if FQMN was properly resolved.
   606  func fullyQualifiedNameToSwaggerName(fqn string, reg *descriptor.Registry) (string, bool) {
   607  	registriesSeenMutex.Lock()
   608  	defer registriesSeenMutex.Unlock()
   609  	if mapping, present := registriesSeen[reg]; present {
   610  		ret, ok := mapping[fqn]
   611  		return ret, ok
   612  	}
   613  	mapping := resolveFullyQualifiedNameToSwaggerNames(append(reg.GetAllFQMNs(), reg.GetAllFQENs()...), reg.GetUseFQNForSwaggerName())
   614  	registriesSeen[reg] = mapping
   615  	ret, ok := mapping[fqn]
   616  	return ret, ok
   617  }
   618  
   619  // Lookup message type by location.name and return a swagger-safe version
   620  // of its FQMN.
   621  func lookupMsgAndSwaggerName(location, name string, reg *descriptor.Registry) (*descriptor.Message, string, error) {
   622  	msg, err := reg.LookupMsg(location, name)
   623  	if err != nil {
   624  		return nil, "", err
   625  	}
   626  	swgName, ok := fullyQualifiedNameToSwaggerName(msg.FQMN(), reg)
   627  	if !ok {
   628  		return nil, "", fmt.Errorf("can't map swagger name from FQMN '%v'", msg.FQMN())
   629  	}
   630  	return msg, swgName, nil
   631  }
   632  
   633  // registriesSeen is used to memoise calls to resolveFullyQualifiedNameToSwaggerNames so
   634  // we don't repeat it unnecessarily, since it can take some time.
   635  var registriesSeen = map[*descriptor.Registry]map[string]string{}
   636  var registriesSeenMutex sync.Mutex
   637  
   638  // Take the names of every proto and "uniq-ify" them. The idea is to produce a
   639  // set of names that meet a couple of conditions. They must be stable, they
   640  // must be unique, and they must be shorter than the FQN.
   641  //
   642  // This likely could be made better. This will always generate the same names
   643  // but may not always produce optimal names. This is a reasonably close
   644  // approximation of what they should look like in most cases.
   645  func resolveFullyQualifiedNameToSwaggerNames(messages []string, useFQNForSwaggerName bool) map[string]string {
   646  	packagesByDepth := make(map[int][][]string)
   647  	uniqueNames := make(map[string]string)
   648  
   649  	hierarchy := func(pkg string) []string {
   650  		return strings.Split(pkg, ".")
   651  	}
   652  
   653  	for _, p := range messages {
   654  		h := hierarchy(p)
   655  		for depth := range h {
   656  			if _, ok := packagesByDepth[depth]; !ok {
   657  				packagesByDepth[depth] = make([][]string, 0)
   658  			}
   659  			packagesByDepth[depth] = append(packagesByDepth[depth], h[len(h)-depth:])
   660  		}
   661  	}
   662  
   663  	count := func(list [][]string, item []string) int {
   664  		i := 0
   665  		for _, element := range list {
   666  			if reflect.DeepEqual(element, item) {
   667  				i++
   668  			}
   669  		}
   670  		return i
   671  	}
   672  
   673  	for _, p := range messages {
   674  		if useFQNForSwaggerName {
   675  			// strip leading dot from proto fqn
   676  			uniqueNames[p] = p[1:]
   677  		} else {
   678  			h := hierarchy(p)
   679  			for depth := 0; depth < len(h); depth++ {
   680  				if count(packagesByDepth[depth], h[len(h)-depth:]) == 1 {
   681  					uniqueNames[p] = strings.Join(h[len(h)-depth-1:], "")
   682  					break
   683  				}
   684  				if depth == len(h)-1 {
   685  					uniqueNames[p] = strings.Join(h, "")
   686  				}
   687  			}
   688  		}
   689  	}
   690  	return uniqueNames
   691  }
   692  
   693  var canRegexp = regexp.MustCompile("{([a-zA-Z][a-zA-Z0-9_.]*).*}")
   694  
   695  // Swagger expects paths of the form /path/{string_value} but grpc-gateway paths are expected to be of the form /path/{string_value=strprefix/*}. This should reformat it correctly.
   696  func templateToSwaggerPath(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message) string {
   697  	// It seems like the right thing to do here is to just use
   698  	// strings.Split(path, "/") but that breaks badly when you hit a url like
   699  	// /{my_field=prefix/*}/ and end up with 2 sections representing my_field.
   700  	// Instead do the right thing and write a small pushdown (counter) automata
   701  	// for it.
   702  	var parts []string
   703  	depth := 0
   704  	buffer := ""
   705  	jsonBuffer := ""
   706  	for _, char := range path {
   707  		switch char {
   708  		case '{':
   709  			// Push on the stack
   710  			depth++
   711  			buffer += string(char)
   712  			jsonBuffer = ""
   713  			jsonBuffer += string(char)
   714  			break
   715  		case '}':
   716  			if depth == 0 {
   717  				panic("Encountered } without matching { before it.")
   718  			}
   719  			// Pop from the stack
   720  			depth--
   721  			buffer += string(char)
   722  			if reg.GetUseJSONNamesForFields() &&
   723  				len(jsonBuffer) > 1 {
   724  				jsonSnakeCaseName := string(jsonBuffer[1:])
   725  				jsonCamelCaseName := string(lowerCamelCase(jsonSnakeCaseName, fields, msgs))
   726  				prev := string(buffer[:len(buffer)-len(jsonSnakeCaseName)-2])
   727  				buffer = strings.Join([]string{prev, "{", jsonCamelCaseName, "}"}, "")
   728  				jsonBuffer = ""
   729  			}
   730  		case '/':
   731  			if depth == 0 {
   732  				parts = append(parts, buffer)
   733  				buffer = ""
   734  				// Since the stack was empty when we hit the '/' we are done with this
   735  				// section.
   736  				continue
   737  			}
   738  			buffer += string(char)
   739  			jsonBuffer += string(char)
   740  		default:
   741  			buffer += string(char)
   742  			jsonBuffer += string(char)
   743  			break
   744  		}
   745  	}
   746  
   747  	// Now append the last element to parts
   748  	parts = append(parts, buffer)
   749  
   750  	// Parts is now an array of segments of the path. Interestingly, since the
   751  	// syntax for this subsection CAN be handled by a regexp since it has no
   752  	// memory.
   753  	for index, part := range parts {
   754  		// If part is a resource name such as "parent", "name", "user.name", the format info must be retained.
   755  		prefix := canRegexp.ReplaceAllString(part, "$1")
   756  		if isResourceName(prefix) {
   757  			continue
   758  		}
   759  		parts[index] = canRegexp.ReplaceAllString(part, "{$1}")
   760  	}
   761  
   762  	return strings.Join(parts, "/")
   763  }
   764  
   765  func isResourceName(prefix string) bool {
   766  	words := strings.Split(prefix, ".")
   767  	l := len(words)
   768  	field := words[l-1]
   769  	words = strings.Split(field, ":")
   770  	field = words[0]
   771  	return field == "parent" || field == "name"
   772  }
   773  
   774  func renderServices(services []*descriptor.Service, paths swaggerPathsObject, reg *descriptor.Registry, requestResponseRefs, customRefs refMap, msgs []*descriptor.Message) error {
   775  	// Correctness of svcIdx and methIdx depends on 'services' containing the services in the same order as the 'file.Service' array.
   776  	svcBaseIdx := 0
   777  	var lastFile *descriptor.File = nil
   778  	for svcIdx, svc := range services {
   779  		if svc.File != lastFile {
   780  			lastFile = svc.File
   781  			svcBaseIdx = svcIdx
   782  		}
   783  		for methIdx, meth := range svc.Methods {
   784  			for bIdx, b := range meth.Bindings {
   785  				// Iterate over all the swagger parameters
   786  				parameters := swaggerParametersObject{}
   787  				for _, parameter := range b.PathParams {
   788  
   789  					var paramType, paramFormat, desc, collectionFormat, defaultValue string
   790  					var enumNames []string
   791  					var items *swaggerItemsObject
   792  					var minItems *int
   793  					switch pt := parameter.Target.GetType(); pt {
   794  					case pbdescriptor.FieldDescriptorProto_TYPE_GROUP, pbdescriptor.FieldDescriptorProto_TYPE_MESSAGE:
   795  						if descriptor.IsWellKnownType(parameter.Target.GetTypeName()) {
   796  							if parameter.IsRepeated() {
   797  								return fmt.Errorf("only primitive and enum types are allowed in repeated path parameters")
   798  							}
   799  							schema := schemaOfField(parameter.Target, reg, customRefs)
   800  							paramType = schema.Type
   801  							paramFormat = schema.Format
   802  							desc = schema.Description
   803  							defaultValue = schema.Default
   804  						} else {
   805  							return fmt.Errorf("only primitive and well-known types are allowed in path parameters")
   806  						}
   807  					case pbdescriptor.FieldDescriptorProto_TYPE_ENUM:
   808  						enum, err := reg.LookupEnum("", parameter.Target.GetTypeName())
   809  						if err != nil {
   810  							return err
   811  						}
   812  						paramType = "string"
   813  						paramFormat = ""
   814  						enumNames = listEnumNames(enum)
   815  						if reg.GetEnumsAsInts() {
   816  							paramType = "integer"
   817  							paramFormat = ""
   818  							enumNames = listEnumNumbers(enum)
   819  						}
   820  						schema := schemaOfField(parameter.Target, reg, customRefs)
   821  						desc = schema.Description
   822  						defaultValue = schema.Default
   823  					default:
   824  						var ok bool
   825  						paramType, paramFormat, ok = primitiveSchema(pt)
   826  						if !ok {
   827  							return fmt.Errorf("unknown field type %v", pt)
   828  						}
   829  
   830  						schema := schemaOfField(parameter.Target, reg, customRefs)
   831  						desc = schema.Description
   832  						defaultValue = schema.Default
   833  					}
   834  
   835  					if parameter.IsRepeated() {
   836  						core := schemaCore{Type: paramType, Format: paramFormat}
   837  						if parameter.IsEnum() {
   838  							var s []string
   839  							core.Enum = enumNames
   840  							enumNames = s
   841  						}
   842  						items = (*swaggerItemsObject)(&core)
   843  						paramType = "array"
   844  						paramFormat = ""
   845  						collectionFormat = reg.GetRepeatedPathParamSeparatorName()
   846  						minItems = new(int)
   847  						*minItems = 1
   848  					}
   849  
   850  					if desc == "" {
   851  						desc = fieldProtoComments(reg, parameter.Target.Message, parameter.Target)
   852  					}
   853  					parameterString := parameter.String()
   854  					if reg.GetUseJSONNamesForFields() {
   855  						parameterString = lowerCamelCase(parameterString, meth.RequestType.Fields, msgs)
   856  					}
   857  					parameters = append(parameters, swaggerParameterObject{
   858  						Name:        parameterString,
   859  						Description: desc,
   860  						In:          "path",
   861  						Required:    true,
   862  						Default:     defaultValue,
   863  						// Parameters in gRPC-Gateway can only be strings?
   864  						Type:             paramType,
   865  						Format:           paramFormat,
   866  						Enum:             enumNames,
   867  						Items:            items,
   868  						CollectionFormat: collectionFormat,
   869  						MinItems:         minItems,
   870  					})
   871  				}
   872  				// Now check if there is a body parameter
   873  				if b.Body != nil {
   874  					var schema swaggerSchemaObject
   875  					desc := ""
   876  
   877  					if len(b.Body.FieldPath) == 0 {
   878  						schema = swaggerSchemaObject{
   879  							schemaCore: schemaCore{},
   880  						}
   881  
   882  						wknSchemaCore, isWkn := wktSchemas[meth.RequestType.FQMN()]
   883  						if !isWkn {
   884  							err := schema.setRefFromFQN(meth.RequestType.FQMN(), reg)
   885  							if err != nil {
   886  								return err
   887  							}
   888  						} else {
   889  							schema.schemaCore = wknSchemaCore
   890  
   891  							// Special workaround for Empty: it's well-known type but wknSchemas only returns schema.schemaCore; but we need to set schema.Properties which is a level higher.
   892  							if meth.RequestType.FQMN() == ".google.protobuf.Empty" {
   893  								schema.Properties = &swaggerSchemaObjectProperties{}
   894  							}
   895  						}
   896  					} else {
   897  						lastField := b.Body.FieldPath[len(b.Body.FieldPath)-1]
   898  						schema = schemaOfField(lastField.Target, reg, customRefs)
   899  						if schema.Description != "" {
   900  							desc = schema.Description
   901  						} else {
   902  							desc = fieldProtoComments(reg, lastField.Target.Message, lastField.Target)
   903  						}
   904  					}
   905  
   906  					if meth.GetClientStreaming() {
   907  						desc += " (streaming inputs)"
   908  					}
   909  					parameters = append(parameters, swaggerParameterObject{
   910  						Name:        "body",
   911  						Description: desc,
   912  						In:          "body",
   913  						Required:    true,
   914  						Schema:      &schema,
   915  					})
   916  					// add the parameters to the query string
   917  					queryParams, err := messageToQueryParameters(meth.RequestType, reg, b.PathParams, b.Body)
   918  					if err != nil {
   919  						return err
   920  					}
   921  					parameters = append(parameters, queryParams...)
   922  				} else if b.HTTPMethod == "GET" || b.HTTPMethod == "DELETE" {
   923  					// add the parameters to the query string
   924  					queryParams, err := messageToQueryParameters(meth.RequestType, reg, b.PathParams, b.Body)
   925  					if err != nil {
   926  						return err
   927  					}
   928  					parameters = append(parameters, queryParams...)
   929  				}
   930  
   931  				pathItemObject, ok := paths[templateToSwaggerPath(b.PathTmpl.Template, reg, meth.RequestType.Fields, msgs)]
   932  				if !ok {
   933  					pathItemObject = swaggerPathItemObject{}
   934  				}
   935  
   936  				methProtoPath := protoPathIndex(reflect.TypeOf((*pbdescriptor.ServiceDescriptorProto)(nil)), "Method")
   937  				desc := "A successful response."
   938  				var responseSchema swaggerSchemaObject
   939  
   940  				if b.ResponseBody == nil || len(b.ResponseBody.FieldPath) == 0 {
   941  					responseSchema = swaggerSchemaObject{
   942  						schemaCore: schemaCore{},
   943  					}
   944  
   945  					// Don't link to a full definition for
   946  					// empty; it's overly verbose.
   947  					// schema.Properties{} renders it as
   948  					// well, without a definition
   949  					wknSchemaCore, isWkn := wktSchemas[meth.ResponseType.FQMN()]
   950  					if !isWkn {
   951  						err := responseSchema.setRefFromFQN(meth.ResponseType.FQMN(), reg)
   952  						if err != nil {
   953  							return err
   954  						}
   955  					} else {
   956  						responseSchema.schemaCore = wknSchemaCore
   957  
   958  						// Special workaround for Empty: it's well-known type but wknSchemas only returns schema.schemaCore; but we need to set schema.Properties which is a level higher.
   959  						if meth.ResponseType.FQMN() == ".google.protobuf.Empty" {
   960  							responseSchema.Properties = &swaggerSchemaObjectProperties{}
   961  						}
   962  					}
   963  				} else {
   964  					// This is resolving the value of response_body in the google.api.HttpRule
   965  					lastField := b.ResponseBody.FieldPath[len(b.ResponseBody.FieldPath)-1]
   966  					responseSchema = schemaOfField(lastField.Target, reg, customRefs)
   967  					if responseSchema.Description != "" {
   968  						desc = responseSchema.Description
   969  					} else {
   970  						desc = fieldProtoComments(reg, lastField.Target.Message, lastField.Target)
   971  					}
   972  				}
   973  				if meth.GetServerStreaming() {
   974  					desc += "(streaming responses)"
   975  					responseSchema.Type = "object"
   976  					swgRef, _ := fullyQualifiedNameToSwaggerName(meth.ResponseType.FQMN(), reg)
   977  					responseSchema.Title = "Stream result of " + swgRef
   978  
   979  					props := swaggerSchemaObjectProperties{
   980  						keyVal{
   981  							Key: "result",
   982  							Value: swaggerSchemaObject{
   983  								schemaCore: schemaCore{
   984  									Ref: responseSchema.Ref,
   985  								},
   986  							},
   987  						},
   988  					}
   989  					streamErrDef, hasStreamError := fullyQualifiedNameToSwaggerName(".grpc.gateway.runtime.StreamError", reg)
   990  					if hasStreamError {
   991  						props = append(props, keyVal{
   992  							Key: "error",
   993  							Value: swaggerSchemaObject{
   994  								schemaCore: schemaCore{
   995  									Ref: fmt.Sprintf("#/definitions/%s", streamErrDef)},
   996  							},
   997  						})
   998  					}
   999  					responseSchema.Properties = &props
  1000  					responseSchema.Ref = ""
  1001  				}
  1002  
  1003  				tag := svc.GetName()
  1004  				if pkg := svc.File.GetPackage(); pkg != "" && reg.IsIncludePackageInTags() {
  1005  					tag = pkg + "." + tag
  1006  				}
  1007  				operationObject := &swaggerOperationObject{
  1008  					Tags:       []string{tag},
  1009  					Parameters: parameters,
  1010  					Responses: swaggerResponsesObject{
  1011  						"200": swaggerResponseObject{
  1012  							Description: desc,
  1013  							Schema:      responseSchema,
  1014  							Headers:     swaggerHeadersObject{},
  1015  						},
  1016  					},
  1017  				}
  1018  				if !reg.GetDisableDefaultErrors() {
  1019  					errDef, hasErrDef := fullyQualifiedNameToSwaggerName(".grpc.gateway.runtime.Error", reg)
  1020  					if hasErrDef {
  1021  						// https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#responses-object
  1022  						operationObject.Responses["default"] = swaggerResponseObject{
  1023  							Description: "An unexpected error response.",
  1024  							Schema: swaggerSchemaObject{
  1025  								schemaCore: schemaCore{
  1026  									Ref: fmt.Sprintf("#/definitions/%s", errDef),
  1027  								},
  1028  							},
  1029  						}
  1030  					}
  1031  				}
  1032  				operationObject.OperationID = fmt.Sprintf("%s_%s", svc.GetName(), meth.GetName())
  1033  				if reg.GetSimpleOperationIDs() {
  1034  					operationObject.OperationID = fmt.Sprintf("%s", meth.GetName())
  1035  				}
  1036  				if bIdx != 0 {
  1037  					// OperationID must be unique in an OpenAPI v2 definition.
  1038  					operationObject.OperationID += strconv.Itoa(bIdx + 1)
  1039  				}
  1040  
  1041  				// Fill reference map with referenced request messages
  1042  				for _, param := range operationObject.Parameters {
  1043  					if param.Schema != nil && param.Schema.Ref != "" {
  1044  						requestResponseRefs[param.Schema.Ref] = struct{}{}
  1045  					}
  1046  				}
  1047  
  1048  				methComments := protoComments(reg, svc.File, nil, "Service", int32(svcIdx-svcBaseIdx), methProtoPath, int32(methIdx))
  1049  				if err := updateSwaggerDataFromComments(reg, operationObject, meth, methComments, false); err != nil {
  1050  					panic(err)
  1051  				}
  1052  
  1053  				opts, err := extractOperationOptionFromMethodDescriptor(meth.MethodDescriptorProto)
  1054  				if opts != nil {
  1055  					if err != nil {
  1056  						panic(err)
  1057  					}
  1058  					operationObject.ExternalDocs = protoExternalDocumentationToSwaggerExternalDocumentation(opts.ExternalDocs, reg, meth)
  1059  					// TODO(ivucica): this would be better supported by looking whether the method is deprecated in the proto file
  1060  					operationObject.Deprecated = opts.Deprecated
  1061  
  1062  					if opts.Summary != "" {
  1063  						operationObject.Summary = opts.Summary
  1064  					}
  1065  					if opts.Description != "" {
  1066  						operationObject.Description = opts.Description
  1067  					}
  1068  					if len(opts.Tags) > 0 {
  1069  						operationObject.Tags = make([]string, len(opts.Tags))
  1070  						copy(operationObject.Tags, opts.Tags)
  1071  					}
  1072  					if opts.OperationId != "" {
  1073  						operationObject.OperationID = opts.OperationId
  1074  					}
  1075  					if opts.Security != nil {
  1076  						newSecurity := []swaggerSecurityRequirementObject{}
  1077  						if operationObject.Security != nil {
  1078  							newSecurity = *operationObject.Security
  1079  						}
  1080  						for _, secReq := range opts.Security {
  1081  							newSecReq := swaggerSecurityRequirementObject{}
  1082  							for secReqKey, secReqValue := range secReq.SecurityRequirement {
  1083  								if secReqValue == nil {
  1084  									continue
  1085  								}
  1086  
  1087  								newSecReqValue := make([]string, len(secReqValue.Scope))
  1088  								copy(newSecReqValue, secReqValue.Scope)
  1089  								newSecReq[secReqKey] = newSecReqValue
  1090  							}
  1091  
  1092  							if len(newSecReq) > 0 {
  1093  								newSecurity = append(newSecurity, newSecReq)
  1094  							}
  1095  						}
  1096  						operationObject.Security = &newSecurity
  1097  					}
  1098  					if opts.Responses != nil {
  1099  						for name, resp := range opts.Responses {
  1100  							// Merge response data into default response if available.
  1101  							respObj := operationObject.Responses[name]
  1102  							if resp.Description != "" {
  1103  								respObj.Description = resp.Description
  1104  							}
  1105  							if resp.Schema != nil {
  1106  								respObj.Schema = swaggerSchemaFromProtoSchema(resp.Schema, reg, customRefs, meth)
  1107  							}
  1108  							if resp.Examples != nil {
  1109  								respObj.Examples = swaggerExamplesFromProtoExamples(resp.Examples)
  1110  							}
  1111  							if resp.Headers != nil {
  1112  								hdrs, err := processHeaders(resp.Headers)
  1113  								if err != nil {
  1114  									return err
  1115  								}
  1116  								respObj.Headers = hdrs
  1117  							}
  1118  							if resp.Extensions != nil {
  1119  								exts, err := processExtensions(resp.Extensions)
  1120  								if err != nil {
  1121  									return err
  1122  								}
  1123  								respObj.extensions = exts
  1124  							}
  1125  							operationObject.Responses[name] = respObj
  1126  						}
  1127  					}
  1128  
  1129  					if opts.Extensions != nil {
  1130  						exts, err := processExtensions(opts.Extensions)
  1131  						if err != nil {
  1132  							return err
  1133  						}
  1134  						operationObject.extensions = exts
  1135  					}
  1136  
  1137  					if len(opts.Produces) > 0 {
  1138  						operationObject.Produces = make([]string, len(opts.Produces))
  1139  						copy(operationObject.Produces, opts.Produces)
  1140  					}
  1141  
  1142  					// TODO(ivucica): add remaining fields of operation object
  1143  				}
  1144  
  1145  				switch b.HTTPMethod {
  1146  				case "DELETE":
  1147  					pathItemObject.Delete = operationObject
  1148  					break
  1149  				case "GET":
  1150  					pathItemObject.Get = operationObject
  1151  					break
  1152  				case "POST":
  1153  					pathItemObject.Post = operationObject
  1154  					break
  1155  				case "PUT":
  1156  					pathItemObject.Put = operationObject
  1157  					break
  1158  				case "PATCH":
  1159  					pathItemObject.Patch = operationObject
  1160  					break
  1161  				}
  1162  				paths[templateToSwaggerPath(b.PathTmpl.Template, reg, meth.RequestType.Fields, msgs)] = pathItemObject
  1163  			}
  1164  		}
  1165  	}
  1166  
  1167  	// Success! return nil on the error object
  1168  	return nil
  1169  }
  1170  
  1171  // This function is called with a param which contains the entire definition of a method.
  1172  func applyTemplate(p param) (*swaggerObject, error) {
  1173  	// Create the basic template object. This is the object that everything is
  1174  	// defined off of.
  1175  	s := swaggerObject{
  1176  		// Swagger 2.0 is the version of this document
  1177  		Swagger:     "2.0",
  1178  		Consumes:    []string{"application/json"},
  1179  		Produces:    []string{"application/json"},
  1180  		Paths:       make(swaggerPathsObject),
  1181  		Definitions: make(swaggerDefinitionsObject),
  1182  		Info: swaggerInfoObject{
  1183  			Title:   *p.File.Name,
  1184  			Version: "version not set",
  1185  		},
  1186  	}
  1187  
  1188  	// Loops through all the services and their exposed GET/POST/PUT/DELETE definitions
  1189  	// and create entries for all of them.
  1190  	// Also adds custom user specified references to second map.
  1191  	requestResponseRefs, customRefs := refMap{}, refMap{}
  1192  	if err := renderServices(p.Services, s.Paths, p.reg, requestResponseRefs, customRefs, p.Messages); err != nil {
  1193  		panic(err)
  1194  	}
  1195  
  1196  	messages := messageMap{}
  1197  	streamingMessages := messageMap{}
  1198  	enums := enumMap{}
  1199  
  1200  	if !p.reg.GetDisableDefaultErrors() {
  1201  		// Add the error type to the message map
  1202  		runtimeError, swgRef, err := lookupMsgAndSwaggerName(".grpc.gateway.runtime", "Error", p.reg)
  1203  		if err == nil {
  1204  			messages[swgRef] = runtimeError
  1205  		} else {
  1206  			// just in case there is an error looking up runtimeError
  1207  			glog.Error(err)
  1208  		}
  1209  	}
  1210  
  1211  	// Find all the service's messages and enumerations that are defined (recursively)
  1212  	// and write request, response and other custom (but referenced) types out as definition objects.
  1213  	findServicesMessagesAndEnumerations(p.Services, p.reg, messages, streamingMessages, enums, requestResponseRefs)
  1214  	renderMessagesAsDefinition(messages, s.Definitions, p.reg, customRefs)
  1215  	renderEnumerationsAsDefinition(enums, s.Definitions, p.reg)
  1216  
  1217  	// File itself might have some comments and metadata.
  1218  	packageProtoPath := protoPathIndex(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), "Package")
  1219  	packageComments := protoComments(p.reg, p.File, nil, "Package", packageProtoPath)
  1220  	if err := updateSwaggerDataFromComments(p.reg, &s, p, packageComments, true); err != nil {
  1221  		panic(err)
  1222  	}
  1223  
  1224  	// There may be additional options in the swagger option in the proto.
  1225  	spb, err := extractSwaggerOptionFromFileDescriptor(p.FileDescriptorProto)
  1226  	if err != nil {
  1227  		panic(err)
  1228  	}
  1229  	if spb != nil {
  1230  		if spb.Swagger != "" {
  1231  			s.Swagger = spb.Swagger
  1232  		}
  1233  		if spb.Info != nil {
  1234  			if spb.Info.Title != "" {
  1235  				s.Info.Title = spb.Info.Title
  1236  			}
  1237  			if spb.Info.Description != "" {
  1238  				s.Info.Description = spb.Info.Description
  1239  			}
  1240  			if spb.Info.TermsOfService != "" {
  1241  				s.Info.TermsOfService = spb.Info.TermsOfService
  1242  			}
  1243  			if spb.Info.Version != "" {
  1244  				s.Info.Version = spb.Info.Version
  1245  			}
  1246  			if spb.Info.Contact != nil {
  1247  				if s.Info.Contact == nil {
  1248  					s.Info.Contact = &swaggerContactObject{}
  1249  				}
  1250  				if spb.Info.Contact.Name != "" {
  1251  					s.Info.Contact.Name = spb.Info.Contact.Name
  1252  				}
  1253  				if spb.Info.Contact.Url != "" {
  1254  					s.Info.Contact.URL = spb.Info.Contact.Url
  1255  				}
  1256  				if spb.Info.Contact.Email != "" {
  1257  					s.Info.Contact.Email = spb.Info.Contact.Email
  1258  				}
  1259  			}
  1260  			if spb.Info.License != nil {
  1261  				if s.Info.License == nil {
  1262  					s.Info.License = &swaggerLicenseObject{}
  1263  				}
  1264  				if spb.Info.License.Name != "" {
  1265  					s.Info.License.Name = spb.Info.License.Name
  1266  				}
  1267  				if spb.Info.License.Url != "" {
  1268  					s.Info.License.URL = spb.Info.License.Url
  1269  				}
  1270  			}
  1271  			if spb.Info.Extensions != nil {
  1272  				exts, err := processExtensions(spb.Info.Extensions)
  1273  				if err != nil {
  1274  					return nil, err
  1275  				}
  1276  				s.Info.extensions = exts
  1277  			}
  1278  		}
  1279  		if spb.Host != "" {
  1280  			s.Host = spb.Host
  1281  		}
  1282  		if spb.BasePath != "" {
  1283  			s.BasePath = spb.BasePath
  1284  		}
  1285  		if len(spb.Schemes) > 0 {
  1286  			s.Schemes = make([]string, len(spb.Schemes))
  1287  			for i, scheme := range spb.Schemes {
  1288  				s.Schemes[i] = strings.ToLower(scheme.String())
  1289  			}
  1290  		}
  1291  		if len(spb.Consumes) > 0 {
  1292  			s.Consumes = make([]string, len(spb.Consumes))
  1293  			copy(s.Consumes, spb.Consumes)
  1294  		}
  1295  		if len(spb.Produces) > 0 {
  1296  			s.Produces = make([]string, len(spb.Produces))
  1297  			copy(s.Produces, spb.Produces)
  1298  		}
  1299  		if spb.SecurityDefinitions != nil && spb.SecurityDefinitions.Security != nil {
  1300  			if s.SecurityDefinitions == nil {
  1301  				s.SecurityDefinitions = swaggerSecurityDefinitionsObject{}
  1302  			}
  1303  			for secDefKey, secDefValue := range spb.SecurityDefinitions.Security {
  1304  				var newSecDefValue swaggerSecuritySchemeObject
  1305  				if oldSecDefValue, ok := s.SecurityDefinitions[secDefKey]; !ok {
  1306  					newSecDefValue = swaggerSecuritySchemeObject{}
  1307  				} else {
  1308  					newSecDefValue = oldSecDefValue
  1309  				}
  1310  				if secDefValue.Type != swagger_options.SecurityScheme_TYPE_INVALID {
  1311  					switch secDefValue.Type {
  1312  					case swagger_options.SecurityScheme_TYPE_BASIC:
  1313  						newSecDefValue.Type = "basic"
  1314  					case swagger_options.SecurityScheme_TYPE_API_KEY:
  1315  						newSecDefValue.Type = "apiKey"
  1316  					case swagger_options.SecurityScheme_TYPE_OAUTH2:
  1317  						newSecDefValue.Type = "oauth2"
  1318  					}
  1319  				}
  1320  				if secDefValue.Description != "" {
  1321  					newSecDefValue.Description = secDefValue.Description
  1322  				}
  1323  				if secDefValue.Name != "" {
  1324  					newSecDefValue.Name = secDefValue.Name
  1325  				}
  1326  				if secDefValue.In != swagger_options.SecurityScheme_IN_INVALID {
  1327  					switch secDefValue.In {
  1328  					case swagger_options.SecurityScheme_IN_QUERY:
  1329  						newSecDefValue.In = "query"
  1330  					case swagger_options.SecurityScheme_IN_HEADER:
  1331  						newSecDefValue.In = "header"
  1332  					}
  1333  				}
  1334  				if secDefValue.Flow != swagger_options.SecurityScheme_FLOW_INVALID {
  1335  					switch secDefValue.Flow {
  1336  					case swagger_options.SecurityScheme_FLOW_IMPLICIT:
  1337  						newSecDefValue.Flow = "implicit"
  1338  					case swagger_options.SecurityScheme_FLOW_PASSWORD:
  1339  						newSecDefValue.Flow = "password"
  1340  					case swagger_options.SecurityScheme_FLOW_APPLICATION:
  1341  						newSecDefValue.Flow = "application"
  1342  					case swagger_options.SecurityScheme_FLOW_ACCESS_CODE:
  1343  						newSecDefValue.Flow = "accessCode"
  1344  					}
  1345  				}
  1346  				if secDefValue.AuthorizationUrl != "" {
  1347  					newSecDefValue.AuthorizationURL = secDefValue.AuthorizationUrl
  1348  				}
  1349  				if secDefValue.TokenUrl != "" {
  1350  					newSecDefValue.TokenURL = secDefValue.TokenUrl
  1351  				}
  1352  				if secDefValue.Scopes != nil {
  1353  					if newSecDefValue.Scopes == nil {
  1354  						newSecDefValue.Scopes = swaggerScopesObject{}
  1355  					}
  1356  					for scopeKey, scopeDesc := range secDefValue.Scopes.Scope {
  1357  						newSecDefValue.Scopes[scopeKey] = scopeDesc
  1358  					}
  1359  				}
  1360  				if secDefValue.Extensions != nil {
  1361  					exts, err := processExtensions(secDefValue.Extensions)
  1362  					if err != nil {
  1363  						return nil, err
  1364  					}
  1365  					newSecDefValue.extensions = exts
  1366  				}
  1367  				s.SecurityDefinitions[secDefKey] = newSecDefValue
  1368  			}
  1369  		}
  1370  		if spb.Security != nil {
  1371  			newSecurity := []swaggerSecurityRequirementObject{}
  1372  			if s.Security == nil {
  1373  				newSecurity = []swaggerSecurityRequirementObject{}
  1374  			} else {
  1375  				newSecurity = s.Security
  1376  			}
  1377  			for _, secReq := range spb.Security {
  1378  				newSecReq := swaggerSecurityRequirementObject{}
  1379  				for secReqKey, secReqValue := range secReq.SecurityRequirement {
  1380  					newSecReqValue := make([]string, len(secReqValue.Scope))
  1381  					copy(newSecReqValue, secReqValue.Scope)
  1382  					newSecReq[secReqKey] = newSecReqValue
  1383  				}
  1384  				newSecurity = append(newSecurity, newSecReq)
  1385  			}
  1386  			s.Security = newSecurity
  1387  		}
  1388  		s.ExternalDocs = protoExternalDocumentationToSwaggerExternalDocumentation(spb.ExternalDocs, p.reg, spb)
  1389  		// Populate all Paths with Responses set at top level,
  1390  		// preferring Responses already set over those at the top level.
  1391  		if spb.Responses != nil {
  1392  			for _, verbs := range s.Paths {
  1393  				var maps []swaggerResponsesObject
  1394  				if verbs.Delete != nil {
  1395  					maps = append(maps, verbs.Delete.Responses)
  1396  				}
  1397  				if verbs.Get != nil {
  1398  					maps = append(maps, verbs.Get.Responses)
  1399  				}
  1400  				if verbs.Post != nil {
  1401  					maps = append(maps, verbs.Post.Responses)
  1402  				}
  1403  				if verbs.Put != nil {
  1404  					maps = append(maps, verbs.Put.Responses)
  1405  				}
  1406  				if verbs.Patch != nil {
  1407  					maps = append(maps, verbs.Patch.Responses)
  1408  				}
  1409  
  1410  				for k, v := range spb.Responses {
  1411  					for _, respMap := range maps {
  1412  						if _, ok := respMap[k]; ok {
  1413  							// Don't overwrite already existing Responses
  1414  							continue
  1415  						}
  1416  						respMap[k] = swaggerResponseObject{
  1417  							Description: v.Description,
  1418  							Schema:      swaggerSchemaFromProtoSchema(v.Schema, p.reg, customRefs, nil),
  1419  							Examples:    swaggerExamplesFromProtoExamples(v.Examples),
  1420  						}
  1421  					}
  1422  				}
  1423  			}
  1424  		}
  1425  
  1426  		if spb.Extensions != nil {
  1427  			exts, err := processExtensions(spb.Extensions)
  1428  			if err != nil {
  1429  				return nil, err
  1430  			}
  1431  			s.extensions = exts
  1432  		}
  1433  
  1434  		// Additional fields on the OpenAPI v2 spec's "Swagger" object
  1435  		// should be added here, once supported in the proto.
  1436  	}
  1437  
  1438  	// Finally add any references added by users that aren't
  1439  	// otherwise rendered.
  1440  	addCustomRefs(s.Definitions, p.reg, customRefs)
  1441  
  1442  	return &s, nil
  1443  }
  1444  
  1445  func processExtensions(inputExts map[string]*structpb.Value) ([]extension, error) {
  1446  	exts := []extension{}
  1447  	for k, v := range inputExts {
  1448  		if !strings.HasPrefix(k, "x-") {
  1449  			return nil, fmt.Errorf("Extension keys need to start with \"x-\": %q", k)
  1450  		}
  1451  		ext, err := (&jsonpb.Marshaler{Indent: "  "}).MarshalToString(v)
  1452  		if err != nil {
  1453  			return nil, err
  1454  		}
  1455  		exts = append(exts, extension{key: k, value: json.RawMessage(ext)})
  1456  	}
  1457  	sort.Slice(exts, func(i, j int) bool { return exts[i].key < exts[j].key })
  1458  	return exts, nil
  1459  }
  1460  
  1461  func validateHeaderTypeAndFormat(headerType string, format string) error {
  1462  	// The type of the object. The value MUST be one of "string", "number", "integer", "boolean", or "array"
  1463  	// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#headerObject
  1464  	// Note: currently not implementing array as we are only implementing this in the operation response context
  1465  	switch headerType {
  1466  	// the format property is an open string-valued property, and can have any value to support documentation needs
  1467  	// primary check for format is to ensure that the number/integer formats are extensions of the specified type
  1468  	// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#dataTypeFormat
  1469  	case "string":
  1470  		return nil
  1471  	case "number":
  1472  		switch format {
  1473  		case "uint",
  1474  			"uint8",
  1475  			"uint16",
  1476  			"uint32",
  1477  			"uint64",
  1478  			"int",
  1479  			"int8",
  1480  			"int16",
  1481  			"int32",
  1482  			"int64",
  1483  			"float",
  1484  			"float32",
  1485  			"float64",
  1486  			"complex64",
  1487  			"complex128",
  1488  			"double",
  1489  			"byte",
  1490  			"rune",
  1491  			"uintptr",
  1492  			"":
  1493  			return nil
  1494  		default:
  1495  			return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
  1496  		}
  1497  	case "integer":
  1498  		switch format {
  1499  		case "uint",
  1500  			"uint8",
  1501  			"uint16",
  1502  			"uint32",
  1503  			"uint64",
  1504  			"int",
  1505  			"int8",
  1506  			"int16",
  1507  			"int32",
  1508  			"int64",
  1509  			"":
  1510  			return nil
  1511  		default:
  1512  			return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
  1513  		}
  1514  	case "boolean":
  1515  		return nil
  1516  	}
  1517  	return fmt.Errorf("the provided header type %q is not supported", headerType)
  1518  }
  1519  
  1520  func validateDefaultValueTypeAndFormat(headerType string, defaultValue string, format string) error {
  1521  	switch headerType {
  1522  	case "string":
  1523  		if !isQuotedString(defaultValue) {
  1524  			return fmt.Errorf("the provided default value %q does not match provider type %q, or is not properly quoted with escaped quotations", defaultValue, headerType)
  1525  		}
  1526  		switch format {
  1527  		case "date-time":
  1528  			unquoteTime := strings.Trim(defaultValue, `"`)
  1529  			_, err := time.Parse(time.RFC3339, unquoteTime)
  1530  			if err != nil {
  1531  				return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
  1532  			}
  1533  		case "date":
  1534  			const (
  1535  				layoutRFC3339Date = "2006-01-02"
  1536  			)
  1537  			unquoteDate := strings.Trim(defaultValue, `"`)
  1538  			_, err := time.Parse(layoutRFC3339Date, unquoteDate)
  1539  			if err != nil {
  1540  				return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
  1541  			}
  1542  		}
  1543  	case "number":
  1544  		err := isJSONNumber(defaultValue, headerType)
  1545  		if err != nil {
  1546  			return err
  1547  		}
  1548  	case "integer":
  1549  		switch format {
  1550  		case "int32":
  1551  			_, err := strconv.ParseInt(defaultValue, 0, 32)
  1552  			if err != nil {
  1553  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  1554  			}
  1555  		case "uint32":
  1556  			_, err := strconv.ParseUint(defaultValue, 0, 32)
  1557  			if err != nil {
  1558  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  1559  			}
  1560  		case "int64":
  1561  			_, err := strconv.ParseInt(defaultValue, 0, 64)
  1562  			if err != nil {
  1563  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  1564  			}
  1565  		case "uint64":
  1566  			_, err := strconv.ParseUint(defaultValue, 0, 64)
  1567  			if err != nil {
  1568  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  1569  			}
  1570  		default:
  1571  			_, err := strconv.ParseInt(defaultValue, 0, 64)
  1572  			if err != nil {
  1573  				return fmt.Errorf("the provided default value %q does not match provided type %q", defaultValue, headerType)
  1574  			}
  1575  		}
  1576  	case "boolean":
  1577  		if !isBool(defaultValue) {
  1578  			return fmt.Errorf("the provided default value %q does not match provider type %q", defaultValue, headerType)
  1579  		}
  1580  	}
  1581  	return nil
  1582  }
  1583  
  1584  func isQuotedString(s string) bool {
  1585  	return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
  1586  }
  1587  
  1588  func isJSONNumber(s string, t string) error {
  1589  	val, err := strconv.ParseFloat(s, 64)
  1590  	if err != nil {
  1591  		return fmt.Errorf("the provided default value %q does not match provider type %q", s, t)
  1592  	}
  1593  	// Floating point values that cannot be represented as sequences of digits (such as Infinity and NaN) are not permitted.
  1594  	// See: https://tools.ietf.org/html/rfc4627#section-2.4
  1595  	if math.IsInf(val, 0) || math.IsNaN(val) {
  1596  		return fmt.Errorf("the provided number %q is not a valid JSON number", s)
  1597  	}
  1598  
  1599  	return nil
  1600  }
  1601  
  1602  func isBool(s string) bool {
  1603  	// Unable to use strconv.ParseBool because it returns truthy values https://golang.org/pkg/strconv/#example_ParseBool
  1604  	// per https://swagger.io/specification/v2/#data-types
  1605  	// type: boolean represents two values: true and false. Note that truthy and falsy values such as "true", "", 0 or null are not considered boolean values.
  1606  	return s == "true" || s == "false"
  1607  }
  1608  
  1609  func processHeaders(inputHdrs map[string]*swagger_options.Header) (swaggerHeadersObject, error) {
  1610  	hdrs := map[string]swaggerHeaderObject{}
  1611  	for k, v := range inputHdrs {
  1612  		header := textproto.CanonicalMIMEHeaderKey(k)
  1613  		ret := swaggerHeaderObject{
  1614  			Description: v.Description,
  1615  			Format:      v.Format,
  1616  			Pattern:     v.Pattern,
  1617  		}
  1618  		err := validateHeaderTypeAndFormat(v.Type, v.Format)
  1619  		if err != nil {
  1620  			return nil, err
  1621  		}
  1622  		ret.Type = v.Type
  1623  		if v.Default != "" {
  1624  			err := validateDefaultValueTypeAndFormat(v.Type, v.Default, v.Format)
  1625  			if err != nil {
  1626  				return nil, err
  1627  			}
  1628  			ret.Default = json.RawMessage(v.Default)
  1629  		}
  1630  		hdrs[header] = ret
  1631  	}
  1632  	return hdrs, nil
  1633  }
  1634  
  1635  // updateSwaggerDataFromComments updates a Swagger object based on a comment
  1636  // from the proto file.
  1637  //
  1638  // First paragraph of a comment is used for summary. Remaining paragraphs of
  1639  // a comment are used for description. If 'Summary' field is not present on
  1640  // the passed swaggerObject, the summary and description are joined by \n\n.
  1641  //
  1642  // If there is a field named 'Info', its 'Summary' and 'Description' fields
  1643  // will be updated instead.
  1644  //
  1645  // If there is no 'Summary', the same behavior will be attempted on 'Title',
  1646  // but only if the last character is not a period.
  1647  func updateSwaggerDataFromComments(reg *descriptor.Registry, swaggerObject interface{}, data interface{}, comment string, isPackageObject bool) error {
  1648  	if len(comment) == 0 {
  1649  		return nil
  1650  	}
  1651  
  1652  	// Checks whether the "use_go_templates" flag is set to true
  1653  	if reg.GetUseGoTemplate() {
  1654  		comment = goTemplateComments(comment, data, reg)
  1655  	}
  1656  
  1657  	// Figure out what to apply changes to.
  1658  	swaggerObjectValue := reflect.ValueOf(swaggerObject)
  1659  	infoObjectValue := swaggerObjectValue.Elem().FieldByName("Info")
  1660  	if !infoObjectValue.CanSet() {
  1661  		// No such field? Apply summary and description directly to
  1662  		// passed object.
  1663  		infoObjectValue = swaggerObjectValue.Elem()
  1664  	}
  1665  
  1666  	// Figure out which properties to update.
  1667  	summaryValue := infoObjectValue.FieldByName("Summary")
  1668  	descriptionValue := infoObjectValue.FieldByName("Description")
  1669  	readOnlyValue := infoObjectValue.FieldByName("ReadOnly")
  1670  
  1671  	if readOnlyValue.Kind() == reflect.Bool && readOnlyValue.CanSet() && strings.Contains(comment, "Output only.") {
  1672  		readOnlyValue.Set(reflect.ValueOf(true))
  1673  	}
  1674  
  1675  	usingTitle := false
  1676  	if !summaryValue.CanSet() {
  1677  		summaryValue = infoObjectValue.FieldByName("Title")
  1678  		usingTitle = true
  1679  	}
  1680  
  1681  	paragraphs := strings.Split(comment, "\n\n")
  1682  
  1683  	// If there is a summary (or summary-equivalent) and it's empty, use the first
  1684  	// paragraph as summary, and the rest as description.
  1685  	if summaryValue.CanSet() {
  1686  		summary := strings.TrimSpace(paragraphs[0])
  1687  		description := strings.TrimSpace(strings.Join(paragraphs[1:], "\n\n"))
  1688  		if !usingTitle || (len(summary) > 0 && summary[len(summary)-1] != '.') {
  1689  			// overrides the schema value only if it's empty
  1690  			// keep the comment precedence when updating the package definition
  1691  			if summaryValue.Len() == 0 || isPackageObject {
  1692  				summaryValue.Set(reflect.ValueOf(summary))
  1693  			}
  1694  			if len(description) > 0 {
  1695  				if !descriptionValue.CanSet() {
  1696  					return fmt.Errorf("Encountered object type with a summary, but no description")
  1697  				}
  1698  				// overrides the schema value only if it's empty
  1699  				// keep the comment precedence when updating the package definition
  1700  				if descriptionValue.Len() == 0 || isPackageObject {
  1701  					descriptionValue.Set(reflect.ValueOf(description))
  1702  				}
  1703  			}
  1704  			return nil
  1705  		}
  1706  	}
  1707  
  1708  	// There was no summary field on the swaggerObject. Try to apply the
  1709  	// whole comment into description if the swagger object description is empty.
  1710  	if descriptionValue.CanSet() {
  1711  		if descriptionValue.Len() == 0 || isPackageObject {
  1712  			descriptionValue.Set(reflect.ValueOf(strings.Join(paragraphs, "\n\n")))
  1713  		}
  1714  		return nil
  1715  	}
  1716  
  1717  	return fmt.Errorf("no description nor summary property")
  1718  }
  1719  
  1720  func fieldProtoComments(reg *descriptor.Registry, msg *descriptor.Message, field *descriptor.Field) string {
  1721  	protoPath := protoPathIndex(reflect.TypeOf((*pbdescriptor.DescriptorProto)(nil)), "Field")
  1722  	for i, f := range msg.Fields {
  1723  		if f == field {
  1724  			return protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index), protoPath, int32(i))
  1725  		}
  1726  	}
  1727  	return ""
  1728  }
  1729  
  1730  func enumValueProtoComments(reg *descriptor.Registry, enum *descriptor.Enum) string {
  1731  	protoPath := protoPathIndex(reflect.TypeOf((*pbdescriptor.EnumDescriptorProto)(nil)), "Value")
  1732  	var comments []string
  1733  	for idx, value := range enum.GetValue() {
  1734  		name := value.GetName()
  1735  		if reg.GetEnumsAsInts() {
  1736  			name = strconv.Itoa(int(value.GetNumber()))
  1737  		}
  1738  		str := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index), protoPath, int32(idx))
  1739  		if str != "" {
  1740  			comments = append(comments, name+": "+str)
  1741  		}
  1742  	}
  1743  	if len(comments) > 0 {
  1744  		return "- " + strings.Join(comments, "\n - ")
  1745  	}
  1746  	return ""
  1747  }
  1748  
  1749  func protoComments(reg *descriptor.Registry, file *descriptor.File, outers []string, typeName string, typeIndex int32, fieldPaths ...int32) string {
  1750  	if file.SourceCodeInfo == nil {
  1751  		fmt.Fprintln(os.Stderr, "descriptor.File should not contain nil SourceCodeInfo")
  1752  		return ""
  1753  	}
  1754  
  1755  	outerPaths := make([]int32, len(outers))
  1756  	for i := range outers {
  1757  		location := ""
  1758  		if file.Package != nil {
  1759  			location = file.GetPackage()
  1760  		}
  1761  
  1762  		msg, err := reg.LookupMsg(location, strings.Join(outers[:i+1], "."))
  1763  		if err != nil {
  1764  			panic(err)
  1765  		}
  1766  		outerPaths[i] = int32(msg.Index)
  1767  	}
  1768  
  1769  	for _, loc := range file.SourceCodeInfo.Location {
  1770  		if !isProtoPathMatches(loc.Path, outerPaths, typeName, typeIndex, fieldPaths) {
  1771  			continue
  1772  		}
  1773  		comments := ""
  1774  		if loc.LeadingComments != nil {
  1775  			comments = strings.TrimRight(*loc.LeadingComments, "\n")
  1776  			comments = strings.TrimSpace(comments)
  1777  			// TODO(ivucica): this is a hack to fix "// " being interpreted as "//".
  1778  			// perhaps we should:
  1779  			// - split by \n
  1780  			// - determine if every (but first and last) line begins with " "
  1781  			// - trim every line only if that is the case
  1782  			// - join by \n
  1783  			comments = strings.Replace(comments, "\n ", "\n", -1)
  1784  		}
  1785  		return comments
  1786  	}
  1787  	return ""
  1788  }
  1789  
  1790  func goTemplateComments(comment string, data interface{}, reg *descriptor.Registry) string {
  1791  	var temp bytes.Buffer
  1792  	tpl, err := template.New("").Funcs(template.FuncMap{
  1793  		// Allows importing documentation from a file
  1794  		"import": func(name string) string {
  1795  			file, err := ioutil.ReadFile(name)
  1796  			if err != nil {
  1797  				return err.Error()
  1798  			}
  1799  			// Runs template over imported file
  1800  			return goTemplateComments(string(file), data, reg)
  1801  		},
  1802  		// Grabs title and description from a field
  1803  		"fieldcomments": func(msg *descriptor.Message, field *descriptor.Field) string {
  1804  			return strings.Replace(fieldProtoComments(reg, msg, field), "\n", "<br>", -1)
  1805  		},
  1806  	}).Parse(comment)
  1807  	if err != nil {
  1808  		// If there is an error parsing the templating insert the error as string in the comment
  1809  		// to make it easier to debug the template error
  1810  		return err.Error()
  1811  	}
  1812  	err = tpl.Execute(&temp, data)
  1813  	if err != nil {
  1814  		// If there is an error executing the templating insert the error as string in the comment
  1815  		// to make it easier to debug the error
  1816  		return err.Error()
  1817  	}
  1818  	return temp.String()
  1819  }
  1820  
  1821  var messageProtoPath = protoPathIndex(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), "MessageType")
  1822  var nestedProtoPath = protoPathIndex(reflect.TypeOf((*pbdescriptor.DescriptorProto)(nil)), "NestedType")
  1823  var packageProtoPath = protoPathIndex(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), "Package")
  1824  var serviceProtoPath = protoPathIndex(reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil)), "Service")
  1825  var methodProtoPath = protoPathIndex(reflect.TypeOf((*pbdescriptor.ServiceDescriptorProto)(nil)), "Method")
  1826  
  1827  func isProtoPathMatches(paths []int32, outerPaths []int32, typeName string, typeIndex int32, fieldPaths []int32) bool {
  1828  	if typeName == "Package" && typeIndex == packageProtoPath {
  1829  		// path for package comments is just [2], and all the other processing
  1830  		// is too complex for it.
  1831  		if len(paths) == 0 || typeIndex != paths[0] {
  1832  			return false
  1833  		}
  1834  		return true
  1835  	}
  1836  
  1837  	if len(paths) != len(outerPaths)*2+2+len(fieldPaths) {
  1838  		return false
  1839  	}
  1840  
  1841  	if typeName == "Method" {
  1842  		if paths[0] != serviceProtoPath || paths[2] != methodProtoPath {
  1843  			return false
  1844  		}
  1845  		paths = paths[2:]
  1846  	} else {
  1847  		typeNameDescriptor := reflect.TypeOf((*pbdescriptor.FileDescriptorProto)(nil))
  1848  
  1849  		if len(outerPaths) > 0 {
  1850  			if paths[0] != messageProtoPath || paths[1] != outerPaths[0] {
  1851  				return false
  1852  			}
  1853  			paths = paths[2:]
  1854  			outerPaths = outerPaths[1:]
  1855  
  1856  			for i, v := range outerPaths {
  1857  				if paths[i*2] != nestedProtoPath || paths[i*2+1] != v {
  1858  					return false
  1859  				}
  1860  			}
  1861  			paths = paths[len(outerPaths)*2:]
  1862  
  1863  			if typeName == "MessageType" {
  1864  				typeName = "NestedType"
  1865  			}
  1866  			typeNameDescriptor = reflect.TypeOf((*pbdescriptor.DescriptorProto)(nil))
  1867  		}
  1868  
  1869  		if paths[0] != protoPathIndex(typeNameDescriptor, typeName) || paths[1] != typeIndex {
  1870  			return false
  1871  		}
  1872  		paths = paths[2:]
  1873  	}
  1874  
  1875  	for i, v := range fieldPaths {
  1876  		if paths[i] != v {
  1877  			return false
  1878  		}
  1879  	}
  1880  	return true
  1881  }
  1882  
  1883  // protoPathIndex returns a path component for google.protobuf.descriptor.SourceCode_Location.
  1884  //
  1885  // Specifically, it returns an id as generated from descriptor proto which
  1886  // can be used to determine what type the id following it in the path is.
  1887  // For example, if we are trying to locate comments related to a field named
  1888  // `Address` in a message named `Person`, the path will be:
  1889  //
  1890  //	 [4, a, 2, b]
  1891  //
  1892  // While `a` gets determined by the order in which the messages appear in
  1893  // the proto file, and `b` is the field index specified in the proto
  1894  // file itself, the path actually needs to specify that `a` refers to a
  1895  // message and not, say, a service; and  that `b` refers to a field and not
  1896  // an option.
  1897  //
  1898  // protoPathIndex figures out the values 4 and 2 in the above example. Because
  1899  // messages are top level objects, the value of 4 comes from field id for
  1900  // `MessageType` inside `google.protobuf.descriptor.FileDescriptor` message.
  1901  // This field has a message type `google.protobuf.descriptor.DescriptorProto`.
  1902  // And inside message `DescriptorProto`, there is a field named `Field` with id
  1903  // 2.
  1904  //
  1905  // Some code generators seem to be hardcoding these values; this method instead
  1906  // interprets them from `descriptor.proto`-derived Go source as necessary.
  1907  func protoPathIndex(descriptorType reflect.Type, what string) int32 {
  1908  	field, ok := descriptorType.Elem().FieldByName(what)
  1909  	if !ok {
  1910  		panic(fmt.Errorf("could not find protobuf descriptor type id for %s", what))
  1911  	}
  1912  	pbtag := field.Tag.Get("protobuf")
  1913  	if pbtag == "" {
  1914  		panic(fmt.Errorf("no Go tag 'protobuf' on protobuf descriptor for %s", what))
  1915  	}
  1916  	path, err := strconv.Atoi(strings.Split(pbtag, ",")[1])
  1917  	if err != nil {
  1918  		panic(fmt.Errorf("protobuf descriptor id for %s cannot be converted to a number: %s", what, err.Error()))
  1919  	}
  1920  
  1921  	return int32(path)
  1922  }
  1923  
  1924  // extractOperationOptionFromMethodDescriptor extracts the message of type
  1925  // swagger_options.Operation from a given proto method's descriptor.
  1926  func extractOperationOptionFromMethodDescriptor(meth *pbdescriptor.MethodDescriptorProto) (*swagger_options.Operation, error) {
  1927  	if meth.Options == nil {
  1928  		return nil, nil
  1929  	}
  1930  	if !proto.HasExtension(meth.Options, swagger_options.E_Openapiv2Operation) {
  1931  		return nil, nil
  1932  	}
  1933  	ext, err := proto.GetExtension(meth.Options, swagger_options.E_Openapiv2Operation)
  1934  	if err != nil {
  1935  		return nil, err
  1936  	}
  1937  	opts, ok := ext.(*swagger_options.Operation)
  1938  	if !ok {
  1939  		return nil, fmt.Errorf("extension is %T; want an Operation", ext)
  1940  	}
  1941  	return opts, nil
  1942  }
  1943  
  1944  // extractSchemaOptionFromMessageDescriptor extracts the message of type
  1945  // swagger_options.Schema from a given proto message's descriptor.
  1946  func extractSchemaOptionFromMessageDescriptor(msg *pbdescriptor.DescriptorProto) (*swagger_options.Schema, error) {
  1947  	if msg.Options == nil {
  1948  		return nil, nil
  1949  	}
  1950  	if !proto.HasExtension(msg.Options, swagger_options.E_Openapiv2Schema) {
  1951  		return nil, nil
  1952  	}
  1953  	ext, err := proto.GetExtension(msg.Options, swagger_options.E_Openapiv2Schema)
  1954  	if err != nil {
  1955  		return nil, err
  1956  	}
  1957  	opts, ok := ext.(*swagger_options.Schema)
  1958  	if !ok {
  1959  		return nil, fmt.Errorf("extension is %T; want a Schema", ext)
  1960  	}
  1961  	return opts, nil
  1962  }
  1963  
  1964  // extractSwaggerOptionFromFileDescriptor extracts the message of type
  1965  // swagger_options.Swagger from a given proto method's descriptor.
  1966  func extractSwaggerOptionFromFileDescriptor(file *pbdescriptor.FileDescriptorProto) (*swagger_options.Swagger, error) {
  1967  	if file.Options == nil {
  1968  		return nil, nil
  1969  	}
  1970  	if !proto.HasExtension(file.Options, swagger_options.E_Openapiv2Swagger) {
  1971  		return nil, nil
  1972  	}
  1973  	ext, err := proto.GetExtension(file.Options, swagger_options.E_Openapiv2Swagger)
  1974  	if err != nil {
  1975  		return nil, err
  1976  	}
  1977  	opts, ok := ext.(*swagger_options.Swagger)
  1978  	if !ok {
  1979  		return nil, fmt.Errorf("extension is %T; want a Swagger object", ext)
  1980  	}
  1981  	return opts, nil
  1982  }
  1983  
  1984  func extractJSONSchemaFromFieldDescriptor(fd *pbdescriptor.FieldDescriptorProto) (*swagger_options.JSONSchema, error) {
  1985  	if fd.Options == nil {
  1986  		return nil, nil
  1987  	}
  1988  	if !proto.HasExtension(fd.Options, swagger_options.E_Openapiv2Field) {
  1989  		return nil, nil
  1990  	}
  1991  	ext, err := proto.GetExtension(fd.Options, swagger_options.E_Openapiv2Field)
  1992  	if err != nil {
  1993  		return nil, err
  1994  	}
  1995  	opts, ok := ext.(*swagger_options.JSONSchema)
  1996  	if !ok {
  1997  		return nil, fmt.Errorf("extension is %T; want a JSONSchema object", ext)
  1998  	}
  1999  	return opts, nil
  2000  }
  2001  
  2002  func protoJSONSchemaToSwaggerSchemaCore(j *swagger_options.JSONSchema, reg *descriptor.Registry, refs refMap) schemaCore {
  2003  	ret := schemaCore{}
  2004  
  2005  	if j.GetRef() != "" {
  2006  		swaggerName, ok := fullyQualifiedNameToSwaggerName(j.GetRef(), reg)
  2007  		if ok {
  2008  			ret.Ref = "#/definitions/" + swaggerName
  2009  			if refs != nil {
  2010  				refs[j.GetRef()] = struct{}{}
  2011  			}
  2012  		} else {
  2013  			ret.Ref += j.GetRef()
  2014  		}
  2015  	} else {
  2016  		f, t := protoJSONSchemaTypeToFormat(j.GetType())
  2017  		ret.Format = f
  2018  		ret.Type = t
  2019  	}
  2020  
  2021  	return ret
  2022  }
  2023  
  2024  func updateSwaggerObjectFromJSONSchema(s *swaggerSchemaObject, j *swagger_options.JSONSchema, reg *descriptor.Registry, data interface{}) {
  2025  	s.Title = j.GetTitle()
  2026  	s.Description = j.GetDescription()
  2027  	if reg.GetUseGoTemplate() {
  2028  		s.Title = goTemplateComments(s.Title, data, reg)
  2029  		s.Description = goTemplateComments(s.Description, data, reg)
  2030  	}
  2031  
  2032  	s.ReadOnly = j.GetReadOnly()
  2033  	s.MultipleOf = j.GetMultipleOf()
  2034  	s.Maximum = j.GetMaximum()
  2035  	s.ExclusiveMaximum = j.GetExclusiveMaximum()
  2036  	s.Minimum = j.GetMinimum()
  2037  	s.ExclusiveMinimum = j.GetExclusiveMinimum()
  2038  	s.MaxLength = j.GetMaxLength()
  2039  	s.MinLength = j.GetMinLength()
  2040  	s.Pattern = j.GetPattern()
  2041  	s.Default = j.GetDefault()
  2042  	s.MaxItems = j.GetMaxItems()
  2043  	s.MinItems = j.GetMinItems()
  2044  	s.UniqueItems = j.GetUniqueItems()
  2045  	s.MaxProperties = j.GetMaxProperties()
  2046  	s.MinProperties = j.GetMinProperties()
  2047  	s.Required = j.GetRequired()
  2048  	s.Enum = j.GetEnum()
  2049  	if overrideType := j.GetType(); len(overrideType) > 0 {
  2050  		s.Type = strings.ToLower(overrideType[0].String())
  2051  	}
  2052  	if j != nil && j.GetExample() != "" {
  2053  		s.Example = json.RawMessage(j.GetExample())
  2054  	}
  2055  	if j != nil && j.GetFormat() != "" {
  2056  		s.Format = j.GetFormat()
  2057  	}
  2058  }
  2059  
  2060  func swaggerSchemaFromProtoSchema(s *swagger_options.Schema, reg *descriptor.Registry, refs refMap, data interface{}) swaggerSchemaObject {
  2061  	ret := swaggerSchemaObject{
  2062  		ExternalDocs: protoExternalDocumentationToSwaggerExternalDocumentation(s.GetExternalDocs(), reg, data),
  2063  	}
  2064  
  2065  	ret.schemaCore = protoJSONSchemaToSwaggerSchemaCore(s.GetJsonSchema(), reg, refs)
  2066  	updateSwaggerObjectFromJSONSchema(&ret, s.GetJsonSchema(), reg, data)
  2067  
  2068  	if s != nil && s.Example != nil {
  2069  		ret.Example = json.RawMessage(s.Example.Value)
  2070  	}
  2071  	if s != nil && s.ExampleString != "" {
  2072  		ret.Example = json.RawMessage(s.ExampleString)
  2073  	}
  2074  
  2075  	return ret
  2076  }
  2077  
  2078  func swaggerExamplesFromProtoExamples(in map[string]string) map[string]interface{} {
  2079  	if len(in) == 0 {
  2080  		return nil
  2081  	}
  2082  	out := make(map[string]interface{})
  2083  	for mimeType, exampleStr := range in {
  2084  		switch mimeType {
  2085  		case "application/json":
  2086  			// JSON example objects are rendered raw.
  2087  			out[mimeType] = json.RawMessage(exampleStr)
  2088  		default:
  2089  			// All other mimetype examples are rendered as strings.
  2090  			out[mimeType] = exampleStr
  2091  		}
  2092  	}
  2093  	return out
  2094  }
  2095  
  2096  func protoJSONSchemaTypeToFormat(in []swagger_options.JSONSchema_JSONSchemaSimpleTypes) (string, string) {
  2097  	if len(in) == 0 {
  2098  		return "", ""
  2099  	}
  2100  
  2101  	// Can't support more than 1 type, just return the first element.
  2102  	// This is due to an inconsistency in the design of the openapiv2 proto
  2103  	// and that used in schemaCore. schemaCore uses the v3 definition of types,
  2104  	// which only allows a single string, while the openapiv2 proto uses the OpenAPI v2
  2105  	// definition, which defers to the JSON schema definition, which allows a string or an array.
  2106  	// Sources:
  2107  	// https://swagger.io/specification/#itemsObject
  2108  	// https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.2
  2109  	switch in[0] {
  2110  	case swagger_options.JSONSchema_UNKNOWN, swagger_options.JSONSchema_NULL:
  2111  		return "", ""
  2112  	case swagger_options.JSONSchema_OBJECT:
  2113  		return "object", ""
  2114  	case swagger_options.JSONSchema_ARRAY:
  2115  		return "array", ""
  2116  	case swagger_options.JSONSchema_BOOLEAN:
  2117  		// NOTE: in swagger specification, format should be empty on boolean type
  2118  		return "boolean", ""
  2119  	case swagger_options.JSONSchema_INTEGER:
  2120  		return "integer", "int32"
  2121  	case swagger_options.JSONSchema_NUMBER:
  2122  		return "number", "double"
  2123  	case swagger_options.JSONSchema_STRING:
  2124  		// NOTE: in swagger specification, format should be empty on string type
  2125  		return "string", ""
  2126  	default:
  2127  		// Maybe panic?
  2128  		return "", ""
  2129  	}
  2130  }
  2131  
  2132  func protoExternalDocumentationToSwaggerExternalDocumentation(in *swagger_options.ExternalDocumentation, reg *descriptor.Registry, data interface{}) *swaggerExternalDocumentationObject {
  2133  	if in == nil {
  2134  		return nil
  2135  	}
  2136  
  2137  	if reg.GetUseGoTemplate() {
  2138  		in.Description = goTemplateComments(in.Description, data, reg)
  2139  	}
  2140  
  2141  	return &swaggerExternalDocumentationObject{
  2142  		Description: in.Description,
  2143  		URL:         in.Url,
  2144  	}
  2145  }
  2146  
  2147  func addCustomRefs(d swaggerDefinitionsObject, reg *descriptor.Registry, refs refMap) {
  2148  	if len(refs) == 0 {
  2149  		return
  2150  	}
  2151  	msgMap := make(messageMap)
  2152  	enumMap := make(enumMap)
  2153  	for ref := range refs {
  2154  		swgName, swgOk := fullyQualifiedNameToSwaggerName(ref, reg)
  2155  		if !swgOk {
  2156  			glog.Errorf("can't resolve swagger name from CustomRef '%v'", ref)
  2157  			continue
  2158  		}
  2159  		if _, ok := d[swgName]; ok {
  2160  			// Skip already existing definitions
  2161  			delete(refs, ref)
  2162  			continue
  2163  		}
  2164  		msg, err := reg.LookupMsg("", ref)
  2165  		if err == nil {
  2166  			msgMap[swgName] = msg
  2167  			continue
  2168  		}
  2169  		enum, err := reg.LookupEnum("", ref)
  2170  		if err == nil {
  2171  			enumMap[swgName] = enum
  2172  			continue
  2173  		}
  2174  
  2175  		// ?? Should be either enum or msg
  2176  	}
  2177  	renderMessagesAsDefinition(msgMap, d, reg, refs)
  2178  	renderEnumerationsAsDefinition(enumMap, d, reg)
  2179  
  2180  	// Run again in case any new refs were added
  2181  	addCustomRefs(d, reg, refs)
  2182  }
  2183  
  2184  func lowerCamelCase(fieldName string, fields []*descriptor.Field, msgs []*descriptor.Message) string {
  2185  	for _, oneField := range fields {
  2186  		if oneField.GetName() == fieldName {
  2187  			return oneField.GetJsonName()
  2188  		}
  2189  	}
  2190  	messageNameToFieldsToJSONName := make(map[string]map[string]string, 0)
  2191  	fieldNameToType := make(map[string]string, 0)
  2192  	for _, msg := range msgs {
  2193  		fieldNameToJSONName := make(map[string]string, 0)
  2194  		for _, oneField := range msg.GetField() {
  2195  			fieldNameToJSONName[oneField.GetName()] = oneField.GetJsonName()
  2196  			fieldNameToType[oneField.GetName()] = oneField.GetTypeName()
  2197  		}
  2198  		messageNameToFieldsToJSONName[msg.GetName()] = fieldNameToJSONName
  2199  	}
  2200  	if strings.Contains(fieldName, ".") {
  2201  		fieldNames := strings.Split(fieldName, ".")
  2202  		fieldNamesWithCamelCase := make([]string, 0)
  2203  		for i := 0; i < len(fieldNames)-1; i++ {
  2204  			fieldNamesWithCamelCase = append(fieldNamesWithCamelCase, doCamelCase(string(fieldNames[i])))
  2205  		}
  2206  		prefix := strings.Join(fieldNamesWithCamelCase, ".")
  2207  		reservedJSONName := getReservedJSONName(fieldName, messageNameToFieldsToJSONName, fieldNameToType)
  2208  		if reservedJSONName != "" {
  2209  			return prefix + "." + reservedJSONName
  2210  		}
  2211  	}
  2212  	return doCamelCase(fieldName)
  2213  }
  2214  
  2215  func doCamelCase(input string) string {
  2216  	parameterString := casing.Camel(input)
  2217  	builder := &strings.Builder{}
  2218  	builder.WriteString(strings.ToLower(string(parameterString[0])))
  2219  	builder.WriteString(parameterString[1:])
  2220  	return builder.String()
  2221  }
  2222  
  2223  func getReservedJSONName(fieldName string, messageNameToFieldsToJSONName map[string]map[string]string, fieldNameToType map[string]string) string {
  2224  	if len(strings.Split(fieldName, ".")) == 2 {
  2225  		fieldNames := strings.Split(fieldName, ".")
  2226  		firstVariable := fieldNames[0]
  2227  		firstType := fieldNameToType[firstVariable]
  2228  		firstTypeShortNames := strings.Split(firstType, ".")
  2229  		firstTypeShortName := firstTypeShortNames[len(firstTypeShortNames)-1]
  2230  		return messageNameToFieldsToJSONName[firstTypeShortName][fieldNames[1]]
  2231  	}
  2232  	fieldNames := strings.Split(fieldName, ".")
  2233  	return getReservedJSONName(strings.Join(fieldNames[1:], "."), messageNameToFieldsToJSONName, fieldNameToType)
  2234  }
  2235  

View as plain text