| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067 | // Copyright 2020 Google LLC. All Rights Reserved.//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at////    http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//package generatorimport (	"fmt"	"log"	"net/url"	"regexp"	"sort"	"strings"	http2 "net/http"	"google.golang.org/protobuf/types/descriptorpb"	"google.golang.org/genproto/googleapis/api/annotations"	status_pb "google.golang.org/genproto/googleapis/rpc/status"	"google.golang.org/protobuf/compiler/protogen"	"google.golang.org/protobuf/proto"	"google.golang.org/protobuf/reflect/protoreflect"	any_pb "google.golang.org/protobuf/types/known/anypb"	"git.ikuban.com/server/kratos-utils/v2/transport/http/handler"	wk "git.ikuban.com/server/swagger-api/v2/generator/wellknown"	v3 "github.com/google/gnostic/openapiv3")type Configuration struct {	Version         *string	Title           *string	Description     *string	Naming          *string	FQSchemaNaming  *bool	EnumType        *string	CircularDepth   *int	DefaultResponse *bool	OutputMode      *string	PreserveInfo    *bool}const (	infoURL = "git.ikuban.com/server/swagger-api")// In order to dynamically add google.rpc.Status responses we need// to know the message descriptors for google.rpc.Status as well// as google.protobuf.Any.var statusProtoDesc = (&status_pb.Status{}).ProtoReflect().Descriptor()var anyProtoDesc = (&any_pb.Any{}).ProtoReflect().Descriptor()// OpenAPIv3Generator holds internal state needed to generate an OpenAPIv3 document for a transcoded Protocol Buffer service.type OpenAPIv3Generator struct {	conf   Configuration	plugin *protogen.Plugin	inputFiles        []*protogen.File	reflect           *OpenAPIv3Reflector	generatedSchemas  []string // Names of schemas that have already been generated.	linterRulePattern *regexp.Regexp	pathPattern       *regexp.Regexp	namedPathPattern  *regexp.Regexp}// NewOpenAPIv3Generator creates a new generator for a protoc plugin invocation.func NewOpenAPIv3Generator(plugin *protogen.Plugin, conf Configuration, inputFiles []*protogen.File) *OpenAPIv3Generator {	return &OpenAPIv3Generator{		conf:   conf,		plugin: plugin,		inputFiles:        inputFiles,		reflect:           NewOpenAPIv3Reflector(conf),		generatedSchemas:  make([]string, 0),		linterRulePattern: regexp.MustCompile(`\(-- .* --\)`),		pathPattern:       regexp.MustCompile("{([^=}]+)}"),		namedPathPattern:  regexp.MustCompile("{(.+)=(.+)}"),	}}// Run runs the generator.func (g *OpenAPIv3Generator) Run(outputFile *protogen.GeneratedFile) error {	d := g.buildDocumentV3()	bytes, err := d.YAMLValue("Generated with protoc-gen-openapi\n" + infoURL)	if err != nil {		return fmt.Errorf("failed to marshal yaml: %s", err.Error())	}	if _, err = outputFile.Write(bytes); err != nil {		return fmt.Errorf("failed to write yaml: %s", err.Error())	}	return nil}func (g *OpenAPIv3Generator) RunV2() ([]byte, error) {	d := g.buildDocumentV3()	bytes, err := d.YAMLValue("Generated with protoc-gen-openapi\n" + infoURL)	if err != nil {		return bytes, fmt.Errorf("failed to marshal yaml: %s", err.Error())	}	return bytes, nil}// buildDocumentV3 builds an OpenAPIv3 document for a plugin request.func (g *OpenAPIv3Generator) buildDocumentV3() *v3.Document {	d := &v3.Document{}	d.Openapi = "3.0.3"	d.Info = &v3.Info{		Version:     *g.conf.Version,		Title:       *g.conf.Title,		Description: *g.conf.Description,	}	d.Paths = &v3.Paths{}	d.Components = &v3.Components{		Schemas: &v3.SchemasOrReferences{			AdditionalProperties: []*v3.NamedSchemaOrReference{},		},	}	// Go through the files and add the services to the documents, keeping	// track of which schemas are referenced in the response so we can	// add them later.	for _, file := range g.inputFiles {		if file.Generate {			// Merge any `Document` annotations with the current			extDocument := proto.GetExtension(file.Desc.Options(), v3.E_Document)			if extDocument != nil {				proto.Merge(d, extDocument.(*v3.Document))			}			g.addPathsToDocumentV3(d, file.Services)		}	}	// While we have required schemas left to generate, go through the files again	// looking for the related message and adding them to the document if required.	for len(g.reflect.requiredSchemas) > 0 {		count := len(g.reflect.requiredSchemas)		for _, file := range g.plugin.Files {			g.addSchemasForMessagesToDocumentV3(d, file.Messages, file.Proto.GetEdition())		}		g.reflect.requiredSchemas = g.reflect.requiredSchemas[count:len(g.reflect.requiredSchemas)]	}	// If there is only 1 service, then use it's title for the	// document, if the document is missing it.	if len(d.Tags) == 1 {		if d.Info.Title == "" && d.Tags[0].Name != "" {			d.Info.Title = d.Tags[0].Name + " API"		}		if d.Info.Description == "" {			d.Info.Description = d.Tags[0].Description		}		d.Tags[0].Description = ""	}	if g.conf.PreserveInfo != nil && *g.conf.PreserveInfo {		d.Info = &v3.Info{			Version:     *g.conf.Version,			Title:       *g.conf.Title,			Description: *g.conf.Description,		}	}	allServers := []string{}	// If paths methods has servers, but they're all the same, then move servers to path level	for _, path := range d.Paths.Path {		servers := []string{}		// Only 1 server will ever be set, per method, by the generator		if path.Value.Get != nil && len(path.Value.Get.Servers) == 1 {			servers = appendUnique(servers, path.Value.Get.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Get.Servers[0].Url)		}		if path.Value.Post != nil && len(path.Value.Post.Servers) == 1 {			servers = appendUnique(servers, path.Value.Post.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Post.Servers[0].Url)		}		if path.Value.Put != nil && len(path.Value.Put.Servers) == 1 {			servers = appendUnique(servers, path.Value.Put.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Put.Servers[0].Url)		}		if path.Value.Delete != nil && len(path.Value.Delete.Servers) == 1 {			servers = appendUnique(servers, path.Value.Delete.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Delete.Servers[0].Url)		}		if path.Value.Patch != nil && len(path.Value.Patch.Servers) == 1 {			servers = appendUnique(servers, path.Value.Patch.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Patch.Servers[0].Url)		}		if path.Value.Head != nil && len(path.Value.Head.Servers) == 1 {			servers = appendUnique(servers, path.Value.Head.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Head.Servers[0].Url)		}		if path.Value.Options != nil && len(path.Value.Options.Servers) == 1 {			servers = appendUnique(servers, path.Value.Options.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Options.Servers[0].Url)		}		if path.Value.Trace != nil && len(path.Value.Trace.Servers) == 1 {			servers = appendUnique(servers, path.Value.Trace.Servers[0].Url)			allServers = appendUnique(allServers, path.Value.Trace.Servers[0].Url)		}		if len(servers) == 1 {			path.Value.Servers = []*v3.Server{{Url: servers[0]}}			if path.Value.Get != nil {				path.Value.Get.Servers = nil			}			if path.Value.Post != nil {				path.Value.Post.Servers = nil			}			if path.Value.Put != nil {				path.Value.Put.Servers = nil			}			if path.Value.Delete != nil {				path.Value.Delete.Servers = nil			}			if path.Value.Patch != nil {				path.Value.Patch.Servers = nil			}			if path.Value.Head != nil {				path.Value.Head.Servers = nil			}			if path.Value.Options != nil {				path.Value.Options.Servers = nil			}			if path.Value.Trace != nil {				path.Value.Trace.Servers = nil			}		}	}	// Set all servers on API level	if len(allServers) > 0 {		d.Servers = []*v3.Server{}		for _, server := range allServers {			d.Servers = append(d.Servers, &v3.Server{Url: server})		}	}	// If there is only 1 server, we can safely remove all path level servers	if len(allServers) == 1 {		for _, path := range d.Paths.Path {			path.Value.Servers = nil		}	}	// Sort the tags.	{		pairs := d.Tags		sort.Slice(pairs, func(i, j int) bool {			return pairs[i].Name < pairs[j].Name		})		d.Tags = pairs	}	// Sort the paths.	{		pairs := d.Paths.Path		sort.Slice(pairs, func(i, j int) bool {			return pairs[i].Name < pairs[j].Name		})		d.Paths.Path = pairs	}	// Sort the schemas.	{		pairs := d.Components.Schemas.AdditionalProperties		sort.Slice(pairs, func(i, j int) bool {			return pairs[i].Name < pairs[j].Name		})		d.Components.Schemas.AdditionalProperties = pairs	}	// Deduplicate and sort security schemes.	if d.Components.SecuritySchemes != nil && d.Components.SecuritySchemes.AdditionalProperties != nil {		seen := make(map[string]bool)		unique := make([]*v3.NamedSecuritySchemeOrReference, 0)		for _, scheme := range d.Components.SecuritySchemes.AdditionalProperties {			if !seen[scheme.Name] {				seen[scheme.Name] = true				unique = append(unique, scheme)			}		}		sort.Slice(unique, func(i, j int) bool {			return unique[i].Name < unique[j].Name		})		d.Components.SecuritySchemes.AdditionalProperties = unique	}	// Deduplicate and sort responses.	if d.Components.Responses != nil && d.Components.Responses.AdditionalProperties != nil {		seen := make(map[string]bool)		unique := make([]*v3.NamedResponseOrReference, 0)		for _, resp := range d.Components.Responses.AdditionalProperties {			if !seen[resp.Name] {				seen[resp.Name] = true				unique = append(unique, resp)			}		}		sort.Slice(unique, func(i, j int) bool {			return unique[i].Name < unique[j].Name		})		d.Components.Responses.AdditionalProperties = unique	}	// Deduplicate and sort parameters.	if d.Components.Parameters != nil && d.Components.Parameters.AdditionalProperties != nil {		seen := make(map[string]bool)		unique := make([]*v3.NamedParameterOrReference, 0)		for _, param := range d.Components.Parameters.AdditionalProperties {			if !seen[param.Name] {				seen[param.Name] = true				unique = append(unique, param)			}		}		sort.Slice(unique, func(i, j int) bool {			return unique[i].Name < unique[j].Name		})		d.Components.Parameters.AdditionalProperties = unique	}	// Deduplicate and sort request bodies.	if d.Components.RequestBodies != nil && d.Components.RequestBodies.AdditionalProperties != nil {		seen := make(map[string]bool)		unique := make([]*v3.NamedRequestBodyOrReference, 0)		for _, body := range d.Components.RequestBodies.AdditionalProperties {			if !seen[body.Name] {				seen[body.Name] = true				unique = append(unique, body)			}		}		sort.Slice(unique, func(i, j int) bool {			return unique[i].Name < unique[j].Name		})		d.Components.RequestBodies.AdditionalProperties = unique	}	// Deduplicate and sort headers.	if d.Components.Headers != nil && d.Components.Headers.AdditionalProperties != nil {		seen := make(map[string]bool)		unique := make([]*v3.NamedHeaderOrReference, 0)		for _, header := range d.Components.Headers.AdditionalProperties {			if !seen[header.Name] {				seen[header.Name] = true				unique = append(unique, header)			}		}		sort.Slice(unique, func(i, j int) bool {			return unique[i].Name < unique[j].Name		})		d.Components.Headers.AdditionalProperties = unique	}	// Deduplicate servers by URL.	if d.Servers != nil && len(d.Servers) > 0 {		seen := make(map[string]bool)		unique := make([]*v3.Server, 0)		for _, server := range d.Servers {			if !seen[server.Url] {				seen[server.Url] = true				unique = append(unique, server)			}		}		d.Servers = unique	}	return d}// filterCommentString removes linter rules from comments.func (g *OpenAPIv3Generator) filterCommentString(c protogen.Comments) string {	comment := g.linterRulePattern.ReplaceAllString(string(c), "")	return strings.TrimSpace(comment)}func (g *OpenAPIv3Generator) findField(name string, inMessage *protogen.Message) *protogen.Field {	for _, field := range inMessage.Fields {		if string(field.Desc.Name()) == name || string(field.Desc.JSONName()) == name {			return field		}	}	return nil}func (g *OpenAPIv3Generator) findAndFormatFieldName(name string, inMessage *protogen.Message) string {	field := g.findField(name, inMessage)	if field != nil {		return g.reflect.formatFieldName(field.Desc)	}	return name}// Note that fields which are mapped to URL query parameters must have a primitive type// or a repeated primitive type or a non-repeated message type.// In the case of a repeated type, the parameter can be repeated in the URL as ...?param=A¶m=B.// In the case of a message type, each field of the message is mapped to a separate parameter,// such as ...?foo.a=A&foo.b=B&foo.c=C.// There are exceptions:// - for wrapper types it will use the same representation as the wrapped primitive type in JSON// - for google.protobuf.timestamp type it will be serialized as a string//// maps, Struct and Empty can NOT be used// messages can have any number of sub messages - including circular (e.g. sub.subsub.sub.subsub.id)// buildQueryParamsV3 extracts any valid query params, including sub and recursive messagesfunc (g *OpenAPIv3Generator) buildQueryParamsV3(field *protogen.Field) []*v3.ParameterOrReference {	depths := map[string]int{}	return g._buildQueryParamsV3(field, depths)}// depths are used to keep track of how many times a message's fields has been seenfunc (g *OpenAPIv3Generator) _buildQueryParamsV3(field *protogen.Field, depths map[string]int) []*v3.ParameterOrReference {	parameters := []*v3.ParameterOrReference{}	queryFieldName := g.reflect.formatFieldName(field.Desc)	fieldDescription := g.filterCommentString(field.Comments.Leading)	if field.Desc.IsMap() {		// Map types are not allowed in query parameteres		return parameters	} else if field.Desc.Kind() == protoreflect.MessageKind {		typeName := g.reflect.fullMessageTypeName(field.Desc.Message())		switch typeName {		case ".google.protobuf.Value":			fieldSchema := g.reflect.schemaOrReferenceForField(field.Desc)			parameters = append(parameters,				&v3.ParameterOrReference{					Oneof: &v3.ParameterOrReference_Parameter{						Parameter: &v3.Parameter{							Name:        queryFieldName,							In:          "query",							Description: fieldDescription,							Required:    false,							Schema:      fieldSchema,						},					},				})			return parameters		case ".google.protobuf.BoolValue", ".google.protobuf.BytesValue", ".google.protobuf.Int32Value", ".google.protobuf.UInt32Value",			".google.protobuf.StringValue", ".google.protobuf.Int64Value", ".google.protobuf.UInt64Value", ".google.protobuf.FloatValue",			".google.protobuf.DoubleValue":			valueField := getValueField(field.Message.Desc)			fieldSchema := g.reflect.schemaOrReferenceForField(valueField)			parameters = append(parameters,				&v3.ParameterOrReference{					Oneof: &v3.ParameterOrReference_Parameter{						Parameter: &v3.Parameter{							Name:        queryFieldName,							In:          "query",							Description: fieldDescription,							Required:    false,							Schema:      fieldSchema,						},					},				})			return parameters		case ".google.protobuf.Timestamp":			fieldSchema := g.reflect.schemaOrReferenceForMessage(field.Message.Desc)			parameters = append(parameters,				&v3.ParameterOrReference{					Oneof: &v3.ParameterOrReference_Parameter{						Parameter: &v3.Parameter{							Name:        queryFieldName,							In:          "query",							Description: fieldDescription,							Required:    false,							Schema:      fieldSchema,						},					},				})			return parameters		case ".google.protobuf.Duration":			fieldSchema := g.reflect.schemaOrReferenceForMessage(field.Message.Desc)			parameters = append(parameters,				&v3.ParameterOrReference{					Oneof: &v3.ParameterOrReference_Parameter{						Parameter: &v3.Parameter{							Name:        queryFieldName,							In:          "query",							Description: fieldDescription,							Required:    false,							Schema:      fieldSchema,						},					},				})			return parameters		}		if field.Desc.IsList() {			// Only non-repeated message types are valid			return parameters		}		// Represent field masks directly as strings (don't expand them).		if typeName == ".google.protobuf.FieldMask" {			fieldSchema := g.reflect.schemaOrReferenceForField(field.Desc)			parameters = append(parameters,				&v3.ParameterOrReference{					Oneof: &v3.ParameterOrReference_Parameter{						Parameter: &v3.Parameter{							Name:        queryFieldName,							In:          "query",							Description: fieldDescription,							Required:    false,							Schema:      fieldSchema,						},					},				})			return parameters		}		// Sub messages are allowed, even circular, as long as the final type is a primitive.		// Go through each of the sub message fields		for _, subField := range field.Message.Fields {			subFieldFullName := string(subField.Desc.FullName())			seen, ok := depths[subFieldFullName]			if !ok {				depths[subFieldFullName] = 0			}			if seen < *g.conf.CircularDepth {				depths[subFieldFullName]++				subParams := g._buildQueryParamsV3(subField, depths)				for _, subParam := range subParams {					if param, ok := subParam.Oneof.(*v3.ParameterOrReference_Parameter); ok {						param.Parameter.Name = queryFieldName + "." + param.Parameter.Name						parameters = append(parameters, subParam)					}				}			}		}	} else if field.Desc.Kind() != protoreflect.GroupKind {		// schemaOrReferenceForField also handles array types		fieldSchema := g.reflect.schemaOrReferenceForField(field.Desc)		parameters = append(parameters,			&v3.ParameterOrReference{				Oneof: &v3.ParameterOrReference_Parameter{					Parameter: &v3.Parameter{						Name:        queryFieldName,						In:          "query",						Description: fieldDescription,						Required:    false,						Schema:      fieldSchema,					},				},			})	}	return parameters}// buildOperationV3 constructs an operation for a set of values.func (g *OpenAPIv3Generator) buildOperationV3(	d *v3.Document,	operationID string,	tagName string,	description string,	defaultHost string,	path string,	bodyField string,	inputMessage *protogen.Message,	outputMessage *protogen.Message,) (*v3.Operation, string) {	// coveredParameters tracks the parameters that have been used in the body or path.	coveredParameters := make([]string, 0)	if bodyField != "" {		coveredParameters = append(coveredParameters, bodyField)	}	// Initialize the list of operation parameters.	parameters := []*v3.ParameterOrReference{}	// Find simple path parameters like {id}	if allMatches := g.pathPattern.FindAllStringSubmatch(path, -1); allMatches != nil {		for _, matches := range allMatches {			// Add the value to the list of covered parameters.			coveredParameters = append(coveredParameters, matches[1])			pathParameter := g.findAndFormatFieldName(matches[1], inputMessage)			path = strings.Replace(path, matches[1], pathParameter, 1)			// Add the path parameters to the operation parameters.			var fieldSchema *v3.SchemaOrReference			var fieldDescription string			field := g.findField(pathParameter, inputMessage)			if field != nil {				fieldSchema = g.reflect.schemaOrReferenceForField(field.Desc)				fieldDescription = g.filterCommentString(field.Comments.Leading)			} else {				// If field does not exist, it is safe to set it to string, as it is ignored downstream				fieldSchema = &v3.SchemaOrReference{					Oneof: &v3.SchemaOrReference_Schema{						Schema: &v3.Schema{							Type: "string",						},					},				}			}			parameters = append(parameters,				&v3.ParameterOrReference{					Oneof: &v3.ParameterOrReference_Parameter{						Parameter: &v3.Parameter{							Name:        pathParameter,							In:          "path",							Description: fieldDescription,							Required:    true,							Schema:      fieldSchema,						},					},				})		}	}	// Find named path parameters like {name=shelves/*}	if matches := g.namedPathPattern.FindStringSubmatch(path); matches != nil {		// Build a list of named path parameters.		namedPathParameters := make([]string, 0)		// Add the "name=" "name" value to the list of covered parameters.		coveredParameters = append(coveredParameters, matches[1])		// Convert the path from the starred form to use named path parameters.		starredPath := matches[2]		parts := strings.Split(starredPath, "/")		// The starred path is assumed to be in the form "things/*/otherthings/*".		// We want to convert it to "things/{thingsId}/otherthings/{otherthingsId}".		for i := 0; i < len(parts)-1; i += 2 {			section := parts[i]			namedPathParameter := g.findAndFormatFieldName(section, inputMessage)			namedPathParameter = singular(namedPathParameter)			parts[i+1] = "{" + namedPathParameter + "}"			namedPathParameters = append(namedPathParameters, namedPathParameter)		}		// Rewrite the path to use the path parameters.		newPath := strings.Join(parts, "/")		path = strings.Replace(path, matches[0], newPath, 1)		// Add the named path parameters to the operation parameters.		for _, namedPathParameter := range namedPathParameters {			parameters = append(parameters,				&v3.ParameterOrReference{					Oneof: &v3.ParameterOrReference_Parameter{						Parameter: &v3.Parameter{							Name:        namedPathParameter,							In:          "path",							Required:    true,							Description: "The " + namedPathParameter + " id.",							Schema: &v3.SchemaOrReference{								Oneof: &v3.SchemaOrReference_Schema{									Schema: &v3.Schema{										Type: "string",									},								},							},						},					},				})		}	}	// Add any unhandled fields in the request message as query parameters.	if bodyField != "*" && string(inputMessage.Desc.FullName()) != "google.api.HttpBody" {		for _, field := range inputMessage.Fields {			fieldName := string(field.Desc.Name())			if !contains(coveredParameters, fieldName) && fieldName != bodyField {				fieldParams := g.buildQueryParamsV3(field)				parameters = append(parameters, fieldParams...)			}		}	}	// Create the response.	name, content := g.reflect.responseContentForMessage(outputMessage.Desc)	responses := &v3.Responses{		ResponseOrReference: []*v3.NamedResponseOrReference{			{				Name: name,				Value: &v3.ResponseOrReference{					Oneof: &v3.ResponseOrReference_Response{						Response: &v3.Response{							Description: "OK",							Content:     content,						},					},				},			},		},	}	// Add the default reponse if needed	if *g.conf.DefaultResponse {		anySchemaName := g.reflect.formatMessageName(anyProtoDesc)		anySchema := wk.NewGoogleProtobufAnySchema(anySchemaName)		g.addSchemaToDocumentV3(d, anySchema)		statusSchemaName := g.reflect.formatMessageName(statusProtoDesc)		statusSchema := wk.NewGoogleRpcStatusSchema(statusSchemaName, anySchemaName)		g.addSchemaToDocumentV3(d, statusSchema)		defaultResponse := &v3.NamedResponseOrReference{			Name: "default",			Value: &v3.ResponseOrReference{				Oneof: &v3.ResponseOrReference_Response{					Response: &v3.Response{						Description: "Default error response",						Content: wk.NewApplicationJsonMediaType(&v3.SchemaOrReference{							Oneof: &v3.SchemaOrReference_Reference{								Reference: &v3.Reference{XRef: "#/components/schemas/" + statusSchemaName}}}),					},				},			},		}		responses.ResponseOrReference = append(responses.ResponseOrReference, defaultResponse)	}	// Create the operation.	op := &v3.Operation{		Tags:        []string{tagName},		Description: description,		OperationId: operationID,		Parameters:  parameters,		Responses:   responses,	}	if defaultHost != "" {		hostURL, err := url.Parse(defaultHost)		if err == nil {			hostURL.Scheme = "https"			op.Servers = append(op.Servers, &v3.Server{Url: hostURL.String()})		}	}	// If a body field is specified, we need to pass a message as the request body.	if bodyField != "" {		var requestSchema *v3.SchemaOrReference		if bodyField == "*" {			// Pass the entire request message as the request body.			requestSchema = g.reflect.schemaOrReferenceForMessage(inputMessage.Desc)		} else {			// If body refers to a message field, use that type.			for _, field := range inputMessage.Fields {				if string(field.Desc.Name()) == bodyField {					switch field.Desc.Kind() {					case protoreflect.StringKind:						requestSchema = &v3.SchemaOrReference{							Oneof: &v3.SchemaOrReference_Schema{								Schema: &v3.Schema{									Type: "string",								},							},						}					case protoreflect.MessageKind:						requestSchema = g.reflect.schemaOrReferenceForMessage(field.Message.Desc)					default:						log.Printf("unsupported field type %+v", field.Desc)					}					break				}			}		}		op.RequestBody = &v3.RequestBodyOrReference{			Oneof: &v3.RequestBodyOrReference_RequestBody{				RequestBody: &v3.RequestBody{					Required: true,					Content: &v3.MediaTypes{						AdditionalProperties: []*v3.NamedMediaType{							{								Name: "application/json",								Value: &v3.MediaType{									Schema: requestSchema,								},							},						},					},				},			},		}	}	return op, path}// addOperationToDocumentV3 adds an operation to the specified path/method.func (g *OpenAPIv3Generator) addOperationToDocumentV3(d *v3.Document, op *v3.Operation, path string, methodName string) {	var selectedPathItem *v3.NamedPathItem	for _, namedPathItem := range d.Paths.Path {		if namedPathItem.Name == path {			selectedPathItem = namedPathItem			break		}	}	// If we get here, we need to create a path item.	if selectedPathItem == nil {		selectedPathItem = &v3.NamedPathItem{Name: path, Value: &v3.PathItem{}}		d.Paths.Path = append(d.Paths.Path, selectedPathItem)	}	// Set the operation on the specified method.	switch methodName {	case "GET":		selectedPathItem.Value.Get = op	case "POST":		selectedPathItem.Value.Post = op	case "PUT":		selectedPathItem.Value.Put = op	case "DELETE":		selectedPathItem.Value.Delete = op	case "PATCH":		selectedPathItem.Value.Patch = op	case http2.MethodHead:		selectedPathItem.Value.Head = op	case http2.MethodOptions:		selectedPathItem.Value.Options = op	case http2.MethodTrace:		selectedPathItem.Value.Trace = op	}}// addPathsToDocumentV3 adds paths from a specified file descriptor.func (g *OpenAPIv3Generator) addPathsToDocumentV3(d *v3.Document, services []*protogen.Service) {	for _, service := range services {		annotationsCount := 0		for _, method := range service.Methods {			comment := g.filterCommentString(method.Comments.Leading)			inputMessage := method.Input			outputMessage := method.Output			operationID := service.GoName + "_" + method.GoName			extOperation := proto.GetExtension(method.Desc.Options(), v3.E_Operation)			if extOperation == nil || extOperation == v3.E_Operation.InterfaceOf(v3.E_Operation.Zero()) {				continue			}			var path string			var httpMethod string			var bodyField string			httpOperation := proto.GetExtension(method.Desc.Options(), annotations.E_Http)			if httpOperation != nil && httpOperation != annotations.E_Http.InterfaceOf(annotations.E_Http.Zero()) {				_httpOperation := httpOperation.(*annotations.HttpRule)				switch httpRule := _httpOperation.GetPattern().(type) {				case *annotations.HttpRule_Post:					path = httpRule.Post					httpMethod = http2.MethodPost					bodyField = _httpOperation.GetBody()				case *annotations.HttpRule_Get:					path = httpRule.Get					httpMethod = http2.MethodGet					bodyField = ""				case *annotations.HttpRule_Delete:					path = httpRule.Delete					httpMethod = http2.MethodDelete					bodyField = ""				case *annotations.HttpRule_Put:					path = httpRule.Put					httpMethod = http2.MethodPut					bodyField = _httpOperation.GetBody()				case *annotations.HttpRule_Patch:					path = httpRule.Patch					httpMethod = http2.MethodPatch					bodyField = _httpOperation.GetBody()				case *annotations.HttpRule_Custom:					path = httpRule.Custom.Path					httpMethod = httpRule.Custom.Kind					bodyField = _httpOperation.GetBody()				}			}			annotationsCount++			if path == "" {				path = handler.PathGenerator(string(service.Desc.FullName()), method.GoName)			}			if httpMethod == "" {				httpMethod = http2.MethodPost			}			if bodyField == "" && (httpMethod == http2.MethodPost || httpMethod == http2.MethodPut || httpMethod == http2.MethodPatch) {				bodyField = "*"			}			defaultHost := proto.GetExtension(service.Desc.Options(), annotations.E_DefaultHost).(string)			op, path2 := g.buildOperationV3(				d, operationID, d.Info.Title, comment, defaultHost, path, bodyField, inputMessage, outputMessage)			// Merge any `Operation` annotations with the current			proto.Merge(op, extOperation.(*v3.Operation))			g.addOperationToDocumentV3(d, op, path2, httpMethod)		}		if annotationsCount > 0 {			d.Tags = append(d.Tags, &v3.Tag{Name: d.Info.Title, Description: d.Info.Description})		}	}}// addSchemaForMessageToDocumentV3 adds the schema to the document if requiredfunc (g *OpenAPIv3Generator) addSchemaToDocumentV3(d *v3.Document, schema *v3.NamedSchemaOrReference) {	if contains(g.generatedSchemas, schema.Name) {		return	}	g.generatedSchemas = append(g.generatedSchemas, schema.Name)	d.Components.Schemas.AdditionalProperties = append(d.Components.Schemas.AdditionalProperties, schema)}// addSchemasForMessagesToDocumentV3 adds info from one file descriptor.func (g *OpenAPIv3Generator) addSchemasForMessagesToDocumentV3(d *v3.Document, messages []*protogen.Message, edition descriptorpb.Edition) {	// For each message, generate a definition.	for _, message := range messages {		if message.Messages != nil {			g.addSchemasForMessagesToDocumentV3(d, message.Messages, edition)		}		schemaName := g.reflect.formatMessageName(message.Desc)		// Only generate this if we need it and haven't already generated it.		if !contains(g.reflect.requiredSchemas, schemaName) ||			contains(g.generatedSchemas, schemaName) {			continue		}		typeName := g.reflect.fullMessageTypeName(message.Desc)		messageDescription := g.filterCommentString(message.Comments.Leading)		// `google.protobuf.Value` and `google.protobuf.Any` have special JSON transcoding		// so we can't just reflect on the message descriptor.		if typeName == ".google.protobuf.Value" {			g.addSchemaToDocumentV3(d, wk.NewGoogleProtobufValueSchema(schemaName))			continue		} else if typeName == ".google.protobuf.Any" {			g.addSchemaToDocumentV3(d, wk.NewGoogleProtobufAnySchema(schemaName))			continue		} else if typeName == ".google.rpc.Status" {			anySchemaName := g.reflect.formatMessageName(anyProtoDesc)			g.addSchemaToDocumentV3(d, wk.NewGoogleProtobufAnySchema(anySchemaName))			g.addSchemaToDocumentV3(d, wk.NewGoogleRpcStatusSchema(schemaName, anySchemaName))			continue		}		// Build an array holding the fields of the message.		definitionProperties := &v3.Properties{			AdditionalProperties: make([]*v3.NamedSchemaOrReference, 0),		}		var required []string		for _, field := range message.Fields {			// Get the field description from the comments.			description := g.filterCommentString(field.Comments.Leading)			// Check the field annotations to see if this is a readonly or writeonly field.			inputOnly := false			outputOnly := false			isRequired := true			extension := proto.GetExtension(field.Desc.Options(), annotations.E_FieldBehavior)			if extension != nil {				switch v := extension.(type) {				case []annotations.FieldBehavior:					for _, vv := range v {						switch vv {						case annotations.FieldBehavior_OUTPUT_ONLY:							outputOnly = true						case annotations.FieldBehavior_INPUT_ONLY:							inputOnly = true						case annotations.FieldBehavior_OPTIONAL:							isRequired = false						}					}				default:					log.Printf("unsupported extension type %T", extension)				}			}			if edition == descriptorpb.Edition_EDITION_2023 {				if fieldOptions, ok := field.Desc.Options().(*descriptorpb.FieldOptions); ok {					if fieldOptions.GetFeatures().GetFieldPresence() == descriptorpb.FeatureSet_EXPLICIT {						isRequired = false					}				}			}			if isRequired {				required = append(required, g.reflect.formatFieldName(field.Desc))			}			// The field is either described by a reference or a schema.			fieldSchema := g.reflect.schemaOrReferenceForField(field.Desc)			if fieldSchema == nil {				continue			}			// If this field has siblings and is a $ref now, create a new schema use `allOf` to wrap it			wrapperNeeded := inputOnly || outputOnly || description != ""			if wrapperNeeded {				if _, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Reference); ok {					fieldSchema = &v3.SchemaOrReference{Oneof: &v3.SchemaOrReference_Schema{Schema: &v3.Schema{						AllOf: []*v3.SchemaOrReference{fieldSchema},					}}}				}			}			if schema, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Schema); ok {				schema.Schema.Description = description				schema.Schema.ReadOnly = outputOnly				schema.Schema.WriteOnly = inputOnly				// Merge any `Property` annotations with the current				extProperty := proto.GetExtension(field.Desc.Options(), v3.E_Property)				if extProperty != nil {					proto.Merge(schema.Schema, extProperty.(*v3.Schema))				}			}			definitionProperties.AdditionalProperties = append(				definitionProperties.AdditionalProperties,				&v3.NamedSchemaOrReference{					Name:  g.reflect.formatFieldName(field.Desc),					Value: fieldSchema,				},			)		}		schema := &v3.Schema{			Type:        "object",			Description: messageDescription,			Properties:  definitionProperties,			Required:    required,		}		// Merge any `Schema` annotations with the current		extSchema := proto.GetExtension(message.Desc.Options(), v3.E_Schema)		if extSchema != nil {			proto.Merge(schema, extSchema.(*v3.Schema))		}		// Add the schema to the components.schema list.		g.addSchemaToDocumentV3(d, &v3.NamedSchemaOrReference{			Name: schemaName,			Value: &v3.SchemaOrReference{				Oneof: &v3.SchemaOrReference_Schema{					Schema: schema,				},			},		})	}}
 |