github.com/grpc-ecosystem/grpc-gateway/v2@v2.19.1/protoc-gen-openapiv2/internal/genopenapi/template.go (about)

     1  package genopenapi
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"net/textproto"
     9  	"os"
    10  	"reflect"
    11  	"regexp"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"text/template"
    17  	"time"
    18  
    19  	"github.com/grpc-ecosystem/grpc-gateway/v2/internal/casing"
    20  	"github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor"
    21  	openapi_options "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
    22  	"google.golang.org/genproto/googleapis/api/annotations"
    23  	"google.golang.org/genproto/googleapis/api/visibility"
    24  	"google.golang.org/grpc/grpclog"
    25  	"google.golang.org/protobuf/encoding/protojson"
    26  	"google.golang.org/protobuf/proto"
    27  	"google.golang.org/protobuf/types/descriptorpb"
    28  	"google.golang.org/protobuf/types/known/structpb"
    29  )
    30  
    31  // The OpenAPI specification does not allow for more than one endpoint with the same HTTP method and path.
    32  // This prevents multiple gRPC service methods from sharing the same stripped version of the path and method.
    33  // For example: `GET /v1/{name=organizations/*}/roles` and `GET /v1/{name=users/*}/roles` both get stripped to `GET /v1/{name}/roles`.
    34  // We must make the URL unique by adding a suffix and an incrementing index to each path parameter
    35  // to differentiate the endpoints.
    36  // Since path parameter names do not affect the request contents (i.e. they're replaced in the path)
    37  // this will be hidden from the real grpc gateway consumer.
    38  const pathParamUniqueSuffixDeliminator = "_"
    39  
    40  const paragraphDeliminator = "\n\n"
    41  
    42  // wktSchemas are the schemas of well-known-types.
    43  // The schemas must match with the behavior of the JSON unmarshaler in
    44  // https://github.com/protocolbuffers/protobuf-go/blob/v1.25.0/encoding/protojson/well_known_types.go
    45  var wktSchemas = map[string]schemaCore{
    46  	".google.protobuf.FieldMask": {
    47  		Type: "string",
    48  	},
    49  	".google.protobuf.Timestamp": {
    50  		Type:   "string",
    51  		Format: "date-time",
    52  	},
    53  	".google.protobuf.Duration": {
    54  		Type: "string",
    55  	},
    56  	".google.protobuf.StringValue": {
    57  		Type: "string",
    58  	},
    59  	".google.protobuf.BytesValue": {
    60  		Type:   "string",
    61  		Format: "byte",
    62  	},
    63  	".google.protobuf.Int32Value": {
    64  		Type:   "integer",
    65  		Format: "int32",
    66  	},
    67  	".google.protobuf.UInt32Value": {
    68  		Type:   "integer",
    69  		Format: "int64",
    70  	},
    71  	".google.protobuf.Int64Value": {
    72  		Type:   "string",
    73  		Format: "int64",
    74  	},
    75  	".google.protobuf.UInt64Value": {
    76  		Type:   "string",
    77  		Format: "uint64",
    78  	},
    79  	".google.protobuf.FloatValue": {
    80  		Type:   "number",
    81  		Format: "float",
    82  	},
    83  	".google.protobuf.DoubleValue": {
    84  		Type:   "number",
    85  		Format: "double",
    86  	},
    87  	".google.protobuf.BoolValue": {
    88  		Type: "boolean",
    89  	},
    90  	".google.protobuf.Empty": {
    91  		Type: "object",
    92  	},
    93  	".google.protobuf.Struct": {
    94  		Type: "object",
    95  	},
    96  	".google.protobuf.Value": {},
    97  	".google.protobuf.ListValue": {
    98  		Type: "array",
    99  		Items: (*openapiItemsObject)(&openapiSchemaObject{
   100  			schemaCore: schemaCore{
   101  				Type: "object",
   102  			}}),
   103  	},
   104  	".google.protobuf.NullValue": {
   105  		Type: "string",
   106  	},
   107  }
   108  
   109  func listEnumNames(reg *descriptor.Registry, enum *descriptor.Enum) (names []string) {
   110  	for _, value := range enum.GetValue() {
   111  		if !isVisible(getEnumValueVisibilityOption(value), reg) {
   112  			continue
   113  		}
   114  		if reg.GetOmitEnumDefaultValue() && value.GetNumber() == 0 {
   115  			continue
   116  		}
   117  		names = append(names, value.GetName())
   118  	}
   119  
   120  	if len(names) > 0 {
   121  		return names
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func listEnumNumbers(reg *descriptor.Registry, enum *descriptor.Enum) (numbers []int) {
   128  	for _, value := range enum.GetValue() {
   129  		if reg.GetOmitEnumDefaultValue() && value.GetNumber() == 0 {
   130  			continue
   131  		}
   132  		if !isVisible(getEnumValueVisibilityOption(value), reg) {
   133  			continue
   134  		}
   135  		numbers = append(numbers, int(value.GetNumber()))
   136  	}
   137  
   138  	if len(numbers) > 0 {
   139  		return numbers
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  func getEnumDefault(reg *descriptor.Registry, enum *descriptor.Enum) interface{} {
   146  	if !reg.GetOmitEnumDefaultValue() {
   147  		for _, value := range enum.GetValue() {
   148  			if value.GetNumber() == 0 {
   149  				return value.GetName()
   150  			}
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  func getEnumDefaultNumber(reg *descriptor.Registry, enum *descriptor.Enum) interface{} {
   157  	if !reg.GetOmitEnumDefaultValue() {
   158  		for _, value := range enum.GetValue() {
   159  			if value.GetNumber() == 0 {
   160  				return int(value.GetNumber())
   161  			}
   162  		}
   163  	}
   164  	return nil
   165  }
   166  
   167  // messageToQueryParameters converts a message to a list of OpenAPI query parameters.
   168  func messageToQueryParameters(message *descriptor.Message, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, httpMethod string) (params []openapiParameterObject, err error) {
   169  	for _, field := range message.Fields {
   170  		// When body is set to oneof field, we want to skip other fields in the oneof group.
   171  		if isBodySameOneOf(body, field) {
   172  			continue
   173  		}
   174  
   175  		if !isVisible(getFieldVisibilityOption(field), reg) {
   176  			continue
   177  		}
   178  		if reg.GetAllowPatchFeature() && field.GetTypeName() == ".google.protobuf.FieldMask" && field.GetName() == "update_mask" && httpMethod == "PATCH" && len(body.FieldPath) != 0 {
   179  			continue
   180  		}
   181  
   182  		p, err := queryParams(message, field, "", reg, pathParams, body, reg.GetRecursiveDepth())
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		params = append(params, p...)
   187  	}
   188  	return params, nil
   189  }
   190  
   191  func isBodySameOneOf(body *descriptor.Body, field *descriptor.Field) bool {
   192  	if field.OneofIndex == nil {
   193  		return false
   194  	}
   195  
   196  	if body == nil || len(body.FieldPath) == 0 {
   197  		return false
   198  	}
   199  
   200  	if body.FieldPath[0].Target.OneofIndex == nil {
   201  		return false
   202  	}
   203  
   204  	return *body.FieldPath[0].Target.OneofIndex == *field.OneofIndex
   205  }
   206  
   207  // queryParams converts a field to a list of OpenAPI query parameters recursively through the use of nestedQueryParams.
   208  func queryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, recursiveCount int) (params []openapiParameterObject, err error) {
   209  	return nestedQueryParams(message, field, prefix, reg, pathParams, body, newCycleChecker(recursiveCount))
   210  }
   211  
   212  type cycleChecker struct {
   213  	m     map[string]int
   214  	count int
   215  }
   216  
   217  func newCycleChecker(recursive int) *cycleChecker {
   218  	return &cycleChecker{
   219  		m:     make(map[string]int),
   220  		count: recursive,
   221  	}
   222  }
   223  
   224  // Check returns whether name is still within recursion
   225  // toleration
   226  func (c *cycleChecker) Check(name string) bool {
   227  	count, ok := c.m[name]
   228  	count += 1
   229  	isCycle := count > c.count
   230  
   231  	if isCycle {
   232  		return false
   233  	}
   234  
   235  	// provision map entry if not available
   236  	if !ok {
   237  		c.m[name] = 1
   238  		return true
   239  	}
   240  
   241  	c.m[name] = count
   242  
   243  	return true
   244  }
   245  
   246  func (c *cycleChecker) Branch() *cycleChecker {
   247  	copy := &cycleChecker{
   248  		count: c.count,
   249  		m:     make(map[string]int, len(c.m)),
   250  	}
   251  
   252  	for k, v := range c.m {
   253  		copy.m[k] = v
   254  	}
   255  
   256  	return copy
   257  }
   258  
   259  // nestedQueryParams converts a field to a list of OpenAPI query parameters recursively.
   260  // This function is a helper function for queryParams, that keeps track of cyclical message references
   261  // through the use of
   262  //
   263  //	touched map[string]int
   264  //
   265  // If a cycle is discovered, an error is returned, as cyclical data structures are dangerous
   266  // in query parameters.
   267  func nestedQueryParams(message *descriptor.Message, field *descriptor.Field, prefix string, reg *descriptor.Registry, pathParams []descriptor.Parameter, body *descriptor.Body, cycle *cycleChecker) (params []openapiParameterObject, err error) {
   268  	// make sure the parameter is not already listed as a path parameter
   269  	for _, pathParam := range pathParams {
   270  		if pathParam.Target == field {
   271  			return nil, nil
   272  		}
   273  	}
   274  	// make sure the parameter is not already listed as a body parameter
   275  	if body != nil {
   276  		if body.FieldPath == nil {
   277  			return nil, nil
   278  		}
   279  		for _, fieldPath := range body.FieldPath {
   280  			if fieldPath.Target == field {
   281  				return nil, nil
   282  			}
   283  		}
   284  	}
   285  	schema := schemaOfField(field, reg, nil)
   286  	fieldType := field.GetTypeName()
   287  	if message.File != nil {
   288  		comments := fieldProtoComments(reg, message, field)
   289  		if err := updateOpenAPIDataFromComments(reg, &schema, message, comments, false); err != nil {
   290  			return nil, err
   291  		}
   292  	}
   293  
   294  	isEnum := field.GetType() == descriptorpb.FieldDescriptorProto_TYPE_ENUM
   295  	items := schema.Items
   296  	if schema.Type != "" || isEnum {
   297  		if schema.Type == "object" {
   298  			location := ""
   299  			if ix := strings.LastIndex(field.Message.FQMN(), "."); ix > 0 {
   300  				location = field.Message.FQMN()[0:ix]
   301  			}
   302  			if m, err := reg.LookupMsg(location, field.GetTypeName()); err == nil {
   303  				if opt := m.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
   304  					k := m.GetField()[0]
   305  					kType, err := getMapParamKey(k.GetType())
   306  					if err != nil {
   307  						return nil, err
   308  					}
   309  					// This will generate a query in the format map_name[key_type]
   310  					fName := fmt.Sprintf("%s[%s]", *field.Name, kType)
   311  					field.Name = proto.String(fName)
   312  					schema.Type = schema.AdditionalProperties.schemaCore.Type
   313  					schema.Description = `This is a request variable of the map type. The query format is "map_name[key]=value", e.g. If the map name is Age, the key type is string, and the value type is integer, the query parameter is expressed as Age["bob"]=18`
   314  				}
   315  			}
   316  		}
   317  		if items != nil && (items.Type == "" || items.Type == "object") && !isEnum {
   318  			return nil, nil // TODO: currently, mapping object in query parameter is not supported
   319  		}
   320  		desc := mergeDescription(schema)
   321  
   322  		// verify if the field is required
   323  		required := false
   324  		for _, fieldName := range schema.Required {
   325  			if fieldName == reg.FieldName(field) {
   326  				required = true
   327  				break
   328  			}
   329  		}
   330  		// verify if the field is required in message options
   331  		if messageSchema, err := extractSchemaOptionFromMessageDescriptor(message.DescriptorProto); err == nil {
   332  			for _, fieldName := range messageSchema.GetJsonSchema().GetRequired() {
   333  				// Required fields can be field names or json_name values
   334  				if fieldName == field.GetJsonName() || fieldName == field.GetName() {
   335  					required = true
   336  					break
   337  				}
   338  			}
   339  		}
   340  
   341  		param := openapiParameterObject{
   342  			Description: desc,
   343  			In:          "query",
   344  			Default:     schema.Default,
   345  			Type:        schema.Type,
   346  			Items:       schema.Items,
   347  			Format:      schema.Format,
   348  			Pattern:     schema.Pattern,
   349  			Required:    required,
   350  			extensions:  schema.extensions,
   351  			Enum:        schema.Enum,
   352  		}
   353  		if param.Type == "array" {
   354  			param.CollectionFormat = "multi"
   355  		}
   356  
   357  		param.Name = prefix + reg.FieldName(field)
   358  
   359  		if isEnum {
   360  			enum, err := reg.LookupEnum("", fieldType)
   361  			if err != nil {
   362  				return nil, fmt.Errorf("unknown enum type %s", fieldType)
   363  			}
   364  			if items != nil { // array
   365  				param.Items = &openapiItemsObject{
   366  					schemaCore: schemaCore{
   367  						Type: "string",
   368  						Enum: listEnumNames(reg, enum),
   369  					},
   370  				}
   371  				if reg.GetEnumsAsInts() {
   372  					param.Items.Type = "integer"
   373  					param.Items.Enum = listEnumNumbers(reg, enum)
   374  				}
   375  			} else {
   376  				param.Type = "string"
   377  				param.Enum = listEnumNames(reg, enum)
   378  				param.Default = getEnumDefault(reg, enum)
   379  				if reg.GetEnumsAsInts() {
   380  					param.Type = "integer"
   381  					param.Enum = listEnumNumbers(reg, enum)
   382  					param.Default = getEnumDefaultNumber(reg, enum)
   383  				}
   384  			}
   385  			valueComments := enumValueProtoComments(reg, enum)
   386  			if valueComments != "" {
   387  				param.Description = strings.TrimLeft(param.Description+"\n\n "+valueComments, "\n")
   388  			}
   389  		}
   390  		return []openapiParameterObject{param}, nil
   391  	}
   392  
   393  	// nested type, recurse
   394  	msg, err := reg.LookupMsg("", fieldType)
   395  	if err != nil {
   396  		return nil, fmt.Errorf("unknown message type %s", fieldType)
   397  	}
   398  
   399  	// Check for cyclical message reference:
   400  	if ok := cycle.Check(*msg.Name); !ok {
   401  		return nil, fmt.Errorf("exceeded recursive count (%d) for query parameter %q", cycle.count, fieldType)
   402  	}
   403  
   404  	// Construct a new map with the message name so a cycle further down the recursive path can be detected.
   405  	// Do not keep anything in the original touched reference and do not pass that reference along.  This will
   406  	// prevent clobbering adjacent records while recursing.
   407  	touchedOut := cycle.Branch()
   408  
   409  	for _, nestedField := range msg.Fields {
   410  		if !isVisible(getFieldVisibilityOption(nestedField), reg) {
   411  			continue
   412  		}
   413  
   414  		fieldName := reg.FieldName(field)
   415  		p, err := nestedQueryParams(msg, nestedField, prefix+fieldName+".", reg, pathParams, body, touchedOut)
   416  		if err != nil {
   417  			return nil, err
   418  		}
   419  		params = append(params, p...)
   420  	}
   421  	return params, nil
   422  }
   423  
   424  func getMapParamKey(t descriptorpb.FieldDescriptorProto_Type) (string, error) {
   425  	tType, f, ok := primitiveSchema(t)
   426  	if !ok || f == "byte" || f == "float" || f == "double" {
   427  		return "", fmt.Errorf("unsupported type: %q", f)
   428  	}
   429  	return tType, nil
   430  }
   431  
   432  // findServicesMessagesAndEnumerations discovers all messages and enums defined in the RPC methods of the service.
   433  func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descriptor.Registry, m messageMap, ms messageMap, e enumMap, refs refMap) {
   434  	for _, svc := range s {
   435  		for _, meth := range svc.Methods {
   436  			// Request may be fully included in query
   437  			{
   438  				if !isVisible(getMethodVisibilityOption(meth), reg) {
   439  					continue
   440  				}
   441  
   442  				swgReqName, ok := fullyQualifiedNameToOpenAPIName(meth.RequestType.FQMN(), reg)
   443  				if !ok {
   444  					grpclog.Errorf("couldn't resolve OpenAPI name for FQMN %q", meth.RequestType.FQMN())
   445  					continue
   446  				}
   447  				if _, ok := refs[fmt.Sprintf("#/definitions/%s", swgReqName)]; ok {
   448  					if !skipRenderingRef(meth.RequestType.FQMN()) {
   449  						m[swgReqName] = meth.RequestType
   450  					}
   451  				}
   452  			}
   453  
   454  			swgRspName, ok := fullyQualifiedNameToOpenAPIName(meth.ResponseType.FQMN(), reg)
   455  			if !ok && !skipRenderingRef(meth.ResponseType.FQMN()) {
   456  				grpclog.Errorf("couldn't resolve OpenAPI name for FQMN %q", meth.ResponseType.FQMN())
   457  				continue
   458  			}
   459  
   460  			findNestedMessagesAndEnumerations(meth.RequestType, reg, m, e)
   461  
   462  			if !skipRenderingRef(meth.ResponseType.FQMN()) {
   463  				m[swgRspName] = meth.ResponseType
   464  			}
   465  			findNestedMessagesAndEnumerations(meth.ResponseType, reg, m, e)
   466  		}
   467  	}
   468  }
   469  
   470  // findNestedMessagesAndEnumerations those can be generated by the services.
   471  func findNestedMessagesAndEnumerations(message *descriptor.Message, reg *descriptor.Registry, m messageMap, e enumMap) {
   472  	// Iterate over all the fields that
   473  	for _, t := range message.Fields {
   474  		if !isVisible(getFieldVisibilityOption(t), reg) {
   475  			continue
   476  		}
   477  
   478  		fieldType := t.GetTypeName()
   479  		// If the type is an empty string then it is a proto primitive
   480  		if fieldType != "" {
   481  			if _, ok := m[fieldType]; !ok {
   482  				msg, err := reg.LookupMsg("", fieldType)
   483  				if err != nil {
   484  					enum, err := reg.LookupEnum("", fieldType)
   485  					if err != nil {
   486  						panic(err)
   487  					}
   488  					e[fieldType] = enum
   489  					continue
   490  				}
   491  				m[fieldType] = msg
   492  				findNestedMessagesAndEnumerations(msg, reg, m, e)
   493  			}
   494  		}
   495  	}
   496  }
   497  
   498  func skipRenderingRef(refName string) bool {
   499  	_, ok := wktSchemas[refName]
   500  	return ok
   501  }
   502  
   503  func renderMessageAsDefinition(msg *descriptor.Message, reg *descriptor.Registry, customRefs refMap, pathParams []descriptor.Parameter) (openapiSchemaObject, error) {
   504  	schema := openapiSchemaObject{
   505  		schemaCore: schemaCore{
   506  			Type: "object",
   507  		},
   508  	}
   509  	msgComments := protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index))
   510  	if err := updateOpenAPIDataFromComments(reg, &schema, msg, msgComments, false); err != nil {
   511  		return openapiSchemaObject{}, err
   512  	}
   513  	opts, err := getMessageOpenAPIOption(reg, msg)
   514  	if err != nil {
   515  		return openapiSchemaObject{}, err
   516  	}
   517  	if opts != nil {
   518  		protoSchema := openapiSchemaFromProtoSchema(opts, reg, customRefs, msg)
   519  
   520  		// Warning: Make sure not to overwrite any fields already set on the schema type.
   521  		schema.ExternalDocs = protoSchema.ExternalDocs
   522  		schema.ReadOnly = protoSchema.ReadOnly
   523  		schema.MultipleOf = protoSchema.MultipleOf
   524  		schema.Maximum = protoSchema.Maximum
   525  		schema.ExclusiveMaximum = protoSchema.ExclusiveMaximum
   526  		schema.Minimum = protoSchema.Minimum
   527  		schema.ExclusiveMinimum = protoSchema.ExclusiveMinimum
   528  		schema.MaxLength = protoSchema.MaxLength
   529  		schema.MinLength = protoSchema.MinLength
   530  		schema.Pattern = protoSchema.Pattern
   531  		schema.Default = protoSchema.Default
   532  		schema.MaxItems = protoSchema.MaxItems
   533  		schema.MinItems = protoSchema.MinItems
   534  		schema.UniqueItems = protoSchema.UniqueItems
   535  		schema.MaxProperties = protoSchema.MaxProperties
   536  		schema.MinProperties = protoSchema.MinProperties
   537  		schema.Required = protoSchema.Required
   538  		schema.XNullable = protoSchema.XNullable
   539  		schema.extensions = protoSchema.extensions
   540  		if protoSchema.schemaCore.Type != "" || protoSchema.schemaCore.Ref != "" {
   541  			schema.schemaCore = protoSchema.schemaCore
   542  		}
   543  		if protoSchema.Title != "" {
   544  			schema.Title = protoSchema.Title
   545  		}
   546  		if protoSchema.Description != "" {
   547  			schema.Description = protoSchema.Description
   548  		}
   549  		if protoSchema.Example != nil {
   550  			schema.Example = protoSchema.Example
   551  		}
   552  	}
   553  
   554  	schema.Required = filterOutExcludedFields(schema.Required, pathParams)
   555  
   556  	for _, f := range msg.Fields {
   557  		if !isVisible(getFieldVisibilityOption(f), reg) {
   558  			continue
   559  		}
   560  
   561  		if shouldExcludeField(f.GetName(), pathParams) {
   562  			continue
   563  		}
   564  		subPathParams := subPathParams(f.GetName(), pathParams)
   565  		fieldSchema, err := renderFieldAsDefinition(f, reg, customRefs, subPathParams)
   566  		if err != nil {
   567  			return openapiSchemaObject{}, err
   568  		}
   569  		comments := fieldProtoComments(reg, msg, f)
   570  		if err := updateOpenAPIDataFromComments(reg, &fieldSchema, f, comments, false); err != nil {
   571  			return openapiSchemaObject{}, err
   572  		}
   573  
   574  		if requiredIdx := find(schema.Required, *f.Name); requiredIdx != -1 && reg.GetUseJSONNamesForFields() {
   575  			schema.Required[requiredIdx] = f.GetJsonName()
   576  		}
   577  
   578  		if fieldSchema.Required != nil {
   579  			schema.Required = getUniqueFields(schema.Required, fieldSchema.Required)
   580  			schema.Required = append(schema.Required, fieldSchema.Required...)
   581  			// To avoid populating both the field schema require and message schema require, unset the field schema require.
   582  			// See issue #2635.
   583  			fieldSchema.Required = nil
   584  		}
   585  
   586  		if reg.GetUseAllOfForRefs() {
   587  			if fieldSchema.Ref != "" {
   588  				// Per the JSON Reference syntax: Any members other than "$ref" in a JSON Reference object SHALL be ignored.
   589  				// https://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03#section-3
   590  				// However, use allOf to specify Title/Description/Example/readOnly fields.
   591  				if fieldSchema.Title != "" || fieldSchema.Description != "" || len(fieldSchema.Example) > 0 || fieldSchema.ReadOnly {
   592  					fieldSchema = openapiSchemaObject{
   593  						Title:       fieldSchema.Title,
   594  						Description: fieldSchema.Description,
   595  						schemaCore: schemaCore{
   596  							Example: fieldSchema.Example,
   597  						},
   598  						ReadOnly: fieldSchema.ReadOnly,
   599  						AllOf:    []allOfEntry{{Ref: fieldSchema.Ref}},
   600  					}
   601  				} else {
   602  					fieldSchema = openapiSchemaObject{schemaCore: schemaCore{Ref: fieldSchema.Ref}}
   603  				}
   604  			}
   605  		}
   606  
   607  		kv := keyVal{Value: fieldSchema}
   608  		kv.Key = reg.FieldName(f)
   609  		if schema.Properties == nil {
   610  			schema.Properties = &openapiSchemaObjectProperties{}
   611  		}
   612  		*schema.Properties = append(*schema.Properties, kv)
   613  	}
   614  
   615  	if msg.FQMN() == ".google.protobuf.Any" {
   616  		transformAnyForJSON(&schema, reg.GetUseJSONNamesForFields())
   617  	}
   618  
   619  	return schema, nil
   620  }
   621  
   622  func renderFieldAsDefinition(f *descriptor.Field, reg *descriptor.Registry, refs refMap, pathParams []descriptor.Parameter) (openapiSchemaObject, error) {
   623  	if len(pathParams) == 0 {
   624  		return schemaOfField(f, reg, refs), nil
   625  	}
   626  	location := ""
   627  	if ix := strings.LastIndex(f.Message.FQMN(), "."); ix > 0 {
   628  		location = f.Message.FQMN()[0:ix]
   629  	}
   630  	msg, err := reg.LookupMsg(location, f.GetTypeName())
   631  	if err != nil {
   632  		return openapiSchemaObject{}, err
   633  	}
   634  	schema, err := renderMessageAsDefinition(msg, reg, refs, pathParams)
   635  	if err != nil {
   636  		return openapiSchemaObject{}, err
   637  	}
   638  	comments := fieldProtoComments(reg, f.Message, f)
   639  	if len(comments) > 0 {
   640  		// Use title and description from field instead of nested message if present.
   641  		paragraphs := strings.Split(comments, paragraphDeliminator)
   642  		schema.Title = strings.TrimSpace(paragraphs[0])
   643  		schema.Description = strings.TrimSpace(strings.Join(paragraphs[1:], paragraphDeliminator))
   644  	}
   645  	return schema, nil
   646  }
   647  
   648  // transformAnyForJSON should be called when the schema object represents a google.protobuf.Any, and will replace the
   649  // Properties slice with a single value for '@type'. We mutate the incorrectly named field so that we inherit the same
   650  // documentation as specified on the original field in the protobuf descriptors.
   651  func transformAnyForJSON(schema *openapiSchemaObject, useJSONNames bool) {
   652  	var typeFieldName string
   653  	if useJSONNames {
   654  		typeFieldName = "typeUrl"
   655  	} else {
   656  		typeFieldName = "type_url"
   657  	}
   658  
   659  	for _, property := range *schema.Properties {
   660  		if property.Key == typeFieldName {
   661  			schema.AdditionalProperties = &openapiSchemaObject{}
   662  			schema.Properties = &openapiSchemaObjectProperties{keyVal{
   663  				Key:   "@type",
   664  				Value: property.Value,
   665  			}}
   666  			break
   667  		}
   668  	}
   669  }
   670  
   671  func renderMessagesAsDefinition(messages messageMap, d openapiDefinitionsObject, reg *descriptor.Registry, customRefs refMap, pathParams []descriptor.Parameter) error {
   672  	for name, msg := range messages {
   673  		swgName, ok := fullyQualifiedNameToOpenAPIName(msg.FQMN(), reg)
   674  		if !ok {
   675  			return fmt.Errorf("can't resolve OpenAPI name from %q", msg.FQMN())
   676  		}
   677  		if skipRenderingRef(name) {
   678  			continue
   679  		}
   680  
   681  		if opt := msg.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
   682  			continue
   683  		}
   684  		var err error
   685  		d[swgName], err = renderMessageAsDefinition(msg, reg, customRefs, pathParams)
   686  		if err != nil {
   687  			return err
   688  		}
   689  	}
   690  	return nil
   691  }
   692  
   693  // isVisible checks if a field/RPC is visible based on the visibility restriction
   694  // combined with the `visibility_restriction_selectors`.
   695  // Elements with an overlap on `visibility_restriction_selectors` are visible, those without are not visible.
   696  // Elements without `google.api.VisibilityRule` annotations entirely are always visible.
   697  func isVisible(r *visibility.VisibilityRule, reg *descriptor.Registry) bool {
   698  	if r == nil {
   699  		return true
   700  	}
   701  
   702  	restrictions := strings.Split(strings.TrimSpace(r.Restriction), ",")
   703  	// No restrictions results in the element always being visible
   704  	if len(restrictions) == 0 {
   705  		return true
   706  	}
   707  
   708  	for _, restriction := range restrictions {
   709  		if reg.GetVisibilityRestrictionSelectors()[strings.TrimSpace(restriction)] {
   710  			return true
   711  		}
   712  	}
   713  
   714  	return false
   715  }
   716  
   717  func shouldExcludeField(name string, excluded []descriptor.Parameter) bool {
   718  	for _, p := range excluded {
   719  		if len(p.FieldPath) == 1 && name == p.FieldPath[0].Name {
   720  			return true
   721  		}
   722  	}
   723  	return false
   724  }
   725  func filterOutExcludedFields(fields []string, excluded []descriptor.Parameter) []string {
   726  	var filtered []string
   727  	for _, f := range fields {
   728  		if !shouldExcludeField(f, excluded) {
   729  			filtered = append(filtered, f)
   730  		}
   731  	}
   732  	return filtered
   733  }
   734  
   735  // schemaOfField returns a OpenAPI Schema Object for a protobuf field.
   736  func schemaOfField(f *descriptor.Field, reg *descriptor.Registry, refs refMap) openapiSchemaObject {
   737  	const (
   738  		singular = 0
   739  		array    = 1
   740  		object   = 2
   741  	)
   742  	var (
   743  		core      schemaCore
   744  		aggregate int
   745  	)
   746  
   747  	fd := f.FieldDescriptorProto
   748  	location := ""
   749  	if ix := strings.LastIndex(f.Message.FQMN(), "."); ix > 0 {
   750  		location = f.Message.FQMN()[0:ix]
   751  	}
   752  	if m, err := reg.LookupMsg(location, f.GetTypeName()); err == nil {
   753  		if opt := m.GetOptions(); opt != nil && opt.MapEntry != nil && *opt.MapEntry {
   754  			fd = m.GetField()[1]
   755  			aggregate = object
   756  		}
   757  	}
   758  	if fd.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
   759  		aggregate = array
   760  	}
   761  
   762  	var props *openapiSchemaObjectProperties
   763  
   764  	switch ft := fd.GetType(); ft {
   765  	case descriptorpb.FieldDescriptorProto_TYPE_ENUM, descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, descriptorpb.FieldDescriptorProto_TYPE_GROUP:
   766  		if wktSchema, ok := wktSchemas[fd.GetTypeName()]; ok {
   767  			core = wktSchema
   768  			if fd.GetTypeName() == ".google.protobuf.Empty" {
   769  				props = &openapiSchemaObjectProperties{}
   770  			}
   771  		} else {
   772  			swgRef, ok := fullyQualifiedNameToOpenAPIName(fd.GetTypeName(), reg)
   773  			if !ok {
   774  				panic(fmt.Sprintf("can't resolve OpenAPI ref from typename %q", fd.GetTypeName()))
   775  			}
   776  			core = schemaCore{
   777  				Ref: "#/definitions/" + swgRef,
   778  			}
   779  			if refs != nil {
   780  				refs[fd.GetTypeName()] = struct{}{}
   781  			}
   782  		}
   783  	default:
   784  		ftype, format, ok := primitiveSchema(ft)
   785  		if ok {
   786  			core = schemaCore{Type: ftype, Format: format}
   787  		} else {
   788  			core = schemaCore{Type: ft.String(), Format: "UNKNOWN"}
   789  		}
   790  	}
   791  
   792  	ret := openapiSchemaObject{}
   793  
   794  	switch aggregate {
   795  	case array:
   796  		if _, ok := wktSchemas[fd.GetTypeName()]; !ok && fd.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE {
   797  			core.Type = "object"
   798  		}
   799  		ret = openapiSchemaObject{
   800  			schemaCore: schemaCore{
   801  				Type:  "array",
   802  				Items: (*openapiItemsObject)(&openapiSchemaObject{schemaCore: core}),
   803  			},
   804  		}
   805  	case object:
   806  		ret = openapiSchemaObject{
   807  			schemaCore: schemaCore{
   808  				Type: "object",
   809  			},
   810  			AdditionalProperties: &openapiSchemaObject{Properties: props, schemaCore: core},
   811  		}
   812  	default:
   813  		ret = openapiSchemaObject{
   814  			schemaCore: core,
   815  			Properties: props,
   816  		}
   817  	}
   818  
   819  	if j, err := getFieldOpenAPIOption(reg, f); err == nil {
   820  		updateswaggerObjectFromJSONSchema(&ret, j, reg, f)
   821  	}
   822  
   823  	if j, err := getFieldBehaviorOption(reg, f); err == nil {
   824  		updateSwaggerObjectFromFieldBehavior(&ret, j, reg, f)
   825  	}
   826  
   827  	for i, required := range ret.Required {
   828  		if required == f.GetName() {
   829  			ret.Required[i] = reg.FieldName(f)
   830  		}
   831  	}
   832  
   833  	if reg.GetProto3OptionalNullable() && f.GetProto3Optional() {
   834  		ret.XNullable = true
   835  	}
   836  
   837  	return ret
   838  }
   839  
   840  // primitiveSchema returns a pair of "Type" and "Format" in JSON Schema for
   841  // the given primitive field type.
   842  // The last return parameter is true iff the field type is actually primitive.
   843  func primitiveSchema(t descriptorpb.FieldDescriptorProto_Type) (ftype, format string, ok bool) {
   844  	switch t {
   845  	case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:
   846  		return "number", "double", true
   847  	case descriptorpb.FieldDescriptorProto_TYPE_FLOAT:
   848  		return "number", "float", true
   849  	case descriptorpb.FieldDescriptorProto_TYPE_INT64:
   850  		return "string", "int64", true
   851  	case descriptorpb.FieldDescriptorProto_TYPE_UINT64:
   852  		// 64bit integer types are marshaled as string in the default JSONPb marshaler.
   853  		// TODO(yugui) Add an option to declare 64bit integers as int64.
   854  		//
   855  		// NOTE: uint64 is not a predefined format of integer type in OpenAPI spec.
   856  		// So we cannot expect that uint64 is commonly supported by OpenAPI processor.
   857  		return "string", "uint64", true
   858  	case descriptorpb.FieldDescriptorProto_TYPE_INT32:
   859  		return "integer", "int32", true
   860  	case descriptorpb.FieldDescriptorProto_TYPE_FIXED64:
   861  		// Ditto.
   862  		return "string", "uint64", true
   863  	case descriptorpb.FieldDescriptorProto_TYPE_FIXED32:
   864  		// Ditto.
   865  		return "integer", "int64", true
   866  	case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
   867  		// NOTE: in OpenAPI specification, format should be empty on boolean type
   868  		return "boolean", "", true
   869  	case descriptorpb.FieldDescriptorProto_TYPE_STRING:
   870  		// NOTE: in OpenAPI specification, can be empty on string type
   871  		// see: https://swagger.io/specification/v2/#data-types
   872  		return "string", "", true
   873  	case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
   874  		return "string", "byte", true
   875  	case descriptorpb.FieldDescriptorProto_TYPE_UINT32:
   876  		// Ditto.
   877  		return "integer", "int64", true
   878  	case descriptorpb.FieldDescriptorProto_TYPE_SFIXED32:
   879  		return "integer", "int32", true
   880  	case descriptorpb.FieldDescriptorProto_TYPE_SFIXED64:
   881  		return "string", "int64", true
   882  	case descriptorpb.FieldDescriptorProto_TYPE_SINT32:
   883  		return "integer", "int32", true
   884  	case descriptorpb.FieldDescriptorProto_TYPE_SINT64:
   885  		return "string", "int64", true
   886  	default:
   887  		return "", "", false
   888  	}
   889  }
   890  
   891  // renderEnumerationsAsDefinition inserts enums into the definitions object.
   892  func renderEnumerationsAsDefinition(enums enumMap, d openapiDefinitionsObject, reg *descriptor.Registry) {
   893  	for _, enum := range enums {
   894  		swgName, ok := fullyQualifiedNameToOpenAPIName(enum.FQEN(), reg)
   895  		if !ok {
   896  			panic(fmt.Sprintf("can't resolve OpenAPI name from FQEN %q", enum.FQEN()))
   897  		}
   898  		enumComments := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index))
   899  
   900  		// it may be necessary to sort the result of the GetValue function.
   901  		enumNames := listEnumNames(reg, enum)
   902  		defaultValue := getEnumDefault(reg, enum)
   903  		valueComments := enumValueProtoComments(reg, enum)
   904  		if valueComments != "" {
   905  			enumComments = strings.TrimLeft(enumComments+"\n\n "+valueComments, "\n")
   906  		}
   907  		enumSchemaObject := openapiSchemaObject{
   908  			schemaCore: schemaCore{
   909  				Type:    "string",
   910  				Enum:    enumNames,
   911  				Default: defaultValue,
   912  			},
   913  		}
   914  		if reg.GetEnumsAsInts() {
   915  			enumSchemaObject.Type = "integer"
   916  			enumSchemaObject.Format = "int32"
   917  			enumSchemaObject.Default = getEnumDefaultNumber(reg, enum)
   918  			enumSchemaObject.Enum = listEnumNumbers(reg, enum)
   919  		}
   920  		if err := updateOpenAPIDataFromComments(reg, &enumSchemaObject, enum, enumComments, false); err != nil {
   921  			panic(err)
   922  		}
   923  
   924  		d[swgName] = enumSchemaObject
   925  	}
   926  }
   927  
   928  // Take in a FQMN or FQEN and return a OpenAPI safe version of the FQMN and
   929  // a boolean indicating if FQMN was properly resolved.
   930  func fullyQualifiedNameToOpenAPIName(fqn string, reg *descriptor.Registry) (string, bool) {
   931  	registriesSeenMutex.Lock()
   932  	defer registriesSeenMutex.Unlock()
   933  	if mapping, present := registriesSeen[reg]; present {
   934  		ret, ok := mapping[fqn]
   935  		return ret, ok
   936  	}
   937  	mapping := resolveFullyQualifiedNameToOpenAPINames(append(reg.GetAllFQMNs(), append(reg.GetAllFQENs(), reg.GetAllFQMethNs()...)...), reg.GetOpenAPINamingStrategy())
   938  	registriesSeen[reg] = mapping
   939  	ret, ok := mapping[fqn]
   940  	return ret, ok
   941  }
   942  
   943  // Lookup message type by location.name and return a openapiv2-safe version
   944  // of its FQMN.
   945  func lookupMsgAndOpenAPIName(location, name string, reg *descriptor.Registry) (*descriptor.Message, string, error) {
   946  	msg, err := reg.LookupMsg(location, name)
   947  	if err != nil {
   948  		return nil, "", err
   949  	}
   950  	swgName, ok := fullyQualifiedNameToOpenAPIName(msg.FQMN(), reg)
   951  	if !ok {
   952  		return nil, "", fmt.Errorf("can't map OpenAPI name from FQMN %q", msg.FQMN())
   953  	}
   954  	return msg, swgName, nil
   955  }
   956  
   957  // registriesSeen is used to memoise calls to resolveFullyQualifiedNameToOpenAPINames so
   958  // we don't repeat it unnecessarily, since it can take some time.
   959  var registriesSeen = map[*descriptor.Registry]map[string]string{}
   960  var registriesSeenMutex sync.Mutex
   961  
   962  // Take the names of every proto message and generate a unique reference for each, according to the given strategy.
   963  func resolveFullyQualifiedNameToOpenAPINames(messages []string, namingStrategy string) map[string]string {
   964  	strategyFn := LookupNamingStrategy(namingStrategy)
   965  	if strategyFn == nil {
   966  		return nil
   967  	}
   968  	return strategyFn(messages)
   969  }
   970  
   971  var canRegexp = regexp.MustCompile("{([a-zA-Z][a-zA-Z0-9_.]*)([^}]*)}")
   972  
   973  // templateToParts will split a URL template as defined by https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
   974  // into a string slice with each part as an element of the slice for use by `partsToOpenAPIPath` and `partsToRegexpMap`.
   975  func templateToParts(path string, reg *descriptor.Registry, fields []*descriptor.Field, msgs []*descriptor.Message) []string {
   976  	// It seems like the right thing to do here is to just use
   977  	// strings.Split(path, "/") but that breaks badly when you hit a url like
   978  	// /{my_field=prefix/*}/ and end up with 2 sections representing my_field.
   979  	// Instead do the right thing and write a small pushdown (counter) automata
   980  	// for it.
   981  	var parts []string
   982  	depth := 0
   983  	buffer := ""
   984  	jsonBuffer := ""
   985  pathLoop:
   986  	for i, char := range path {
   987  		switch char {
   988  		case '{':
   989  			// Push on the stack
   990  			depth++
   991  			buffer += string(char)
   992  			jsonBuffer = ""
   993  			jsonBuffer += string(char)
   994  		case '}':
   995  			if depth == 0 {
   996  				panic("Encountered } without matching { before it.")
   997  			}
   998  			// Pop from the stack
   999  			depth--
  1000  			buffer += string(char)
  1001  			if reg.GetUseJSONNamesForFields() &&
  1002  				len(jsonBuffer) > 1 {
  1003  				jsonSnakeCaseName := jsonBuffer[1:]
  1004  				jsonCamelCaseName := lowerCamelCase(jsonSnakeCaseName, fields, msgs)
  1005  				prev := buffer[:len(buffer)-len(jsonSnakeCaseName)-2]
  1006  				buffer = strings.Join([]string{prev, "{", jsonCamelCaseName, "}"}, "")
  1007  				jsonBuffer = ""
  1008  			}
  1009  		case '/':
  1010  			if depth == 0 {
  1011  				parts = append(parts, buffer)
  1012  				buffer = ""
  1013  				// Since the stack was empty when we hit the '/' we are done with this
  1014  				// section.
  1015  				continue
  1016  			}
  1017  			buffer += string(char)
  1018  			jsonBuffer += string(char)
  1019  		case ':':
  1020  			if depth == 0 {
  1021  				// As soon as we find a ":" outside a variable,
  1022  				// everything following is a verb
  1023  				parts = append(parts, buffer)
  1024  				buffer = path[i:]
  1025  				break pathLoop
  1026  			}
  1027  			buffer += string(char)
  1028  			jsonBuffer += string(char)
  1029  		default:
  1030  			buffer += string(char)
  1031  			jsonBuffer += string(char)
  1032  		}
  1033  	}
  1034  
  1035  	// Now append the last element to parts
  1036  	parts = append(parts, buffer)
  1037  
  1038  	return parts
  1039  }
  1040  
  1041  // partsToOpenAPIPath converts each path part of the form /path/{string_value=strprefix/*} which is defined in
  1042  // https://github.com/googleapis/googleapis/blob/master/google/api/http.proto to the OpenAPI expected form /path/{string_value}.
  1043  // For example this would replace the path segment of "{foo=bar/*}" with "{foo}" or "prefix{bang=bash/**}" with "prefix{bang}".
  1044  // OpenAPI 2 only allows simple path parameters with the constraints on that parameter specified in the OpenAPI
  1045  // schema's "pattern" instead of in the path parameter itself.
  1046  func partsToOpenAPIPath(parts []string, overrides map[string]string) string {
  1047  	for index, part := range parts {
  1048  		part = canRegexp.ReplaceAllString(part, "{$1}")
  1049  		if override, ok := overrides[part]; ok {
  1050  			part = override
  1051  		}
  1052  		parts[index] = part
  1053  	}
  1054  	if last := len(parts) - 1; strings.HasPrefix(parts[last], ":") {
  1055  		// Last item is a verb (":" LITERAL).
  1056  		return strings.Join(parts[:last], "/") + parts[last]
  1057  	}
  1058  	return strings.Join(parts, "/")
  1059  }
  1060  
  1061  // partsToRegexpMap returns a map of parameter name to ECMA 262 patterns
  1062  // which is what the "pattern" field on an OpenAPI parameter expects.
  1063  // See https://swagger.io/specification/v2/ (Parameter Object) and
  1064  // https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3.
  1065  // The expression is generated based on expressions defined by https://github.com/googleapis/googleapis/blob/master/google/api/http.proto
  1066  // "Path Template Syntax" section which allow for a "param_name=foobar/*/bang/**" style expressions inside
  1067  // the path parameter placeholders that indicate constraints on the values of those parameters.
  1068  // This function will scan the split parts of a path template for parameters and
  1069  // outputs a map of the name of the parameter to a ECMA regular expression.  See the http.proto file for descriptions
  1070  // of the supported syntax. This function will ignore any path parameters that don't contain a "=" after the
  1071  // parameter name.  For supported parameters, we assume "*" represent all characters except "/" as it's
  1072  // intended to match a single path element and we assume "**" matches any character as it's intended to match multiple
  1073  // path elements.
  1074  // For example "{name=organizations/*/roles/*}" would produce the regular expression for the "name" parameter of
  1075  // "organizations/[^/]+/roles/[^/]+" or "{bar=bing/*/bang/**}" would produce the regular expression for the "bar"
  1076  // parameter of "bing/[^/]+/bang/.+".
  1077  //
  1078  // Note that OpenAPI does not actually support path parameters with "/", see https://github.com/OAI/OpenAPI-Specification/issues/892
  1079  func partsToRegexpMap(parts []string) map[string]string {
  1080  	regExps := make(map[string]string)
  1081  	for _, part := range parts {
  1082  		if strings.Contains(part, "/") {
  1083  			grpclog.Warningf("Path parameter %q contains '/', which is not supported in OpenAPI", part)
  1084  		}
  1085  		if submatch := canRegexp.FindStringSubmatch(part); len(submatch) > 2 {
  1086  			if strings.HasPrefix(submatch[2], "=") { // this part matches the standard and should be made into a regular expression
  1087  				// assume the string's characters other than "**" and "*" are literals (not necessarily a good assumption 100% of the times, but it will support most use cases)
  1088  				regex := submatch[2][1:]
  1089  				regex = strings.ReplaceAll(regex, "**", ".+")   // ** implies any character including "/"
  1090  				regex = strings.ReplaceAll(regex, "*", "[^/]+") // * implies any character except "/"
  1091  				regExps[submatch[1]] = regex
  1092  			}
  1093  		}
  1094  	}
  1095  	return regExps
  1096  }
  1097  
  1098  func renderServiceTags(services []*descriptor.Service, reg *descriptor.Registry) []openapiTagObject {
  1099  	var tags []openapiTagObject
  1100  	for _, svc := range services {
  1101  		if !isVisible(getServiceVisibilityOption(svc), reg) {
  1102  			continue
  1103  		}
  1104  		tagName := svc.GetName()
  1105  		if pkg := svc.File.GetPackage(); pkg != "" && reg.IsIncludePackageInTags() {
  1106  			tagName = pkg + "." + tagName
  1107  		}
  1108  
  1109  		tag := openapiTagObject{
  1110  			Name: tagName,
  1111  		}
  1112  
  1113  		opts, err := getServiceOpenAPIOption(reg, svc)
  1114  		if err != nil {
  1115  			grpclog.Error(err)
  1116  			return nil
  1117  		}
  1118  		if opts != nil {
  1119  			tag.Description = opts.Description
  1120  			if reg.GetUseGoTemplate() {
  1121  				tag.Description = goTemplateComments(tag.Description, svc, reg)
  1122  			}
  1123  			if opts.ExternalDocs != nil {
  1124  				tag.ExternalDocs = &openapiExternalDocumentationObject{
  1125  					Description: opts.ExternalDocs.Description,
  1126  					URL:         opts.ExternalDocs.Url,
  1127  				}
  1128  				if reg.GetUseGoTemplate() {
  1129  					tag.ExternalDocs.Description = goTemplateComments(opts.ExternalDocs.Description, svc, reg)
  1130  				}
  1131  			}
  1132  			if opts.GetName() != "" {
  1133  				tag.Name = opts.GetName()
  1134  			}
  1135  		}
  1136  		tags = append(tags, tag)
  1137  	}
  1138  	return tags
  1139  }
  1140  
  1141  func renderServices(services []*descriptor.Service, paths *openapiPathsObject, reg *descriptor.Registry, requestResponseRefs, customRefs refMap, msgs []*descriptor.Message, defs openapiDefinitionsObject) error {
  1142  	// Correctness of svcIdx and methIdx depends on 'services' containing the services in the same order as the 'file.Service' array.
  1143  	svcBaseIdx := 0
  1144  	var lastFile *descriptor.File = nil
  1145  	for svcIdx, svc := range services {
  1146  		if svc.File != lastFile {
  1147  			lastFile = svc.File
  1148  			svcBaseIdx = svcIdx
  1149  		}
  1150  
  1151  		if !isVisible(getServiceVisibilityOption(svc), reg) {
  1152  			continue
  1153  		}
  1154  
  1155  		for methIdx, meth := range svc.Methods {
  1156  			if !isVisible(getMethodVisibilityOption(meth), reg) {
  1157  				continue
  1158  			}
  1159  
  1160  			for bIdx, b := range meth.Bindings {
  1161  				operationFunc := operationForMethod(b.HTTPMethod)
  1162  				// Iterate over all the OpenAPI parameters
  1163  				parameters := openapiParametersObject{}
  1164  				// split the path template into its parts
  1165  				parts := templateToParts(b.PathTmpl.Template, reg, meth.RequestType.Fields, msgs)
  1166  				// extract any constraints specified in the path placeholders into ECMA regular expressions
  1167  				pathParamRegexpMap := partsToRegexpMap(parts)
  1168  				// Keep track of path parameter overrides
  1169  				var pathParamNames = make(map[string]string)
  1170  				for _, parameter := range b.PathParams {
  1171  
  1172  					var paramType, paramFormat, desc, collectionFormat string
  1173  					var defaultValue interface{}
  1174  					var enumNames interface{}
  1175  					var items *openapiItemsObject
  1176  					var minItems *int
  1177  					var extensions []extension
  1178  					switch pt := parameter.Target.GetType(); pt {
  1179  					case descriptorpb.FieldDescriptorProto_TYPE_GROUP, descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
  1180  						if descriptor.IsWellKnownType(parameter.Target.GetTypeName()) {
  1181  							if parameter.IsRepeated() {
  1182  								return errors.New("only primitive and enum types are allowed in repeated path parameters")
  1183  							}
  1184  							schema := schemaOfField(parameter.Target, reg, customRefs)
  1185  							paramType = schema.Type
  1186  							paramFormat = schema.Format
  1187  							desc = schema.Description
  1188  							defaultValue = schema.Default
  1189  							extensions = schema.extensions
  1190  						} else {
  1191  							return errors.New("only primitive and well-known types are allowed in path parameters")
  1192  						}
  1193  					case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
  1194  						enum, err := reg.LookupEnum("", parameter.Target.GetTypeName())
  1195  						if err != nil {
  1196  							return err
  1197  						}
  1198  						paramType = "string"
  1199  						paramFormat = ""
  1200  						enumNames = listEnumNames(reg, enum)
  1201  						if reg.GetEnumsAsInts() {
  1202  							paramType = "integer"
  1203  							paramFormat = ""
  1204  							enumNames = listEnumNumbers(reg, enum)
  1205  						}
  1206  
  1207  						schema := schemaOfField(parameter.Target, reg, customRefs)
  1208  						desc = schema.Description
  1209  						defaultValue = schema.Default
  1210  						extensions = schema.extensions
  1211  					default:
  1212  						var ok bool
  1213  						paramType, paramFormat, ok = primitiveSchema(pt)
  1214  						if !ok {
  1215  							return fmt.Errorf("unknown field type %v", pt)
  1216  						}
  1217  
  1218  						schema := schemaOfField(parameter.Target, reg, customRefs)
  1219  						desc = schema.Description
  1220  						defaultValue = schema.Default
  1221  						extensions = schema.extensions
  1222  						// If there is no mandatory format based on the field,
  1223  						// allow it to be overridden by the user
  1224  						if paramFormat == "" {
  1225  							paramFormat = schema.Format
  1226  						}
  1227  					}
  1228  
  1229  					if parameter.IsRepeated() {
  1230  						core := schemaCore{Type: paramType, Format: paramFormat}
  1231  						if parameter.IsEnum() {
  1232  							core.Enum = enumNames
  1233  							enumNames = nil
  1234  						}
  1235  						items = (*openapiItemsObject)(&openapiSchemaObject{schemaCore: core})
  1236  						paramType = "array"
  1237  						paramFormat = ""
  1238  						collectionFormat = reg.GetRepeatedPathParamSeparatorName()
  1239  						minItems = new(int)
  1240  						*minItems = 1
  1241  					}
  1242  
  1243  					if desc == "" {
  1244  						desc = fieldProtoComments(reg, parameter.Target.Message, parameter.Target)
  1245  					}
  1246  					parameterString := parameter.String()
  1247  					if reg.GetUseJSONNamesForFields() {
  1248  						parameterString = lowerCamelCase(parameterString, meth.RequestType.Fields, msgs)
  1249  					}
  1250  					var pattern string
  1251  					if regExp, ok := pathParamRegexpMap[parameterString]; ok {
  1252  						pattern = regExp
  1253  					}
  1254  					if fc := getFieldConfiguration(reg, parameter.Target); fc != nil {
  1255  						pathParamName := fc.GetPathParamName()
  1256  						if pathParamName != "" && pathParamName != parameterString {
  1257  							pathParamNames["{"+parameterString+"}"] = "{" + pathParamName + "}"
  1258  							parameterString, _, _ = strings.Cut(pathParamName, "=")
  1259  						}
  1260  					}
  1261  					parameters = append(parameters, openapiParameterObject{
  1262  						Name:        parameterString,
  1263  						Description: desc,
  1264  						In:          "path",
  1265  						Required:    true,
  1266  						Default:     defaultValue,
  1267  						// Parameters in gRPC-Gateway can only be strings?
  1268  						Type:             paramType,
  1269  						Format:           paramFormat,
  1270  						Enum:             enumNames,
  1271  						Items:            items,
  1272  						CollectionFormat: collectionFormat,
  1273  						MinItems:         minItems,
  1274  						Pattern:          pattern,
  1275  						extensions:       extensions,
  1276  					})
  1277  				}
  1278  				// Now check if there is a body parameter
  1279  				if b.Body != nil {
  1280  					// Recursively render fields as definitions as long as they contain path parameters.
  1281  					// Special case for top level body if we don't have a body field.
  1282  					var schema openapiSchemaObject
  1283  					desc := ""
  1284  					var bodyFieldName string
  1285  					schema = openapiSchemaObject{
  1286  						schemaCore: schemaCore{},
  1287  					}
  1288  					if len(b.Body.FieldPath) == 0 {
  1289  						// No field for body, use type.
  1290  						bodyFieldName = "body"
  1291  						wknSchemaCore, isWkn := wktSchemas[meth.RequestType.FQMN()]
  1292  						if isWkn {
  1293  							schema.schemaCore = wknSchemaCore
  1294  							// 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.
  1295  							if meth.RequestType.FQMN() == ".google.protobuf.Empty" {
  1296  								schema.Properties = &openapiSchemaObjectProperties{}
  1297  							}
  1298  						} else {
  1299  							messageSchema, err := renderMessageAsDefinition(meth.RequestType, reg, customRefs, b.PathParams)
  1300  							if err != nil {
  1301  								return err
  1302  							}
  1303  							if len(b.PathParams) == 0 {
  1304  								if err := schema.setRefFromFQN(meth.RequestType.FQMN(), reg); err != nil {
  1305  									return err
  1306  								}
  1307  								desc = messageSchema.Description
  1308  							} else {
  1309  								if meth.Name != nil {
  1310  									methFQN, ok := fullyQualifiedNameToOpenAPIName(meth.FQMN(), reg)
  1311  									if !ok {
  1312  										panic(fmt.Errorf("failed to resolve method FQN: '%s'", meth.FQMN()))
  1313  									}
  1314  									defName := methFQN + "Body"
  1315  									schema.Ref = fmt.Sprintf("#/definitions/%s", defName)
  1316  									defs[defName] = messageSchema
  1317  								} else {
  1318  									schema = messageSchema
  1319  									if schema.Properties == nil || len(*schema.Properties) == 0 {
  1320  										grpclog.Warningf("created a body with 0 properties in the message, this might be unintended: %s", *meth.RequestType)
  1321  									}
  1322  								}
  1323  							}
  1324  						}
  1325  					} else {
  1326  						// Body field path is limited to one path component. From google.api.HttpRule.body:
  1327  						// "NOTE: the referred field must be present at the top-level of the request message type."
  1328  						// Ref: https://github.com/googleapis/googleapis/blob/b3397f5febbf21dfc69b875ddabaf76bee765058/google/api/http.proto#L350-L352
  1329  						if len(b.Body.FieldPath) > 1 {
  1330  							return fmt.Errorf("Body of request %q is not a top level field: '%v'.", meth.Service.GetName(), b.Body.FieldPath)
  1331  						}
  1332  						bodyField := b.Body.FieldPath[0]
  1333  						if reg.GetUseJSONNamesForFields() {
  1334  							bodyFieldName = lowerCamelCase(bodyField.Name, meth.RequestType.Fields, msgs)
  1335  						} else {
  1336  							bodyFieldName = bodyField.Name
  1337  						}
  1338  						// Align pathParams with body field path.
  1339  						pathParams := subPathParams(bodyField.Name, b.PathParams)
  1340  						var err error
  1341  						schema, err = renderFieldAsDefinition(bodyField.Target, reg, customRefs, pathParams)
  1342  						if err != nil {
  1343  							return err
  1344  						}
  1345  						if schema.Title != "" {
  1346  							desc = mergeDescription(schema)
  1347  						} else {
  1348  							desc = fieldProtoComments(reg, bodyField.Target.Message, bodyField.Target)
  1349  						}
  1350  					}
  1351  
  1352  					if meth.GetClientStreaming() {
  1353  						desc += " (streaming inputs)"
  1354  					}
  1355  					parameters = append(parameters, openapiParameterObject{
  1356  						Name:        bodyFieldName,
  1357  						Description: desc,
  1358  						In:          "body",
  1359  						Required:    true,
  1360  						Schema:      &schema,
  1361  					})
  1362  				}
  1363  
  1364  				// add the parameters to the query string
  1365  				queryParams, err := messageToQueryParameters(meth.RequestType, reg, b.PathParams, b.Body, b.HTTPMethod)
  1366  				if err != nil {
  1367  					return err
  1368  				}
  1369  				parameters = append(parameters, queryParams...)
  1370  
  1371  				path := partsToOpenAPIPath(parts, pathParamNames)
  1372  
  1373  				pathItemObject, ok := getPathItemObject(*paths, path)
  1374  
  1375  				if !ok {
  1376  					pathItemObject = openapiPathItemObject{}
  1377  				} else {
  1378  					// handle case where we have an existing mapping for the same path and method
  1379  					existingOperationObject := operationFunc(&pathItemObject)
  1380  					if existingOperationObject != nil {
  1381  						var firstPathParameter *openapiParameterObject
  1382  						var firstParamIndex int
  1383  						for index, param := range parameters {
  1384  							param := param
  1385  							if param.In == "path" {
  1386  								firstPathParameter = &param
  1387  								firstParamIndex = index
  1388  								break
  1389  							}
  1390  						}
  1391  						if firstPathParameter == nil {
  1392  							// Without a path parameter, there is nothing to vary to support multiple mappings of the same path/method.
  1393  							// Previously this did not log an error and only overwrote the mapping, we now log the error but
  1394  							// still overwrite the mapping
  1395  							grpclog.Errorf("Duplicate mapping for path %s %s", b.HTTPMethod, path)
  1396  						} else {
  1397  							newPathCount := 0
  1398  							var newPath string
  1399  							var newPathElement string
  1400  							// Iterate until there is not an existing operation that matches the same escaped path.
  1401  							// Most of the time this will only be a single iteration, but a large API could technically have
  1402  							// a pretty large amount of these if it used similar patterns for all its functions.
  1403  							for existingOperationObject != nil {
  1404  								newPathCount += 1
  1405  								newPathElement = firstPathParameter.Name + pathParamUniqueSuffixDeliminator + strconv.Itoa(newPathCount)
  1406  								newPath = strings.ReplaceAll(path, "{"+firstPathParameter.Name+"}", "{"+newPathElement+"}")
  1407  
  1408  								if newPathItemObject, ok := getPathItemObject(*paths, newPath); ok {
  1409  									existingOperationObject = operationFunc(&newPathItemObject)
  1410  								} else {
  1411  									existingOperationObject = nil
  1412  								}
  1413  							}
  1414  							// update the pathItemObject we are adding to with the new path
  1415  							pathItemObject, _ = getPathItemObject(*paths, newPath)
  1416  							firstPathParameter.Name = newPathElement
  1417  							path = newPath
  1418  							parameters[firstParamIndex] = *firstPathParameter
  1419  						}
  1420  					}
  1421  				}
  1422  
  1423  				methProtoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.ServiceDescriptorProto)(nil)), "Method")
  1424  				desc := "A successful response."
  1425  				var responseSchema openapiSchemaObject
  1426  
  1427  				if b.ResponseBody == nil || len(b.ResponseBody.FieldPath) == 0 {
  1428  					responseSchema = openapiSchemaObject{
  1429  						schemaCore: schemaCore{},
  1430  					}
  1431  
  1432  					// Don't link to a full definition for
  1433  					// empty; it's overly verbose.
  1434  					// schema.Properties{} renders it as
  1435  					// well, without a definition
  1436  					wknSchemaCore, isWkn := wktSchemas[meth.ResponseType.FQMN()]
  1437  					if !isWkn {
  1438  						if err := responseSchema.setRefFromFQN(meth.ResponseType.FQMN(), reg); err != nil {
  1439  							return err
  1440  						}
  1441  					} else {
  1442  						responseSchema.schemaCore = wknSchemaCore
  1443  
  1444  						// 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.
  1445  						if meth.ResponseType.FQMN() == ".google.protobuf.Empty" {
  1446  							responseSchema.Properties = &openapiSchemaObjectProperties{}
  1447  						}
  1448  					}
  1449  				} else {
  1450  					// This is resolving the value of response_body in the google.api.HttpRule
  1451  					lastField := b.ResponseBody.FieldPath[len(b.ResponseBody.FieldPath)-1]
  1452  					responseSchema = schemaOfField(lastField.Target, reg, customRefs)
  1453  					if responseSchema.Description != "" {
  1454  						desc = responseSchema.Description
  1455  					} else {
  1456  						desc = fieldProtoComments(reg, lastField.Target.Message, lastField.Target)
  1457  					}
  1458  				}
  1459  				if meth.GetServerStreaming() {
  1460  					desc += "(streaming responses)"
  1461  					responseSchema.Type = "object"
  1462  					swgRef, _ := fullyQualifiedNameToOpenAPIName(meth.ResponseType.FQMN(), reg)
  1463  					responseSchema.Title = "Stream result of " + swgRef
  1464  
  1465  					props := openapiSchemaObjectProperties{
  1466  						keyVal{
  1467  							Key: "result",
  1468  							Value: openapiSchemaObject{
  1469  								schemaCore: schemaCore{
  1470  									Ref: responseSchema.Ref,
  1471  								},
  1472  							},
  1473  						},
  1474  					}
  1475  					if !reg.GetDisableDefaultErrors() {
  1476  						statusDef, hasStatus := fullyQualifiedNameToOpenAPIName(".google.rpc.Status", reg)
  1477  						if hasStatus {
  1478  							props = append(props, keyVal{
  1479  								Key: "error",
  1480  								Value: openapiSchemaObject{
  1481  									schemaCore: schemaCore{
  1482  										Ref: fmt.Sprintf("#/definitions/%s", statusDef)},
  1483  								},
  1484  							})
  1485  						}
  1486  					}
  1487  
  1488  					// Special case HttpBody responses, they will be unformatted bytes
  1489  					if meth.ResponseType.FQMN() == ".google.api.HttpBody" {
  1490  						responseSchema.Type = "string"
  1491  						responseSchema.Format = "binary"
  1492  						responseSchema.Title = "Free form byte stream"
  1493  						// The error response is still JSON, but technically the full response
  1494  						// is still unformatted, so don't include the error response structure.
  1495  						props = nil
  1496  					}
  1497  
  1498  					responseSchema.Properties = &props
  1499  					responseSchema.Ref = ""
  1500  				}
  1501  
  1502  				operationObject := &openapiOperationObject{
  1503  					Parameters: parameters,
  1504  					Responses:  openapiResponsesObject{},
  1505  				}
  1506  
  1507  				if !reg.GetDisableDefaultResponses() {
  1508  					operationObject.Responses["200"] = openapiResponseObject{
  1509  						Description: desc,
  1510  						Schema:      responseSchema,
  1511  						Headers:     openapiHeadersObject{},
  1512  					}
  1513  				}
  1514  
  1515  				if !reg.GetDisableServiceTags() {
  1516  					tag := svc.GetName()
  1517  					if pkg := svc.File.GetPackage(); pkg != "" && reg.IsIncludePackageInTags() {
  1518  						tag = pkg + "." + tag
  1519  					}
  1520  					operationObject.Tags = []string{tag}
  1521  				}
  1522  
  1523  				if !reg.GetDisableDefaultErrors() {
  1524  					errDef, hasErrDef := fullyQualifiedNameToOpenAPIName(".google.rpc.Status", reg)
  1525  					if hasErrDef {
  1526  						// https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#responses-object
  1527  						operationObject.Responses["default"] = openapiResponseObject{
  1528  							Description: "An unexpected error response.",
  1529  							Schema: openapiSchemaObject{
  1530  								schemaCore: schemaCore{
  1531  									Ref: fmt.Sprintf("#/definitions/%s", errDef),
  1532  								},
  1533  							},
  1534  						}
  1535  					}
  1536  				}
  1537  				operationObject.OperationID = fmt.Sprintf("%s_%s", svc.GetName(), meth.GetName())
  1538  				if reg.GetSimpleOperationIDs() {
  1539  					operationObject.OperationID = meth.GetName()
  1540  				}
  1541  				if bIdx != 0 {
  1542  					// OperationID must be unique in an OpenAPI v2 definition.
  1543  					operationObject.OperationID += strconv.Itoa(bIdx + 1)
  1544  				}
  1545  
  1546  				// Fill reference map with referenced request messages
  1547  				for _, param := range operationObject.Parameters {
  1548  					if param.Schema != nil && param.Schema.Ref != "" {
  1549  						requestResponseRefs[param.Schema.Ref] = struct{}{}
  1550  					}
  1551  				}
  1552  
  1553  				methComments := protoComments(reg, svc.File, nil, "Service", int32(svcIdx-svcBaseIdx), methProtoPath, int32(methIdx))
  1554  				if err := updateOpenAPIDataFromComments(reg, operationObject, meth, methComments, false); err != nil {
  1555  					panic(err)
  1556  				}
  1557  
  1558  				svcOpts, err := getServiceOpenAPIOption(reg, svc)
  1559  				if err != nil {
  1560  					grpclog.Error(err)
  1561  					return err
  1562  				}
  1563  				opts, err := getMethodOpenAPIOption(reg, meth)
  1564  				if opts != nil {
  1565  					if err != nil {
  1566  						panic(err)
  1567  					}
  1568  					operationObject.ExternalDocs = protoExternalDocumentationToOpenAPIExternalDocumentation(opts.ExternalDocs, reg, meth)
  1569  					// TODO(ivucica): this would be better supported by looking whether the method is deprecated in the proto file
  1570  					operationObject.Deprecated = opts.Deprecated
  1571  
  1572  					if opts.Summary != "" {
  1573  						operationObject.Summary = opts.Summary
  1574  					}
  1575  					if opts.Description != "" {
  1576  						operationObject.Description = opts.Description
  1577  					}
  1578  					if len(opts.Tags) > 0 {
  1579  						operationObject.Tags = make([]string, len(opts.Tags))
  1580  						copy(operationObject.Tags, opts.Tags)
  1581  					} else if svcOpts.GetName() != "" {
  1582  						operationObject.Tags = []string{svcOpts.GetName()}
  1583  					}
  1584  					if opts.OperationId != "" {
  1585  						operationObject.OperationID = opts.OperationId
  1586  					}
  1587  					if opts.Security != nil {
  1588  						newSecurity := []openapiSecurityRequirementObject{}
  1589  						if operationObject.Security != nil {
  1590  							newSecurity = *operationObject.Security
  1591  						}
  1592  						for _, secReq := range opts.Security {
  1593  							newSecReq := openapiSecurityRequirementObject{}
  1594  							for secReqKey, secReqValue := range secReq.SecurityRequirement {
  1595  								if secReqValue == nil {
  1596  									continue
  1597  								}
  1598  
  1599  								newSecReqValue := make([]string, len(secReqValue.Scope))
  1600  								copy(newSecReqValue, secReqValue.Scope)
  1601  								newSecReq[secReqKey] = newSecReqValue
  1602  							}
  1603  
  1604  							if len(newSecReq) > 0 {
  1605  								newSecurity = append(newSecurity, newSecReq)
  1606  							}
  1607  						}
  1608  						operationObject.Security = &newSecurity
  1609  					}
  1610  					if opts.Responses != nil {
  1611  						for name, resp := range opts.Responses {
  1612  							// Merge response data into default response if available.
  1613  							respObj := operationObject.Responses[name]
  1614  							if resp.Description != "" {
  1615  								respObj.Description = resp.Description
  1616  							}
  1617  							if resp.Schema != nil {
  1618  								respObj.Schema = openapiSchemaFromProtoSchema(resp.Schema, reg, customRefs, meth)
  1619  							}
  1620  							if resp.Examples != nil {
  1621  								respObj.Examples = openapiExamplesFromProtoExamples(resp.Examples)
  1622  							}
  1623  							if resp.Headers != nil {
  1624  								hdrs, err := processHeaders(resp.Headers)
  1625  								if err != nil {
  1626  									return err
  1627  								}
  1628  								respObj.Headers = hdrs
  1629  							}
  1630  							if resp.Extensions != nil {
  1631  								exts, err := processExtensions(resp.Extensions)
  1632  								if err != nil {
  1633  									return err
  1634  								}
  1635  								respObj.extensions = exts
  1636  							}
  1637  							operationObject.Responses[name] = respObj
  1638  						}
  1639  					}
  1640  
  1641  					if opts.Extensions != nil {
  1642  						exts, err := processExtensions(opts.Extensions)
  1643  						if err != nil {
  1644  							return err
  1645  						}
  1646  						operationObject.extensions = exts
  1647  					}
  1648  
  1649  					if len(opts.Consumes) > 0 {
  1650  						operationObject.Consumes = make([]string, len(opts.Consumes))
  1651  						copy(operationObject.Consumes, opts.Consumes)
  1652  					}
  1653  
  1654  					if len(opts.Produces) > 0 {
  1655  						operationObject.Produces = make([]string, len(opts.Produces))
  1656  						copy(operationObject.Produces, opts.Produces)
  1657  					}
  1658  
  1659  					if params := opts.Parameters; params != nil && len(params.Headers) > 0 {
  1660  						for _, header := range params.Headers {
  1661  							param := openapiParameterObject{
  1662  								In:          "header",
  1663  								Name:        header.Name,
  1664  								Description: header.Description,
  1665  								Required:    header.Required,
  1666  								Format:      header.Format,
  1667  							}
  1668  
  1669  							switch header.Type {
  1670  							case openapi_options.HeaderParameter_STRING:
  1671  								param.Type = "string"
  1672  							case openapi_options.HeaderParameter_NUMBER:
  1673  								param.Type = "number"
  1674  							case openapi_options.HeaderParameter_INTEGER:
  1675  								param.Type = "integer"
  1676  							case openapi_options.HeaderParameter_BOOLEAN:
  1677  								param.Type = "boolean"
  1678  							default:
  1679  								return fmt.Errorf("invalid header parameter type: %+v", header.Type)
  1680  							}
  1681  
  1682  							operationObject.Parameters = append(operationObject.Parameters, param)
  1683  						}
  1684  					}
  1685  
  1686  					// TODO(ivucica): add remaining fields of operation object
  1687  				}
  1688  
  1689  				switch b.HTTPMethod {
  1690  				case "DELETE":
  1691  					pathItemObject.Delete = operationObject
  1692  				case "GET":
  1693  					pathItemObject.Get = operationObject
  1694  				case "POST":
  1695  					pathItemObject.Post = operationObject
  1696  				case "PUT":
  1697  					pathItemObject.Put = operationObject
  1698  				case "PATCH":
  1699  					pathItemObject.Patch = operationObject
  1700  				case "HEAD":
  1701  					pathItemObject.Head = operationObject
  1702  				case "OPTIONS":
  1703  					pathItemObject.Options = operationObject
  1704  				}
  1705  
  1706  				updatePaths(paths, path, pathItemObject)
  1707  			}
  1708  		}
  1709  	}
  1710  
  1711  	// Success! return nil on the error object
  1712  	return nil
  1713  }
  1714  
  1715  // Returns the openapiPathItemObject associated with a path. If path is not present, returns
  1716  // empty openapiPathItemObject and false.
  1717  func getPathItemObject(paths openapiPathsObject, path string) (openapiPathItemObject, bool) {
  1718  	for _, pathData := range paths {
  1719  		if pathData.Path == path {
  1720  			return pathData.PathItemObject, true
  1721  		}
  1722  	}
  1723  
  1724  	return openapiPathItemObject{}, false
  1725  }
  1726  
  1727  // If a path already exists in openapiPathsObject, updates that path's openapiPathItemObject. If not,
  1728  // appends a new path and openapiPathItemObject to the openapiPathsObject.
  1729  func updatePaths(paths *openapiPathsObject, path string, pathItemObject openapiPathItemObject) {
  1730  	for i, p := range *paths {
  1731  		if p.Path == path {
  1732  			(*paths)[i].PathItemObject = pathItemObject
  1733  			return
  1734  		}
  1735  	}
  1736  	*paths = append(*paths, pathData{
  1737  		Path:           path,
  1738  		PathItemObject: pathItemObject,
  1739  	})
  1740  }
  1741  
  1742  func mergeDescription(schema openapiSchemaObject) string {
  1743  	desc := schema.Description
  1744  	if schema.Title != "" { // join title because title of parameter object will be ignored
  1745  		desc = strings.TrimSpace(schema.Title + paragraphDeliminator + schema.Description)
  1746  	}
  1747  	return desc
  1748  }
  1749  
  1750  func operationForMethod(httpMethod string) func(*openapiPathItemObject) *openapiOperationObject {
  1751  	switch httpMethod {
  1752  	case "GET":
  1753  		return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Get }
  1754  	case "POST":
  1755  		return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Post }
  1756  	case "PUT":
  1757  		return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Put }
  1758  	case "DELETE":
  1759  		return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Delete }
  1760  	case "PATCH":
  1761  		return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Patch }
  1762  	case "HEAD":
  1763  		return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Head }
  1764  	case "OPTIONS":
  1765  		return func(obj *openapiPathItemObject) *openapiOperationObject { return obj.Options }
  1766  	default:
  1767  		return func(obj *openapiPathItemObject) *openapiOperationObject { return nil }
  1768  	}
  1769  }
  1770  
  1771  // This function is called with a param which contains the entire definition of a method.
  1772  func applyTemplate(p param) (*openapiSwaggerObject, error) {
  1773  	// Create the basic template object. This is the object that everything is
  1774  	// defined off of.
  1775  	s := openapiSwaggerObject{
  1776  		// OpenAPI 2.0 is the version of this document
  1777  		Swagger:     "2.0",
  1778  		Consumes:    []string{"application/json"},
  1779  		Produces:    []string{"application/json"},
  1780  		Paths:       openapiPathsObject{},
  1781  		Definitions: make(openapiDefinitionsObject),
  1782  		Info: openapiInfoObject{
  1783  			Title:   *p.File.Name,
  1784  			Version: "version not set",
  1785  		},
  1786  	}
  1787  
  1788  	// Loops through all the services and their exposed GET/POST/PUT/DELETE definitions
  1789  	// and create entries for all of them.
  1790  	// Also adds custom user specified references to second map.
  1791  	requestResponseRefs, customRefs := refMap{}, refMap{}
  1792  	if err := renderServices(p.Services, &s.Paths, p.reg, requestResponseRefs, customRefs, p.Messages, s.Definitions); err != nil {
  1793  		panic(err)
  1794  	}
  1795  
  1796  	messages := messageMap{}
  1797  	streamingMessages := messageMap{}
  1798  	enums := enumMap{}
  1799  
  1800  	if !p.reg.GetDisableDefaultErrors() {
  1801  		// Add the error type to the message map
  1802  		runtimeError, swgRef, err := lookupMsgAndOpenAPIName("google.rpc", "Status", p.reg)
  1803  		if err == nil {
  1804  			messages[swgRef] = runtimeError
  1805  		} else {
  1806  			// just in case there is an error looking up runtimeError
  1807  			grpclog.Error(err)
  1808  		}
  1809  	}
  1810  
  1811  	// Find all the service's messages and enumerations that are defined (recursively)
  1812  	// and write request, response and other custom (but referenced) types out as definition objects.
  1813  	findServicesMessagesAndEnumerations(p.Services, p.reg, messages, streamingMessages, enums, requestResponseRefs)
  1814  	if err := renderMessagesAsDefinition(messages, s.Definitions, p.reg, customRefs, nil); err != nil {
  1815  		return nil, err
  1816  	}
  1817  	renderEnumerationsAsDefinition(enums, s.Definitions, p.reg)
  1818  
  1819  	// File itself might have some comments and metadata.
  1820  	packageProtoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Package")
  1821  	packageComments := protoComments(p.reg, p.File, nil, "Package", packageProtoPath)
  1822  	if err := updateOpenAPIDataFromComments(p.reg, &s, p, packageComments, true); err != nil {
  1823  		return nil, err
  1824  	}
  1825  
  1826  	// There may be additional options in the OpenAPI option in the proto.
  1827  	spb, err := getFileOpenAPIOption(p.reg, p.File)
  1828  	if err != nil {
  1829  		return nil, err
  1830  	}
  1831  	if spb != nil {
  1832  		if spb.Swagger != "" {
  1833  			s.Swagger = spb.Swagger
  1834  		}
  1835  		if spb.Info != nil {
  1836  			if spb.Info.Title != "" {
  1837  				s.Info.Title = spb.Info.Title
  1838  			}
  1839  			if spb.Info.Description != "" {
  1840  				s.Info.Description = spb.Info.Description
  1841  			}
  1842  			if spb.Info.TermsOfService != "" {
  1843  				s.Info.TermsOfService = spb.Info.TermsOfService
  1844  			}
  1845  			if spb.Info.Version != "" {
  1846  				s.Info.Version = spb.Info.Version
  1847  			}
  1848  			if spb.Info.Contact != nil {
  1849  				if s.Info.Contact == nil {
  1850  					s.Info.Contact = &openapiContactObject{}
  1851  				}
  1852  				if spb.Info.Contact.Name != "" {
  1853  					s.Info.Contact.Name = spb.Info.Contact.Name
  1854  				}
  1855  				if spb.Info.Contact.Url != "" {
  1856  					s.Info.Contact.URL = spb.Info.Contact.Url
  1857  				}
  1858  				if spb.Info.Contact.Email != "" {
  1859  					s.Info.Contact.Email = spb.Info.Contact.Email
  1860  				}
  1861  			}
  1862  			if spb.Info.License != nil {
  1863  				if s.Info.License == nil {
  1864  					s.Info.License = &openapiLicenseObject{}
  1865  				}
  1866  				if spb.Info.License.Name != "" {
  1867  					s.Info.License.Name = spb.Info.License.Name
  1868  				}
  1869  				if spb.Info.License.Url != "" {
  1870  					s.Info.License.URL = spb.Info.License.Url
  1871  				}
  1872  			}
  1873  			if spb.Info.Extensions != nil {
  1874  				exts, err := processExtensions(spb.Info.Extensions)
  1875  				if err != nil {
  1876  					return nil, err
  1877  				}
  1878  				s.Info.extensions = exts
  1879  			}
  1880  		}
  1881  		if spb.Host != "" {
  1882  			s.Host = spb.Host
  1883  		}
  1884  		if spb.BasePath != "" {
  1885  			s.BasePath = spb.BasePath
  1886  		}
  1887  		if len(spb.Schemes) > 0 {
  1888  			s.Schemes = make([]string, len(spb.Schemes))
  1889  			for i, scheme := range spb.Schemes {
  1890  				s.Schemes[i] = strings.ToLower(scheme.String())
  1891  			}
  1892  		}
  1893  		if len(spb.Consumes) > 0 {
  1894  			s.Consumes = make([]string, len(spb.Consumes))
  1895  			copy(s.Consumes, spb.Consumes)
  1896  		}
  1897  		if len(spb.Produces) > 0 {
  1898  			s.Produces = make([]string, len(spb.Produces))
  1899  			copy(s.Produces, spb.Produces)
  1900  		}
  1901  		if spb.SecurityDefinitions != nil && spb.SecurityDefinitions.Security != nil {
  1902  			if s.SecurityDefinitions == nil {
  1903  				s.SecurityDefinitions = openapiSecurityDefinitionsObject{}
  1904  			}
  1905  			for secDefKey, secDefValue := range spb.SecurityDefinitions.Security {
  1906  				var newSecDefValue openapiSecuritySchemeObject
  1907  				if oldSecDefValue, ok := s.SecurityDefinitions[secDefKey]; !ok {
  1908  					newSecDefValue = openapiSecuritySchemeObject{}
  1909  				} else {
  1910  					newSecDefValue = oldSecDefValue
  1911  				}
  1912  				if secDefValue.Type != openapi_options.SecurityScheme_TYPE_INVALID {
  1913  					switch secDefValue.Type {
  1914  					case openapi_options.SecurityScheme_TYPE_BASIC:
  1915  						newSecDefValue.Type = "basic"
  1916  					case openapi_options.SecurityScheme_TYPE_API_KEY:
  1917  						newSecDefValue.Type = "apiKey"
  1918  					case openapi_options.SecurityScheme_TYPE_OAUTH2:
  1919  						newSecDefValue.Type = "oauth2"
  1920  					}
  1921  				}
  1922  				if secDefValue.Description != "" {
  1923  					newSecDefValue.Description = secDefValue.Description
  1924  				}
  1925  				if secDefValue.Name != "" {
  1926  					newSecDefValue.Name = secDefValue.Name
  1927  				}
  1928  				if secDefValue.In != openapi_options.SecurityScheme_IN_INVALID {
  1929  					switch secDefValue.In {
  1930  					case openapi_options.SecurityScheme_IN_QUERY:
  1931  						newSecDefValue.In = "query"
  1932  					case openapi_options.SecurityScheme_IN_HEADER:
  1933  						newSecDefValue.In = "header"
  1934  					}
  1935  				}
  1936  				if secDefValue.Flow != openapi_options.SecurityScheme_FLOW_INVALID {
  1937  					switch secDefValue.Flow {
  1938  					case openapi_options.SecurityScheme_FLOW_IMPLICIT:
  1939  						newSecDefValue.Flow = "implicit"
  1940  					case openapi_options.SecurityScheme_FLOW_PASSWORD:
  1941  						newSecDefValue.Flow = "password"
  1942  					case openapi_options.SecurityScheme_FLOW_APPLICATION:
  1943  						newSecDefValue.Flow = "application"
  1944  					case openapi_options.SecurityScheme_FLOW_ACCESS_CODE:
  1945  						newSecDefValue.Flow = "accessCode"
  1946  					}
  1947  				}
  1948  				if secDefValue.AuthorizationUrl != "" {
  1949  					newSecDefValue.AuthorizationURL = secDefValue.AuthorizationUrl
  1950  				}
  1951  				if secDefValue.TokenUrl != "" {
  1952  					newSecDefValue.TokenURL = secDefValue.TokenUrl
  1953  				}
  1954  				if secDefValue.Scopes != nil {
  1955  					if newSecDefValue.Scopes == nil {
  1956  						newSecDefValue.Scopes = openapiScopesObject{}
  1957  					}
  1958  					for scopeKey, scopeDesc := range secDefValue.Scopes.Scope {
  1959  						newSecDefValue.Scopes[scopeKey] = scopeDesc
  1960  					}
  1961  				}
  1962  				if secDefValue.Extensions != nil {
  1963  					exts, err := processExtensions(secDefValue.Extensions)
  1964  					if err != nil {
  1965  						return nil, err
  1966  					}
  1967  					newSecDefValue.extensions = exts
  1968  				}
  1969  				s.SecurityDefinitions[secDefKey] = newSecDefValue
  1970  			}
  1971  		}
  1972  		if spb.Security != nil {
  1973  			var newSecurity []openapiSecurityRequirementObject
  1974  			if s.Security != nil {
  1975  				newSecurity = s.Security
  1976  			}
  1977  			for _, secReq := range spb.Security {
  1978  				newSecReq := openapiSecurityRequirementObject{}
  1979  				for secReqKey, secReqValue := range secReq.SecurityRequirement {
  1980  					if secReqValue == nil {
  1981  						return nil, fmt.Errorf("malformed security requirement spec for key %q; value is required", secReqKey)
  1982  					}
  1983  					newSecReqValue := make([]string, len(secReqValue.Scope))
  1984  					copy(newSecReqValue, secReqValue.Scope)
  1985  					newSecReq[secReqKey] = newSecReqValue
  1986  				}
  1987  				newSecurity = append(newSecurity, newSecReq)
  1988  			}
  1989  			s.Security = newSecurity
  1990  		}
  1991  		s.ExternalDocs = protoExternalDocumentationToOpenAPIExternalDocumentation(spb.ExternalDocs, p.reg, spb)
  1992  		// Populate all Paths with Responses set at top level,
  1993  		// preferring Responses already set over those at the top level.
  1994  		if spb.Responses != nil {
  1995  			for _, verbs := range s.Paths {
  1996  				var maps []openapiResponsesObject
  1997  				if verbs.PathItemObject.Delete != nil {
  1998  					maps = append(maps, verbs.PathItemObject.Delete.Responses)
  1999  				}
  2000  				if verbs.PathItemObject.Get != nil {
  2001  					maps = append(maps, verbs.PathItemObject.Get.Responses)
  2002  				}
  2003  				if verbs.PathItemObject.Post != nil {
  2004  					maps = append(maps, verbs.PathItemObject.Post.Responses)
  2005  				}
  2006  				if verbs.PathItemObject.Put != nil {
  2007  					maps = append(maps, verbs.PathItemObject.Put.Responses)
  2008  				}
  2009  				if verbs.PathItemObject.Patch != nil {
  2010  					maps = append(maps, verbs.PathItemObject.Patch.Responses)
  2011  				}
  2012  
  2013  				for k, v := range spb.Responses {
  2014  					for _, respMap := range maps {
  2015  						if _, ok := respMap[k]; ok {
  2016  							// Don't overwrite already existing Responses
  2017  							continue
  2018  						}
  2019  						respMap[k] = openapiResponseObject{
  2020  							Description: v.Description,
  2021  							Schema:      openapiSchemaFromProtoSchema(v.Schema, p.reg, customRefs, nil),
  2022  							Examples:    openapiExamplesFromProtoExamples(v.Examples),
  2023  						}
  2024  					}
  2025  				}
  2026  			}
  2027  		}
  2028  
  2029  		if spb.Extensions != nil {
  2030  			exts, err := processExtensions(spb.Extensions)
  2031  			if err != nil {
  2032  				return nil, err
  2033  			}
  2034  			s.extensions = exts
  2035  		}
  2036  
  2037  		if spb.Tags != nil {
  2038  			for _, v := range spb.Tags {
  2039  				newTag := openapiTagObject{}
  2040  				newTag.Name = v.Name
  2041  				newTag.Description = v.Description
  2042  				if p.reg.GetUseGoTemplate() {
  2043  					newTag.Description = goTemplateComments(newTag.Description, nil, p.reg)
  2044  				}
  2045  				if v.ExternalDocs != nil {
  2046  					newTag.ExternalDocs = &openapiExternalDocumentationObject{
  2047  						Description: v.ExternalDocs.Description,
  2048  						URL:         v.ExternalDocs.Url,
  2049  					}
  2050  					if p.reg.GetUseGoTemplate() {
  2051  						newTag.ExternalDocs.Description = goTemplateComments(v.ExternalDocs.Description, nil, p.reg)
  2052  					}
  2053  				}
  2054  				if v.Extensions != nil {
  2055  					exts, err := processExtensions(v.Extensions)
  2056  					if err != nil {
  2057  						return nil, err
  2058  					}
  2059  					newTag.extensions = exts
  2060  				}
  2061  				s.Tags = append(s.Tags, newTag)
  2062  			}
  2063  		}
  2064  
  2065  		// Additional fields on the OpenAPI v2 spec's "OpenAPI" object
  2066  		// should be added here, once supported in the proto.
  2067  	}
  2068  
  2069  	if !p.reg.GetDisableServiceTags() {
  2070  		s.Tags = mergeTags(s.Tags, renderServiceTags(p.Services, p.reg))
  2071  	}
  2072  
  2073  	// Finally add any references added by users that aren't
  2074  	// otherwise rendered.
  2075  	if err := addCustomRefs(s.Definitions, p.reg, customRefs); err != nil {
  2076  		return nil, err
  2077  	}
  2078  
  2079  	return &s, nil
  2080  }
  2081  
  2082  func mergeTags(existingTags []openapiTagObject, tags []openapiTagObject) []openapiTagObject {
  2083  	for _, tag := range tags {
  2084  		matched := false
  2085  		for i, existingTag := range existingTags {
  2086  			if existingTag.Name == tag.Name {
  2087  				if existingTag.Description == "" {
  2088  					existingTags[i].Description = tag.Description
  2089  				}
  2090  				if existingTag.ExternalDocs == nil {
  2091  					existingTags[i].ExternalDocs = tag.ExternalDocs
  2092  				} else if tag.ExternalDocs != nil {
  2093  					if existingTag.ExternalDocs.Description == "" {
  2094  						existingTags[i].ExternalDocs.Description = tag.ExternalDocs.Description
  2095  					}
  2096  					if existingTag.ExternalDocs.URL == "" {
  2097  						existingTags[i].ExternalDocs.URL = tag.ExternalDocs.URL
  2098  					}
  2099  				}
  2100  				if existingTag.extensions == nil {
  2101  					existingTags[i].extensions = tag.extensions
  2102  				} else if tag.extensions != nil {
  2103  					for _, ext := range tag.extensions {
  2104  						matchedExt := false
  2105  						for _, existingExt := range existingTag.extensions {
  2106  							if existingExt.key == ext.key {
  2107  								matchedExt = true
  2108  								break
  2109  							}
  2110  						}
  2111  						if !matchedExt {
  2112  							existingTags[i].extensions = append(existingTags[i].extensions, ext)
  2113  						}
  2114  					}
  2115  				}
  2116  				matched = true
  2117  				break
  2118  			}
  2119  		}
  2120  		if !matched {
  2121  			existingTags = append(existingTags, tag)
  2122  		}
  2123  	}
  2124  	return existingTags
  2125  }
  2126  
  2127  func processExtensions(inputExts map[string]*structpb.Value) ([]extension, error) {
  2128  	exts := make([]extension, 0, len(inputExts))
  2129  	for k, v := range inputExts {
  2130  		if !strings.HasPrefix(k, "x-") {
  2131  			return nil, fmt.Errorf("extension keys need to start with \"x-\": %q", k)
  2132  		}
  2133  		ext, err := (&protojson.MarshalOptions{Indent: "  "}).Marshal(v)
  2134  		if err != nil {
  2135  			return nil, err
  2136  		}
  2137  		exts = append(exts, extension{key: k, value: ext})
  2138  	}
  2139  	sort.Slice(exts, func(i, j int) bool { return exts[i].key < exts[j].key })
  2140  	return exts, nil
  2141  }
  2142  
  2143  func validateHeaderTypeAndFormat(headerType, format string) error {
  2144  	// The type of the object. The value MUST be one of "string", "number", "integer", "boolean", or "array"
  2145  	// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#headerObject
  2146  	// Note: currently not implementing array as we are only implementing this in the operation response context
  2147  	switch headerType {
  2148  	// the format property is an open string-valued property, and can have any value to support documentation needs
  2149  	// primary check for format is to ensure that the number/integer formats are extensions of the specified type
  2150  	// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#dataTypeFormat
  2151  	case "string":
  2152  		return nil
  2153  	case "number":
  2154  		switch format {
  2155  		case "uint",
  2156  			"uint8",
  2157  			"uint16",
  2158  			"uint32",
  2159  			"uint64",
  2160  			"int",
  2161  			"int8",
  2162  			"int16",
  2163  			"int32",
  2164  			"int64",
  2165  			"float",
  2166  			"float32",
  2167  			"float64",
  2168  			"complex64",
  2169  			"complex128",
  2170  			"double",
  2171  			"byte",
  2172  			"rune",
  2173  			"uintptr",
  2174  			"":
  2175  			return nil
  2176  		default:
  2177  			return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
  2178  		}
  2179  	case "integer":
  2180  		switch format {
  2181  		case "uint",
  2182  			"uint8",
  2183  			"uint16",
  2184  			"uint32",
  2185  			"uint64",
  2186  			"int",
  2187  			"int8",
  2188  			"int16",
  2189  			"int32",
  2190  			"int64",
  2191  			"":
  2192  			return nil
  2193  		default:
  2194  			return fmt.Errorf("the provided format %q is not a valid extension of the type %q", format, headerType)
  2195  		}
  2196  	case "boolean":
  2197  		return nil
  2198  	}
  2199  	return fmt.Errorf("the provided header type %q is not supported", headerType)
  2200  }
  2201  
  2202  func validateDefaultValueTypeAndFormat(headerType string, defaultValue string, format string) error {
  2203  	switch headerType {
  2204  	case "string":
  2205  		if !isQuotedString(defaultValue) {
  2206  			return fmt.Errorf("the provided default value %q does not match provider type %q, or is not properly quoted with escaped quotations", defaultValue, headerType)
  2207  		}
  2208  		switch format {
  2209  		case "date-time":
  2210  			unquoteTime := strings.Trim(defaultValue, `"`)
  2211  			if _, err := time.Parse(time.RFC3339, unquoteTime); err != nil {
  2212  				return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
  2213  			}
  2214  		case "date":
  2215  			const layoutRFC3339Date = "2006-01-02"
  2216  			unquoteDate := strings.Trim(defaultValue, `"`)
  2217  			if _, err := time.Parse(layoutRFC3339Date, unquoteDate); err != nil {
  2218  				return fmt.Errorf("the provided default value %q is not a valid RFC3339 date-time string", defaultValue)
  2219  			}
  2220  		}
  2221  	case "number":
  2222  		if err := isJSONNumber(defaultValue, headerType); err != nil {
  2223  			return err
  2224  		}
  2225  	case "integer":
  2226  		switch format {
  2227  		case "int32":
  2228  			if _, err := strconv.ParseInt(defaultValue, 0, 32); err != nil {
  2229  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2230  			}
  2231  		case "uint32":
  2232  			if _, err := strconv.ParseUint(defaultValue, 0, 32); err != nil {
  2233  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2234  			}
  2235  		case "int64":
  2236  			if _, err := strconv.ParseInt(defaultValue, 0, 64); err != nil {
  2237  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2238  			}
  2239  		case "uint64":
  2240  			if _, err := strconv.ParseUint(defaultValue, 0, 64); err != nil {
  2241  				return fmt.Errorf("the provided default value %q does not match provided format %q", defaultValue, format)
  2242  			}
  2243  		default:
  2244  			if _, err := strconv.ParseInt(defaultValue, 0, 64); err != nil {
  2245  				return fmt.Errorf("the provided default value %q does not match provided type %q", defaultValue, headerType)
  2246  			}
  2247  		}
  2248  	case "boolean":
  2249  		if !isBool(defaultValue) {
  2250  			return fmt.Errorf("the provided default value %q does not match provider type %q", defaultValue, headerType)
  2251  		}
  2252  	}
  2253  	return nil
  2254  }
  2255  
  2256  func isQuotedString(s string) bool {
  2257  	return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
  2258  }
  2259  
  2260  func isJSONNumber(s string, t string) error {
  2261  	val, err := strconv.ParseFloat(s, 64)
  2262  	if err != nil {
  2263  		return fmt.Errorf("the provided default value %q does not match provider type %q", s, t)
  2264  	}
  2265  	// Floating point values that cannot be represented as sequences of digits (such as Infinity and NaN) are not permitted.
  2266  	// See: https://tools.ietf.org/html/rfc4627#section-2.4
  2267  	if math.IsInf(val, 0) || math.IsNaN(val) {
  2268  		return fmt.Errorf("the provided number %q is not a valid JSON number", s)
  2269  	}
  2270  
  2271  	return nil
  2272  }
  2273  
  2274  func isBool(s string) bool {
  2275  	// Unable to use strconv.ParseBool because it returns truthy values https://golang.org/pkg/strconv/#example_ParseBool
  2276  	// per https://swagger.io/specification/v2/#data-types
  2277  	// 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.
  2278  	return s == "true" || s == "false"
  2279  }
  2280  
  2281  func processHeaders(inputHdrs map[string]*openapi_options.Header) (openapiHeadersObject, error) {
  2282  	hdrs := make(map[string]openapiHeaderObject, len(inputHdrs))
  2283  	for k, v := range inputHdrs {
  2284  		header := textproto.CanonicalMIMEHeaderKey(k)
  2285  		ret := openapiHeaderObject{
  2286  			Description: v.Description,
  2287  			Format:      v.Format,
  2288  			Pattern:     v.Pattern,
  2289  		}
  2290  		if err := validateHeaderTypeAndFormat(v.Type, v.Format); err != nil {
  2291  			return nil, err
  2292  		}
  2293  		ret.Type = v.Type
  2294  		if v.Default != "" {
  2295  			if err := validateDefaultValueTypeAndFormat(v.Type, v.Default, v.Format); err != nil {
  2296  				return nil, err
  2297  			}
  2298  			ret.Default = RawExample(v.Default)
  2299  		}
  2300  		hdrs[header] = ret
  2301  	}
  2302  	return hdrs, nil
  2303  }
  2304  
  2305  func removeInternalComments(comment string) string {
  2306  	c := []string{}
  2307  	for len(comment) > 0 {
  2308  		open := strings.SplitN(comment, "(--", 2)
  2309  		if len(open) == 1 {
  2310  			c = append(c, open[0])
  2311  			break
  2312  		}
  2313  		ex := strings.TrimRight(open[0], " \t")
  2314  		// Trim only one line prior to all spaces
  2315  		switch {
  2316  		case strings.HasSuffix(ex, "\r\n"):
  2317  			ex = strings.TrimSuffix(ex, "\r\n")
  2318  		case strings.HasSuffix(ex, "\n"):
  2319  			ex = strings.TrimSuffix(ex, "\n")
  2320  		}
  2321  		if ex != "" {
  2322  			c = append(c, ex)
  2323  		}
  2324  		comment = open[1]
  2325  
  2326  		close := strings.SplitN(comment, "--)", 2)
  2327  		if len(close) > 1 {
  2328  			comment = close[1]
  2329  		} else {
  2330  			break
  2331  		}
  2332  	}
  2333  	return strings.Join(c, "")
  2334  }
  2335  
  2336  // updateOpenAPIDataFromComments updates a OpenAPI object based on a comment
  2337  // from the proto file.
  2338  //
  2339  // First paragraph of a comment is used for summary. Remaining paragraphs of
  2340  // a comment are used for description. If 'Summary' field is not present on
  2341  // the passed swaggerObject, the summary and description are joined by \n\n.
  2342  //
  2343  // If there is a field named 'Info', its 'Summary' and 'Description' fields
  2344  // will be updated instead.
  2345  //
  2346  // If there is no 'Summary', the same behavior will be attempted on 'Title',
  2347  // but only if the last character is not a period.
  2348  func updateOpenAPIDataFromComments(reg *descriptor.Registry, swaggerObject interface{}, data interface{}, comment string, isPackageObject bool) error {
  2349  	if len(comment) == 0 {
  2350  		return nil
  2351  	}
  2352  
  2353  	// Checks whether the "ignore_comments" flag is set to true
  2354  	if reg.GetIgnoreComments() {
  2355  		return nil
  2356  	}
  2357  
  2358  	// Checks whether the "remove_internal_comments" flag is set to true
  2359  	if reg.GetRemoveInternalComments() {
  2360  		comment = removeInternalComments(comment)
  2361  	}
  2362  
  2363  	// Checks whether the "use_go_templates" flag is set to true
  2364  	if reg.GetUseGoTemplate() {
  2365  		comment = goTemplateComments(comment, data, reg)
  2366  	}
  2367  
  2368  	// Figure out what to apply changes to.
  2369  	swaggerObjectValue := reflect.ValueOf(swaggerObject)
  2370  	infoObjectValue := swaggerObjectValue.Elem().FieldByName("Info")
  2371  	if !infoObjectValue.CanSet() {
  2372  		// No such field? Apply summary and description directly to
  2373  		// passed object.
  2374  		infoObjectValue = swaggerObjectValue.Elem()
  2375  	}
  2376  
  2377  	// Figure out which properties to update.
  2378  	summaryValue := infoObjectValue.FieldByName("Summary")
  2379  	descriptionValue := infoObjectValue.FieldByName("Description")
  2380  	readOnlyValue := infoObjectValue.FieldByName("ReadOnly")
  2381  
  2382  	if readOnlyValue.Kind() == reflect.Bool && readOnlyValue.CanSet() && strings.Contains(comment, "Output only.") {
  2383  		readOnlyValue.Set(reflect.ValueOf(true))
  2384  	}
  2385  
  2386  	usingTitle := false
  2387  	if !summaryValue.CanSet() {
  2388  		summaryValue = infoObjectValue.FieldByName("Title")
  2389  		usingTitle = true
  2390  	}
  2391  
  2392  	paragraphs := strings.Split(comment, paragraphDeliminator)
  2393  
  2394  	// If there is a summary (or summary-equivalent) and it's empty, use the first
  2395  	// paragraph as summary, and the rest as description.
  2396  	if summaryValue.CanSet() {
  2397  		summary := strings.TrimSpace(paragraphs[0])
  2398  		description := strings.TrimSpace(strings.Join(paragraphs[1:], paragraphDeliminator))
  2399  		if !usingTitle || (len(summary) > 0 && summary[len(summary)-1] != '.') {
  2400  			// overrides the schema value only if it's empty
  2401  			// keep the comment precedence when updating the package definition
  2402  			if summaryValue.Len() == 0 || isPackageObject {
  2403  				summaryValue.Set(reflect.ValueOf(summary))
  2404  			}
  2405  			if len(description) > 0 {
  2406  				if !descriptionValue.CanSet() {
  2407  					return errors.New("encountered object type with a summary, but no description")
  2408  				}
  2409  				// overrides the schema value only if it's empty
  2410  				// keep the comment precedence when updating the package definition
  2411  				if descriptionValue.Len() == 0 || isPackageObject {
  2412  					descriptionValue.Set(reflect.ValueOf(description))
  2413  				}
  2414  			}
  2415  			return nil
  2416  		}
  2417  	}
  2418  
  2419  	// There was no summary field on the swaggerObject. Try to apply the
  2420  	// whole comment into description if the OpenAPI object description is empty.
  2421  	if descriptionValue.CanSet() {
  2422  		if descriptionValue.Len() == 0 || isPackageObject {
  2423  			descriptionValue.Set(reflect.ValueOf(strings.Join(paragraphs, paragraphDeliminator)))
  2424  		}
  2425  		return nil
  2426  	}
  2427  
  2428  	return errors.New("no description nor summary property")
  2429  }
  2430  
  2431  func fieldProtoComments(reg *descriptor.Registry, msg *descriptor.Message, field *descriptor.Field) string {
  2432  	protoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.DescriptorProto)(nil)), "Field")
  2433  	for i, f := range msg.Fields {
  2434  		if f == field {
  2435  			return protoComments(reg, msg.File, msg.Outers, "MessageType", int32(msg.Index), protoPath, int32(i))
  2436  		}
  2437  	}
  2438  	return ""
  2439  }
  2440  
  2441  func enumValueProtoComments(reg *descriptor.Registry, enum *descriptor.Enum) string {
  2442  	protoPath := protoPathIndex(reflect.TypeOf((*descriptorpb.EnumDescriptorProto)(nil)), "Value")
  2443  	var comments []string
  2444  	for idx, value := range enum.GetValue() {
  2445  		if reg.GetOmitEnumDefaultValue() && value.GetNumber() == 0 {
  2446  			continue
  2447  		}
  2448  		if !isVisible(getEnumValueVisibilityOption(value), reg) {
  2449  			continue
  2450  		}
  2451  		name := value.GetName()
  2452  		if reg.GetEnumsAsInts() {
  2453  			name = strconv.Itoa(int(value.GetNumber()))
  2454  		}
  2455  		if str := protoComments(reg, enum.File, enum.Outers, "EnumType", int32(enum.Index), protoPath, int32(idx)); str != "" {
  2456  			comments = append(comments, name+": "+str)
  2457  		}
  2458  	}
  2459  	if len(comments) > 0 {
  2460  		return "- " + strings.Join(comments, "\n - ")
  2461  	}
  2462  	return ""
  2463  }
  2464  
  2465  func protoComments(reg *descriptor.Registry, file *descriptor.File, outers []string, typeName string, typeIndex int32, fieldPaths ...int32) string {
  2466  	if file.SourceCodeInfo == nil {
  2467  		fmt.Fprintln(os.Stderr, file.GetName(), "descriptor.File should not contain nil SourceCodeInfo")
  2468  		return ""
  2469  	}
  2470  
  2471  	outerPaths := make([]int32, len(outers))
  2472  	for i := range outers {
  2473  		location := ""
  2474  		if file.Package != nil {
  2475  			location = file.GetPackage()
  2476  		}
  2477  
  2478  		msg, err := reg.LookupMsg(location, strings.Join(outers[:i+1], "."))
  2479  		if err != nil {
  2480  			panic(err)
  2481  		}
  2482  		outerPaths[i] = int32(msg.Index)
  2483  	}
  2484  
  2485  	for _, loc := range file.SourceCodeInfo.Location {
  2486  		if !isProtoPathMatches(loc.Path, outerPaths, typeName, typeIndex, fieldPaths) {
  2487  			continue
  2488  		}
  2489  		comments := ""
  2490  		if loc.LeadingComments != nil {
  2491  			comments = strings.TrimRight(*loc.LeadingComments, "\n")
  2492  			comments = strings.TrimSpace(comments)
  2493  			// TODO(ivucica): this is a hack to fix "// " being interpreted as "//".
  2494  			// perhaps we should:
  2495  			// - split by \n
  2496  			// - determine if every (but first and last) line begins with " "
  2497  			// - trim every line only if that is the case
  2498  			// - join by \n
  2499  			comments = strings.ReplaceAll(comments, "\n ", "\n")
  2500  			comments = removeInternalComments(comments)
  2501  		}
  2502  		if loc.TrailingComments != nil {
  2503  			trailing := strings.TrimSpace(*loc.TrailingComments)
  2504  			if comments == "" {
  2505  				comments = trailing
  2506  			} else {
  2507  				comments += "\n\n" + trailing
  2508  			}
  2509  		}
  2510  		return comments
  2511  	}
  2512  	return ""
  2513  }
  2514  
  2515  func goTemplateComments(comment string, data interface{}, reg *descriptor.Registry) string {
  2516  	var temp bytes.Buffer
  2517  	tpl, err := template.New("").Funcs(template.FuncMap{
  2518  		// Allows importing documentation from a file
  2519  		"import": func(name string) string {
  2520  			file, err := os.ReadFile(name)
  2521  			if err != nil {
  2522  				return err.Error()
  2523  			}
  2524  			// Runs template over imported file
  2525  			return goTemplateComments(string(file), data, reg)
  2526  		},
  2527  		// Grabs title and description from a field
  2528  		"fieldcomments": func(msg *descriptor.Message, field *descriptor.Field) string {
  2529  			return strings.ReplaceAll(fieldProtoComments(reg, msg, field), "\n", "<br>")
  2530  		},
  2531  		"arg": func(name string) string {
  2532  			if v, f := reg.GetGoTemplateArgs()[name]; f {
  2533  				return v
  2534  			}
  2535  			return fmt.Sprintf("goTemplateArg %s not found", name)
  2536  		},
  2537  	}).Parse(comment)
  2538  	if err != nil {
  2539  		// If there is an error parsing the templating insert the error as string in the comment
  2540  		// to make it easier to debug the template error
  2541  		return err.Error()
  2542  	}
  2543  	if err := tpl.Execute(&temp, data); err != nil {
  2544  		// If there is an error executing the templating insert the error as string in the comment
  2545  		// to make it easier to debug the error
  2546  		return err.Error()
  2547  	}
  2548  	return temp.String()
  2549  }
  2550  
  2551  var messageProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "MessageType")
  2552  var nestedProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.DescriptorProto)(nil)), "NestedType")
  2553  var packageProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Package")
  2554  var serviceProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil)), "Service")
  2555  var methodProtoPath = protoPathIndex(reflect.TypeOf((*descriptorpb.ServiceDescriptorProto)(nil)), "Method")
  2556  
  2557  func isProtoPathMatches(paths []int32, outerPaths []int32, typeName string, typeIndex int32, fieldPaths []int32) bool {
  2558  	if typeName == "Package" && typeIndex == packageProtoPath {
  2559  		// path for package comments is just [2], and all the other processing
  2560  		// is too complex for it.
  2561  		if len(paths) == 0 || typeIndex != paths[0] {
  2562  			return false
  2563  		}
  2564  		return true
  2565  	}
  2566  
  2567  	if len(paths) != len(outerPaths)*2+2+len(fieldPaths) {
  2568  		return false
  2569  	}
  2570  
  2571  	if typeName == "Method" {
  2572  		if paths[0] != serviceProtoPath || paths[2] != methodProtoPath {
  2573  			return false
  2574  		}
  2575  		paths = paths[2:]
  2576  	} else {
  2577  		typeNameDescriptor := reflect.TypeOf((*descriptorpb.FileDescriptorProto)(nil))
  2578  
  2579  		if len(outerPaths) > 0 {
  2580  			if paths[0] != messageProtoPath || paths[1] != outerPaths[0] {
  2581  				return false
  2582  			}
  2583  			paths = paths[2:]
  2584  			outerPaths = outerPaths[1:]
  2585  
  2586  			for i, v := range outerPaths {
  2587  				if paths[i*2] != nestedProtoPath || paths[i*2+1] != v {
  2588  					return false
  2589  				}
  2590  			}
  2591  			paths = paths[len(outerPaths)*2:]
  2592  
  2593  			if typeName == "MessageType" {
  2594  				typeName = "NestedType"
  2595  			}
  2596  			typeNameDescriptor = reflect.TypeOf((*descriptorpb.DescriptorProto)(nil))
  2597  		}
  2598  
  2599  		if paths[0] != protoPathIndex(typeNameDescriptor, typeName) || paths[1] != typeIndex {
  2600  			return false
  2601  		}
  2602  		paths = paths[2:]
  2603  	}
  2604  
  2605  	for i, v := range fieldPaths {
  2606  		if paths[i] != v {
  2607  			return false
  2608  		}
  2609  	}
  2610  	return true
  2611  }
  2612  
  2613  // protoPathIndex returns a path component for google.protobuf.descriptor.SourceCode_Location.
  2614  //
  2615  // Specifically, it returns an id as generated from descriptor proto which
  2616  // can be used to determine what type the id following it in the path is.
  2617  // For example, if we are trying to locate comments related to a field named
  2618  // `Address` in a message named `Person`, the path will be:
  2619  //
  2620  //	[4, a, 2, b]
  2621  //
  2622  // While `a` gets determined by the order in which the messages appear in
  2623  // the proto file, and `b` is the field index specified in the proto
  2624  // file itself, the path actually needs to specify that `a` refers to a
  2625  // message and not, say, a service; and  that `b` refers to a field and not
  2626  // an option.
  2627  //
  2628  // protoPathIndex figures out the values 4 and 2 in the above example. Because
  2629  // messages are top level objects, the value of 4 comes from field id for
  2630  // `MessageType` inside `google.protobuf.descriptor.FileDescriptor` message.
  2631  // This field has a message type `google.protobuf.descriptor.DescriptorProto`.
  2632  // And inside message `DescriptorProto`, there is a field named `Field` with id
  2633  // 2.
  2634  //
  2635  // Some code generators seem to be hardcoding these values; this method instead
  2636  // interprets them from `descriptor.proto`-derived Go source as necessary.
  2637  func protoPathIndex(descriptorType reflect.Type, what string) int32 {
  2638  	field, ok := descriptorType.Elem().FieldByName(what)
  2639  	if !ok {
  2640  		panic(fmt.Errorf("could not find protobuf descriptor type id for %s", what))
  2641  	}
  2642  	pbtag := field.Tag.Get("protobuf")
  2643  	if pbtag == "" {
  2644  		panic(fmt.Errorf("no Go tag 'protobuf' on protobuf descriptor for %s", what))
  2645  	}
  2646  	path, err := strconv.ParseInt(strings.Split(pbtag, ",")[1], 10, 32)
  2647  	if err != nil {
  2648  		panic(fmt.Errorf("protobuf descriptor id for %s cannot be converted to a number: %s", what, err.Error()))
  2649  	}
  2650  	return int32(path)
  2651  }
  2652  
  2653  // extractOperationOptionFromMethodDescriptor extracts the message of type
  2654  // openapi_options.Operation from a given proto method's descriptor.
  2655  func extractOperationOptionFromMethodDescriptor(meth *descriptorpb.MethodDescriptorProto) (*openapi_options.Operation, error) {
  2656  	if meth.Options == nil {
  2657  		return nil, nil
  2658  	}
  2659  	if !proto.HasExtension(meth.Options, openapi_options.E_Openapiv2Operation) {
  2660  		return nil, nil
  2661  	}
  2662  	ext := proto.GetExtension(meth.Options, openapi_options.E_Openapiv2Operation)
  2663  	opts, ok := ext.(*openapi_options.Operation)
  2664  	if !ok {
  2665  		return nil, fmt.Errorf("extension is %T; want an Operation", ext)
  2666  	}
  2667  	return opts, nil
  2668  }
  2669  
  2670  // extractSchemaOptionFromMessageDescriptor extracts the message of type
  2671  // openapi_options.Schema from a given proto message's descriptor.
  2672  func extractSchemaOptionFromMessageDescriptor(msg *descriptorpb.DescriptorProto) (*openapi_options.Schema, error) {
  2673  	if msg.Options == nil {
  2674  		return nil, nil
  2675  	}
  2676  	if !proto.HasExtension(msg.Options, openapi_options.E_Openapiv2Schema) {
  2677  		return nil, nil
  2678  	}
  2679  	ext := proto.GetExtension(msg.Options, openapi_options.E_Openapiv2Schema)
  2680  	opts, ok := ext.(*openapi_options.Schema)
  2681  	if !ok {
  2682  		return nil, fmt.Errorf("extension is %T; want a Schema", ext)
  2683  	}
  2684  	return opts, nil
  2685  }
  2686  
  2687  // extractTagOptionFromServiceDescriptor extracts the tag of type
  2688  // openapi_options.Tag from a given proto service's descriptor.
  2689  func extractTagOptionFromServiceDescriptor(svc *descriptorpb.ServiceDescriptorProto) (*openapi_options.Tag, error) {
  2690  	if svc.Options == nil {
  2691  		return nil, nil
  2692  	}
  2693  	if !proto.HasExtension(svc.Options, openapi_options.E_Openapiv2Tag) {
  2694  		return nil, nil
  2695  	}
  2696  	ext := proto.GetExtension(svc.Options, openapi_options.E_Openapiv2Tag)
  2697  	opts, ok := ext.(*openapi_options.Tag)
  2698  	if !ok {
  2699  		return nil, fmt.Errorf("extension is %T; want a Tag", ext)
  2700  	}
  2701  	return opts, nil
  2702  }
  2703  
  2704  // extractOpenAPIOptionFromFileDescriptor extracts the message of type
  2705  // openapi_options.OpenAPI from a given proto method's descriptor.
  2706  func extractOpenAPIOptionFromFileDescriptor(file *descriptorpb.FileDescriptorProto) (*openapi_options.Swagger, error) {
  2707  	if file.Options == nil {
  2708  		return nil, nil
  2709  	}
  2710  	if !proto.HasExtension(file.Options, openapi_options.E_Openapiv2Swagger) {
  2711  		return nil, nil
  2712  	}
  2713  	ext := proto.GetExtension(file.Options, openapi_options.E_Openapiv2Swagger)
  2714  	opts, ok := ext.(*openapi_options.Swagger)
  2715  	if !ok {
  2716  		return nil, fmt.Errorf("extension is %T; want a OpenAPI object", ext)
  2717  	}
  2718  	return opts, nil
  2719  }
  2720  
  2721  func extractJSONSchemaFromFieldDescriptor(fd *descriptorpb.FieldDescriptorProto) (*openapi_options.JSONSchema, error) {
  2722  	if fd.Options == nil {
  2723  		return nil, nil
  2724  	}
  2725  	if !proto.HasExtension(fd.Options, openapi_options.E_Openapiv2Field) {
  2726  		return nil, nil
  2727  	}
  2728  	ext := proto.GetExtension(fd.Options, openapi_options.E_Openapiv2Field)
  2729  	opts, ok := ext.(*openapi_options.JSONSchema)
  2730  	if !ok {
  2731  		return nil, fmt.Errorf("extension is %T; want a JSONSchema object", ext)
  2732  	}
  2733  	return opts, nil
  2734  }
  2735  
  2736  func extractFieldBehaviorFromFieldDescriptor(fd *descriptorpb.FieldDescriptorProto) ([]annotations.FieldBehavior, error) {
  2737  	if fd.Options == nil {
  2738  		return nil, nil
  2739  	}
  2740  	if !proto.HasExtension(fd.Options, annotations.E_FieldBehavior) {
  2741  		return nil, nil
  2742  	}
  2743  	ext := proto.GetExtension(fd.Options, annotations.E_FieldBehavior)
  2744  	opts, ok := ext.([]annotations.FieldBehavior)
  2745  	if !ok {
  2746  		return nil, fmt.Errorf("extension is %T; want a []FieldBehavior object", ext)
  2747  	}
  2748  	return opts, nil
  2749  }
  2750  
  2751  func getFieldVisibilityOption(fd *descriptor.Field) *visibility.VisibilityRule {
  2752  	if fd.Options == nil {
  2753  		return nil
  2754  	}
  2755  	if !proto.HasExtension(fd.Options, visibility.E_FieldVisibility) {
  2756  		return nil
  2757  	}
  2758  	ext := proto.GetExtension(fd.Options, visibility.E_FieldVisibility)
  2759  	opts, ok := ext.(*visibility.VisibilityRule)
  2760  	if !ok {
  2761  		return nil
  2762  	}
  2763  	return opts
  2764  }
  2765  
  2766  func getServiceVisibilityOption(fd *descriptor.Service) *visibility.VisibilityRule {
  2767  	if fd.Options == nil {
  2768  		return nil
  2769  	}
  2770  	if !proto.HasExtension(fd.Options, visibility.E_ApiVisibility) {
  2771  		return nil
  2772  	}
  2773  	ext := proto.GetExtension(fd.Options, visibility.E_ApiVisibility)
  2774  	opts, ok := ext.(*visibility.VisibilityRule)
  2775  	if !ok {
  2776  		return nil
  2777  	}
  2778  	return opts
  2779  }
  2780  
  2781  func getMethodVisibilityOption(fd *descriptor.Method) *visibility.VisibilityRule {
  2782  	if fd.Options == nil {
  2783  		return nil
  2784  	}
  2785  	if !proto.HasExtension(fd.Options, visibility.E_MethodVisibility) {
  2786  		return nil
  2787  	}
  2788  	ext := proto.GetExtension(fd.Options, visibility.E_MethodVisibility)
  2789  	opts, ok := ext.(*visibility.VisibilityRule)
  2790  	if !ok {
  2791  		return nil
  2792  	}
  2793  	return opts
  2794  }
  2795  
  2796  func getEnumValueVisibilityOption(fd *descriptorpb.EnumValueDescriptorProto) *visibility.VisibilityRule {
  2797  	if fd.Options == nil {
  2798  		return nil
  2799  	}
  2800  	if !proto.HasExtension(fd.Options, visibility.E_ValueVisibility) {
  2801  		return nil
  2802  	}
  2803  	ext := proto.GetExtension(fd.Options, visibility.E_ValueVisibility)
  2804  	opts, ok := ext.(*visibility.VisibilityRule)
  2805  	if !ok {
  2806  		return nil
  2807  	}
  2808  	return opts
  2809  }
  2810  
  2811  func getMethodOpenAPIOption(reg *descriptor.Registry, meth *descriptor.Method) (*openapi_options.Operation, error) {
  2812  	opts, err := extractOperationOptionFromMethodDescriptor(meth.MethodDescriptorProto)
  2813  	if err != nil {
  2814  		return nil, err
  2815  	}
  2816  	if opts != nil {
  2817  		return opts, nil
  2818  	}
  2819  	opts, ok := reg.GetOpenAPIMethodOption(meth.FQMN())
  2820  	if !ok {
  2821  		return nil, nil
  2822  	}
  2823  	return opts, nil
  2824  }
  2825  
  2826  func getMessageOpenAPIOption(reg *descriptor.Registry, msg *descriptor.Message) (*openapi_options.Schema, error) {
  2827  	opts, err := extractSchemaOptionFromMessageDescriptor(msg.DescriptorProto)
  2828  	if err != nil {
  2829  		return nil, err
  2830  	}
  2831  	if opts != nil {
  2832  		return opts, nil
  2833  	}
  2834  	opts, ok := reg.GetOpenAPIMessageOption(msg.FQMN())
  2835  	if !ok {
  2836  		return nil, nil
  2837  	}
  2838  	return opts, nil
  2839  }
  2840  
  2841  func getServiceOpenAPIOption(reg *descriptor.Registry, svc *descriptor.Service) (*openapi_options.Tag, error) {
  2842  	if opts, ok := reg.GetOpenAPIServiceOption(svc.FQSN()); ok {
  2843  		return opts, nil
  2844  	}
  2845  	opts, err := extractTagOptionFromServiceDescriptor(svc.ServiceDescriptorProto)
  2846  	if err != nil {
  2847  		return nil, err
  2848  	}
  2849  	return opts, nil
  2850  }
  2851  
  2852  func getFileOpenAPIOption(reg *descriptor.Registry, file *descriptor.File) (*openapi_options.Swagger, error) {
  2853  	opts, err := extractOpenAPIOptionFromFileDescriptor(file.FileDescriptorProto)
  2854  	if err != nil {
  2855  		return nil, err
  2856  	}
  2857  	if opts != nil {
  2858  		return opts, nil
  2859  	}
  2860  	opts, ok := reg.GetOpenAPIFileOption(*file.Name)
  2861  	if !ok {
  2862  		return nil, nil
  2863  	}
  2864  	return opts, nil
  2865  }
  2866  
  2867  func getFieldOpenAPIOption(reg *descriptor.Registry, fd *descriptor.Field) (*openapi_options.JSONSchema, error) {
  2868  	opts, err := extractJSONSchemaFromFieldDescriptor(fd.FieldDescriptorProto)
  2869  	if err != nil {
  2870  		return nil, err
  2871  	}
  2872  	if opts != nil {
  2873  		return opts, nil
  2874  	}
  2875  	opts, ok := reg.GetOpenAPIFieldOption(fd.FQFN())
  2876  	if !ok {
  2877  		return nil, nil
  2878  	}
  2879  	return opts, nil
  2880  }
  2881  
  2882  func getFieldBehaviorOption(reg *descriptor.Registry, fd *descriptor.Field) ([]annotations.FieldBehavior, error) {
  2883  	opts, err := extractFieldBehaviorFromFieldDescriptor(fd.FieldDescriptorProto)
  2884  	if err != nil {
  2885  		return nil, err
  2886  	}
  2887  	if opts != nil {
  2888  		return opts, nil
  2889  	}
  2890  	return opts, nil
  2891  }
  2892  
  2893  func protoJSONSchemaToOpenAPISchemaCore(j *openapi_options.JSONSchema, reg *descriptor.Registry, refs refMap) schemaCore {
  2894  	ret := schemaCore{}
  2895  
  2896  	if j.GetRef() != "" {
  2897  		openapiName, ok := fullyQualifiedNameToOpenAPIName(j.GetRef(), reg)
  2898  		if ok {
  2899  			ret.Ref = "#/definitions/" + openapiName
  2900  			if refs != nil {
  2901  				refs[j.GetRef()] = struct{}{}
  2902  			}
  2903  		} else {
  2904  			ret.Ref += j.GetRef()
  2905  		}
  2906  	} else {
  2907  		f, t := protoJSONSchemaTypeToFormat(j.GetType())
  2908  		ret.Format = f
  2909  		ret.Type = t
  2910  	}
  2911  
  2912  	return ret
  2913  }
  2914  
  2915  func updateswaggerObjectFromJSONSchema(s *openapiSchemaObject, j *openapi_options.JSONSchema, reg *descriptor.Registry, data interface{}) {
  2916  	s.Title = j.GetTitle()
  2917  	s.Description = j.GetDescription()
  2918  	if reg.GetUseGoTemplate() {
  2919  		s.Title = goTemplateComments(s.Title, data, reg)
  2920  		s.Description = goTemplateComments(s.Description, data, reg)
  2921  	}
  2922  	if s.Type == "array" {
  2923  		s.Items.MaxLength = j.GetMaxLength()
  2924  		s.Items.MinLength = j.GetMinLength()
  2925  		s.Items.Pattern = j.GetPattern()
  2926  		s.Items.Default = j.GetDefault()
  2927  		s.Items.UniqueItems = j.GetUniqueItems()
  2928  		s.Items.MaxProperties = j.GetMaxProperties()
  2929  		s.Items.MinProperties = j.GetMinProperties()
  2930  		s.Items.Required = j.GetRequired()
  2931  		s.Items.Minimum = j.GetMinimum()
  2932  		s.Items.Maximum = j.GetMaximum()
  2933  		s.Items.ReadOnly = j.GetReadOnly()
  2934  		s.Items.MultipleOf = j.GetMultipleOf()
  2935  		s.Items.ExclusiveMaximum = j.GetExclusiveMaximum()
  2936  		s.Items.ExclusiveMinimum = j.GetExclusiveMinimum()
  2937  		s.Items.Enum = j.GetEnum()
  2938  
  2939  		if j.GetDefault() == "" {
  2940  			s.Items.Default = nil
  2941  		}
  2942  		if len(j.GetEnum()) == 0 {
  2943  			s.Items.Enum = nil
  2944  		}
  2945  		if j.GetFormat() != "" {
  2946  			s.Items.Format = j.GetFormat()
  2947  		}
  2948  	} else {
  2949  		s.MaxLength = j.GetMaxLength()
  2950  		s.MinLength = j.GetMinLength()
  2951  		s.Pattern = j.GetPattern()
  2952  		s.Default = j.GetDefault()
  2953  		s.UniqueItems = j.GetUniqueItems()
  2954  		s.MaxProperties = j.GetMaxProperties()
  2955  		s.MinProperties = j.GetMinProperties()
  2956  		s.Required = j.GetRequired()
  2957  		s.Minimum = j.GetMinimum()
  2958  		s.Maximum = j.GetMaximum()
  2959  		s.ReadOnly = j.GetReadOnly()
  2960  		s.MultipleOf = j.GetMultipleOf()
  2961  		s.ExclusiveMaximum = j.GetExclusiveMaximum()
  2962  		s.ExclusiveMinimum = j.GetExclusiveMinimum()
  2963  		s.Enum = j.GetEnum()
  2964  
  2965  		if j.GetDefault() == "" {
  2966  			s.Default = nil
  2967  		}
  2968  		if len(j.GetEnum()) == 0 {
  2969  			s.Enum = nil
  2970  		}
  2971  		if j.GetFormat() != "" {
  2972  			s.Format = j.GetFormat()
  2973  		}
  2974  	}
  2975  	s.MaxItems = j.GetMaxItems()
  2976  	s.MinItems = j.GetMinItems()
  2977  
  2978  	if j.GetExtensions() != nil {
  2979  		exts, err := processExtensions(j.GetExtensions())
  2980  		if err != nil {
  2981  			panic(err)
  2982  		}
  2983  		s.extensions = exts
  2984  	}
  2985  	if overrideType := j.GetType(); len(overrideType) > 0 {
  2986  		s.Type = strings.ToLower(overrideType[0].String())
  2987  	}
  2988  	if j.GetExample() != "" {
  2989  		s.Example = RawExample(j.GetExample())
  2990  	}
  2991  }
  2992  
  2993  func updateSwaggerObjectFromFieldBehavior(s *openapiSchemaObject, j []annotations.FieldBehavior, reg *descriptor.Registry, field *descriptor.Field) {
  2994  	for _, fb := range j {
  2995  		switch fb {
  2996  		case annotations.FieldBehavior_REQUIRED:
  2997  			if reg.GetUseJSONNamesForFields() {
  2998  				s.Required = append(s.Required, *field.JsonName)
  2999  			} else {
  3000  				s.Required = append(s.Required, *field.Name)
  3001  			}
  3002  		case annotations.FieldBehavior_OUTPUT_ONLY:
  3003  			s.ReadOnly = true
  3004  		case annotations.FieldBehavior_FIELD_BEHAVIOR_UNSPECIFIED:
  3005  		case annotations.FieldBehavior_OPTIONAL:
  3006  		case annotations.FieldBehavior_INPUT_ONLY:
  3007  			// OpenAPI v3 supports a writeOnly property, but this is not supported in Open API v2
  3008  		case annotations.FieldBehavior_IMMUTABLE:
  3009  		}
  3010  	}
  3011  }
  3012  
  3013  func openapiSchemaFromProtoSchema(s *openapi_options.Schema, reg *descriptor.Registry, refs refMap, data interface{}) openapiSchemaObject {
  3014  	ret := openapiSchemaObject{
  3015  		ExternalDocs: protoExternalDocumentationToOpenAPIExternalDocumentation(s.GetExternalDocs(), reg, data),
  3016  	}
  3017  
  3018  	ret.schemaCore = protoJSONSchemaToOpenAPISchemaCore(s.GetJsonSchema(), reg, refs)
  3019  	updateswaggerObjectFromJSONSchema(&ret, s.GetJsonSchema(), reg, data)
  3020  
  3021  	if s != nil && s.Example != "" {
  3022  		ret.Example = RawExample(s.Example)
  3023  	}
  3024  
  3025  	return ret
  3026  }
  3027  
  3028  func openapiExamplesFromProtoExamples(in map[string]string) map[string]interface{} {
  3029  	if len(in) == 0 {
  3030  		return nil
  3031  	}
  3032  	out := make(map[string]interface{}, len(in))
  3033  	for mimeType, exampleStr := range in {
  3034  		switch mimeType {
  3035  		case "application/json":
  3036  			// JSON example objects are rendered raw.
  3037  			out[mimeType] = RawExample(exampleStr)
  3038  		default:
  3039  			// All other mimetype examples are rendered as strings.
  3040  			out[mimeType] = exampleStr
  3041  		}
  3042  	}
  3043  	return out
  3044  }
  3045  
  3046  func protoJSONSchemaTypeToFormat(in []openapi_options.JSONSchema_JSONSchemaSimpleTypes) (string, string) {
  3047  	if len(in) == 0 {
  3048  		return "", ""
  3049  	}
  3050  
  3051  	// Can't support more than 1 type, just return the first element.
  3052  	// This is due to an inconsistency in the design of the openapiv2 proto
  3053  	// and that used in schemaCore. schemaCore uses the v3 definition of types,
  3054  	// which only allows a single string, while the openapiv2 proto uses the OpenAPI v2
  3055  	// definition, which defers to the JSON schema definition, which allows a string or an array.
  3056  	// Sources:
  3057  	// https://swagger.io/specification/#itemsObject
  3058  	// https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.2
  3059  	switch in[0] {
  3060  	case openapi_options.JSONSchema_UNKNOWN, openapi_options.JSONSchema_NULL:
  3061  		return "", ""
  3062  	case openapi_options.JSONSchema_OBJECT:
  3063  		return "object", ""
  3064  	case openapi_options.JSONSchema_ARRAY:
  3065  		return "array", ""
  3066  	case openapi_options.JSONSchema_BOOLEAN:
  3067  		// NOTE: in OpenAPI specification, format should be empty on boolean type
  3068  		return "boolean", ""
  3069  	case openapi_options.JSONSchema_INTEGER:
  3070  		return "integer", "int32"
  3071  	case openapi_options.JSONSchema_NUMBER:
  3072  		return "number", "double"
  3073  	case openapi_options.JSONSchema_STRING:
  3074  		// NOTE: in OpenAPI specification, format should be empty on string type
  3075  		return "string", ""
  3076  	default:
  3077  		// Maybe panic?
  3078  		return "", ""
  3079  	}
  3080  }
  3081  
  3082  func protoExternalDocumentationToOpenAPIExternalDocumentation(in *openapi_options.ExternalDocumentation, reg *descriptor.Registry, data interface{}) *openapiExternalDocumentationObject {
  3083  	if in == nil {
  3084  		return nil
  3085  	}
  3086  
  3087  	if reg.GetUseGoTemplate() {
  3088  		in.Description = goTemplateComments(in.Description, data, reg)
  3089  	}
  3090  
  3091  	return &openapiExternalDocumentationObject{
  3092  		Description: in.Description,
  3093  		URL:         in.Url,
  3094  	}
  3095  }
  3096  
  3097  func addCustomRefs(d openapiDefinitionsObject, reg *descriptor.Registry, refs refMap) error {
  3098  	if len(refs) == 0 {
  3099  		return nil
  3100  	}
  3101  	msgMap := make(messageMap)
  3102  	enumMap := make(enumMap)
  3103  	for ref := range refs {
  3104  		swgName, swgOk := fullyQualifiedNameToOpenAPIName(ref, reg)
  3105  		if !swgOk {
  3106  			grpclog.Errorf("can't resolve OpenAPI name from CustomRef %q", ref)
  3107  			continue
  3108  		}
  3109  		if _, ok := d[swgName]; ok {
  3110  			// Skip already existing definitions
  3111  			delete(refs, ref)
  3112  			continue
  3113  		}
  3114  		msg, err := reg.LookupMsg("", ref)
  3115  		if err == nil {
  3116  			msgMap[swgName] = msg
  3117  			continue
  3118  		}
  3119  		enum, err := reg.LookupEnum("", ref)
  3120  		if err == nil {
  3121  			enumMap[swgName] = enum
  3122  			continue
  3123  		}
  3124  
  3125  		// ?? Should be either enum or msg
  3126  	}
  3127  	if err := renderMessagesAsDefinition(msgMap, d, reg, refs, nil); err != nil {
  3128  		return err
  3129  	}
  3130  	renderEnumerationsAsDefinition(enumMap, d, reg)
  3131  
  3132  	// Run again in case any new refs were added
  3133  	return addCustomRefs(d, reg, refs)
  3134  }
  3135  
  3136  func lowerCamelCase(fieldName string, fields []*descriptor.Field, msgs []*descriptor.Message) string {
  3137  	for _, oneField := range fields {
  3138  		if oneField.GetName() == fieldName {
  3139  			return oneField.GetJsonName()
  3140  		}
  3141  	}
  3142  	messageNameToFieldsToJSONName := make(map[string]map[string]string, len(msgs))
  3143  	fieldNameToType := make(map[string]string)
  3144  	for _, msg := range msgs {
  3145  		fieldNameToJSONName := make(map[string]string)
  3146  		for _, oneField := range msg.GetField() {
  3147  			fieldNameToJSONName[oneField.GetName()] = oneField.GetJsonName()
  3148  			fieldNameToType[oneField.GetName()] = oneField.GetTypeName()
  3149  		}
  3150  		messageNameToFieldsToJSONName[msg.GetName()] = fieldNameToJSONName
  3151  	}
  3152  	if strings.Contains(fieldName, ".") {
  3153  		fieldNames := strings.Split(fieldName, ".")
  3154  		fieldNamesWithCamelCase := make([]string, 0)
  3155  		for i := 0; i < len(fieldNames)-1; i++ {
  3156  			fieldNamesWithCamelCase = append(fieldNamesWithCamelCase, casing.JSONCamelCase(fieldNames[i]))
  3157  		}
  3158  		prefix := strings.Join(fieldNamesWithCamelCase, ".")
  3159  		reservedJSONName := getReservedJSONName(fieldName, messageNameToFieldsToJSONName, fieldNameToType)
  3160  		if reservedJSONName != "" {
  3161  			return prefix + "." + reservedJSONName
  3162  		}
  3163  	}
  3164  	return casing.JSONCamelCase(fieldName)
  3165  }
  3166  
  3167  func getReservedJSONName(fieldName string, messageNameToFieldsToJSONName map[string]map[string]string, fieldNameToType map[string]string) string {
  3168  	if len(strings.Split(fieldName, ".")) == 2 {
  3169  		fieldNames := strings.Split(fieldName, ".")
  3170  		firstVariable := fieldNames[0]
  3171  		firstType := fieldNameToType[firstVariable]
  3172  		firstTypeShortNames := strings.Split(firstType, ".")
  3173  		firstTypeShortName := firstTypeShortNames[len(firstTypeShortNames)-1]
  3174  		return messageNameToFieldsToJSONName[firstTypeShortName][fieldNames[1]]
  3175  	}
  3176  	fieldNames := strings.Split(fieldName, ".")
  3177  	return getReservedJSONName(strings.Join(fieldNames[1:], "."), messageNameToFieldsToJSONName, fieldNameToType)
  3178  }
  3179  
  3180  func find(a []string, x string) int {
  3181  	// This is a linear search but we are dealing with a small number of fields
  3182  	for i, n := range a {
  3183  		if x == n {
  3184  			return i
  3185  		}
  3186  	}
  3187  	return -1
  3188  }
  3189  
  3190  // Make a deep copy of the outer parameters that has paramName as the first component,
  3191  // but remove the first component of the field path.
  3192  func subPathParams(paramName string, outerParams []descriptor.Parameter) []descriptor.Parameter {
  3193  	var innerParams []descriptor.Parameter
  3194  	for _, p := range outerParams {
  3195  		if len(p.FieldPath) > 1 && p.FieldPath[0].Name == paramName {
  3196  			subParam := descriptor.Parameter{
  3197  				FieldPath: p.FieldPath[1:],
  3198  				Target:    p.Target,
  3199  				Method:    p.Method,
  3200  			}
  3201  			innerParams = append(innerParams, subParam)
  3202  		}
  3203  	}
  3204  	return innerParams
  3205  }
  3206  
  3207  func getFieldConfiguration(reg *descriptor.Registry, fd *descriptor.Field) *openapi_options.JSONSchema_FieldConfiguration {
  3208  	if j, err := getFieldOpenAPIOption(reg, fd); err == nil {
  3209  		return j.GetFieldConfiguration()
  3210  	}
  3211  	return nil
  3212  }