github.com/ffalor/go-swagger@v0.0.0-20231011000038-9f25265ac351/generator/operation.go (about)

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package generator
    16  
    17  import (
    18  	"encoding/json"
    19  	"errors"
    20  	"fmt"
    21  	"path/filepath"
    22  	"sort"
    23  	"strings"
    24  
    25  	"github.com/go-openapi/analysis"
    26  	"github.com/go-openapi/loads"
    27  	"github.com/go-openapi/runtime"
    28  	"github.com/go-openapi/spec"
    29  	"github.com/go-openapi/swag"
    30  )
    31  
    32  type respSort struct {
    33  	Code     int
    34  	Response spec.Response
    35  }
    36  
    37  type responses []respSort
    38  
    39  func (s responses) Len() int           { return len(s) }
    40  func (s responses) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    41  func (s responses) Less(i, j int) bool { return s[i].Code < s[j].Code }
    42  
    43  // sortedResponses produces a sorted list of responses.
    44  // TODO: this is redundant with the definition given in struct.go
    45  func sortedResponses(input map[int]spec.Response) responses {
    46  	var res responses
    47  	for k, v := range input {
    48  		if k > 0 {
    49  			res = append(res, respSort{k, v})
    50  		}
    51  	}
    52  	sort.Sort(res)
    53  	return res
    54  }
    55  
    56  // GenerateServerOperation generates a parameter model, parameter validator, http handler implementations for a given operation.
    57  //
    58  // It also generates an operation handler interface that uses the parameter model for handling a valid request.
    59  // Allows for specifying a list of tags to include only certain tags for the generation
    60  func GenerateServerOperation(operationNames []string, opts *GenOpts) error {
    61  	if err := opts.CheckOpts(); err != nil {
    62  		return err
    63  	}
    64  
    65  	if err := opts.setTemplates(); err != nil {
    66  		return err
    67  	}
    68  
    69  	specDoc, analyzed, err := opts.analyzeSpec()
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	ops := gatherOperations(analyzed, operationNames)
    75  
    76  	if len(ops) == 0 {
    77  		return errors.New("no operations were selected")
    78  	}
    79  
    80  	for operationName, opRef := range ops {
    81  		method, path, operation := opRef.Method, opRef.Path, opRef.Op
    82  
    83  		serverPackage := opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget)
    84  		generator := operationGenerator{
    85  			Name:                 operationName,
    86  			Method:               method,
    87  			Path:                 path,
    88  			BasePath:             specDoc.BasePath(),
    89  			APIPackage:           opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget),
    90  			ModelsPackage:        opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget),
    91  			ClientPackage:        opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget),
    92  			ServerPackage:        serverPackage,
    93  			Operation:            *operation,
    94  			SecurityRequirements: analyzed.SecurityRequirementsFor(operation),
    95  			SecurityDefinitions:  analyzed.SecurityDefinitionsFor(operation),
    96  			Principal:            opts.PrincipalAlias(),
    97  			Target:               filepath.Join(opts.Target, filepath.FromSlash(serverPackage)),
    98  			Base:                 opts.Target,
    99  			Tags:                 opts.Tags,
   100  			IncludeHandler:       opts.IncludeHandler,
   101  			IncludeParameters:    opts.IncludeParameters,
   102  			IncludeResponses:     opts.IncludeResponses,
   103  			IncludeValidator:     opts.IncludeValidator,
   104  			DumpData:             opts.DumpData,
   105  			DefaultScheme:        opts.DefaultScheme,
   106  			DefaultProduces:      opts.DefaultProduces,
   107  			DefaultConsumes:      opts.DefaultConsumes,
   108  			Doc:                  specDoc,
   109  			Analyzed:             analyzed,
   110  			GenOpts:              opts,
   111  		}
   112  		if err := generator.Generate(); err != nil {
   113  			return err
   114  		}
   115  	}
   116  	return nil
   117  }
   118  
   119  type operationGenerator struct {
   120  	Authorized        bool
   121  	IncludeHandler    bool
   122  	IncludeParameters bool
   123  	IncludeResponses  bool
   124  	IncludeValidator  bool
   125  	DumpData          bool
   126  
   127  	Principal            string
   128  	Target               string
   129  	Base                 string
   130  	Name                 string
   131  	Method               string
   132  	Path                 string
   133  	BasePath             string
   134  	APIPackage           string
   135  	ModelsPackage        string
   136  	ServerPackage        string
   137  	ClientPackage        string
   138  	Operation            spec.Operation
   139  	SecurityRequirements [][]analysis.SecurityRequirement
   140  	SecurityDefinitions  map[string]spec.SecurityScheme
   141  	Tags                 []string
   142  	DefaultScheme        string
   143  	DefaultProduces      string
   144  	DefaultConsumes      string
   145  	Doc                  *loads.Document
   146  	Analyzed             *analysis.Spec
   147  	GenOpts              *GenOpts
   148  }
   149  
   150  // Generate a single operation
   151  func (o *operationGenerator) Generate() error {
   152  
   153  	defaultImports := o.GenOpts.defaultImports()
   154  
   155  	apiPackage := o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.APIPackage, defaultOperationsTarget)
   156  	imports := o.GenOpts.initImports(
   157  		filepath.Join(o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.ServerPackage, defaultServerTarget), apiPackage))
   158  
   159  	bldr := codeGenOpBuilder{
   160  		ModelsPackage:       o.ModelsPackage,
   161  		Principal:           o.GenOpts.PrincipalAlias(),
   162  		Target:              o.Target,
   163  		DefaultImports:      defaultImports,
   164  		Imports:             imports,
   165  		DefaultScheme:       o.DefaultScheme,
   166  		Doc:                 o.Doc,
   167  		Analyzed:            o.Analyzed,
   168  		BasePath:            o.BasePath,
   169  		GenOpts:             o.GenOpts,
   170  		Name:                o.Name,
   171  		Operation:           o.Operation,
   172  		Method:              o.Method,
   173  		Path:                o.Path,
   174  		IncludeValidator:    o.IncludeValidator,
   175  		APIPackage:          o.APIPackage, // defaults to main operations package
   176  		DefaultProduces:     o.DefaultProduces,
   177  		DefaultConsumes:     o.DefaultConsumes,
   178  		Authed:              len(o.Analyzed.SecurityRequirementsFor(&o.Operation)) > 0,
   179  		Security:            o.Analyzed.SecurityRequirementsFor(&o.Operation),
   180  		SecurityDefinitions: o.Analyzed.SecurityDefinitionsFor(&o.Operation),
   181  		RootAPIPackage:      o.GenOpts.LanguageOpts.ManglePackageName(o.ServerPackage, defaultServerTarget),
   182  	}
   183  
   184  	_, tags, _ := bldr.analyzeTags()
   185  
   186  	op, err := bldr.MakeOperation()
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	op.Tags = tags
   192  	operations := make(GenOperations, 0, 1)
   193  	operations = append(operations, op)
   194  	sort.Sort(operations)
   195  
   196  	for _, pp := range operations {
   197  		op := pp
   198  		if o.GenOpts.DumpData {
   199  			_ = dumpData(swag.ToDynamicJSON(op))
   200  			continue
   201  		}
   202  		if err := o.GenOpts.renderOperation(&op); err != nil {
   203  			return err
   204  		}
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  type codeGenOpBuilder struct {
   211  	Authed           bool
   212  	IncludeValidator bool
   213  
   214  	Name                string
   215  	Method              string
   216  	Path                string
   217  	BasePath            string
   218  	APIPackage          string
   219  	APIPackageAlias     string
   220  	RootAPIPackage      string
   221  	ModelsPackage       string
   222  	Principal           string
   223  	Target              string
   224  	Operation           spec.Operation
   225  	Doc                 *loads.Document
   226  	PristineDoc         *loads.Document
   227  	Analyzed            *analysis.Spec
   228  	DefaultImports      map[string]string
   229  	Imports             map[string]string
   230  	DefaultScheme       string
   231  	DefaultProduces     string
   232  	DefaultConsumes     string
   233  	Security            [][]analysis.SecurityRequirement
   234  	SecurityDefinitions map[string]spec.SecurityScheme
   235  	ExtraSchemas        map[string]GenSchema
   236  	GenOpts             *GenOpts
   237  }
   238  
   239  // paramMappings yields a map of safe parameter names for an operation
   240  func paramMappings(params map[string]spec.Parameter) (map[string]map[string]string, string) {
   241  	idMapping := map[string]map[string]string{
   242  		"query":    make(map[string]string, len(params)),
   243  		"path":     make(map[string]string, len(params)),
   244  		"formData": make(map[string]string, len(params)),
   245  		"header":   make(map[string]string, len(params)),
   246  		"body":     make(map[string]string, len(params)),
   247  	}
   248  
   249  	// In order to avoid unstable generation, adopt same naming convention
   250  	// for all parameters with same name across locations.
   251  	seenIds := make(map[string]interface{}, len(params))
   252  	for id, p := range params {
   253  		if val, ok := seenIds[p.Name]; ok {
   254  			previous := val.(struct{ id, in string })
   255  			idMapping[p.In][p.Name] = swag.ToGoName(id)
   256  			// rewrite the previously found one
   257  			idMapping[previous.in][p.Name] = swag.ToGoName(previous.id)
   258  		} else {
   259  			idMapping[p.In][p.Name] = swag.ToGoName(p.Name)
   260  		}
   261  		seenIds[strings.ToLower(idMapping[p.In][p.Name])] = struct{ id, in string }{id: id, in: p.In}
   262  	}
   263  
   264  	// pick a deconflicted private name for timeout for this operation
   265  	timeoutName := renameTimeout(seenIds, "timeout")
   266  
   267  	return idMapping, timeoutName
   268  }
   269  
   270  // renameTimeout renames the variable in use by client template to avoid conflicting
   271  // with param names.
   272  //
   273  // NOTE: this merely protects the timeout field in the client parameter struct,
   274  // fields "Context" and "HTTPClient" remain exposed to name conflicts.
   275  func renameTimeout(seenIds map[string]interface{}, timeoutName string) string {
   276  	if seenIds == nil {
   277  		return timeoutName
   278  	}
   279  	current := strings.ToLower(timeoutName)
   280  	if _, ok := seenIds[current]; !ok {
   281  		return timeoutName
   282  	}
   283  	var next string
   284  	switch current {
   285  	case "timeout":
   286  		next = "requestTimeout"
   287  	case "requesttimeout":
   288  		next = "httpRequestTimeout"
   289  	case "httprequesttimeout":
   290  		next = "swaggerTimeout"
   291  	case "swaggertimeout":
   292  		next = "operationTimeout"
   293  	case "operationtimeout":
   294  		next = "opTimeout"
   295  	case "optimeout":
   296  		next = "operTimeout"
   297  	default:
   298  		next = timeoutName + "1"
   299  	}
   300  	return renameTimeout(seenIds, next)
   301  }
   302  
   303  func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) {
   304  	debugLog("[%s %s] parsing operation (id: %q)", b.Method, b.Path, b.Operation.ID)
   305  	// NOTE: we assume flatten is enabled by default (i.e. complex constructs are resolved from the models package),
   306  	// but do not assume the spec is necessarily fully flattened (i.e. all schemas moved to definitions).
   307  	//
   308  	// Fully flattened means that all complex constructs are present as
   309  	// definitions and models produced accordingly in ModelsPackage,
   310  	// whereas minimal flatten simply ensures that there are no weird $ref's in the spec.
   311  	//
   312  	// When some complex anonymous constructs are specified, extra schemas are produced in the operations package.
   313  	//
   314  	// In all cases, resetting definitions to the _original_ (untransformed) spec is not an option:
   315  	// we take from there the spec possibly already transformed by the GenDefinitions stage.
   316  	resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.Doc)
   317  	receiver := "o"
   318  
   319  	operation := b.Operation
   320  	var params, qp, pp, hp, fp GenParameters
   321  	var hasQueryParams, hasPathParams, hasHeaderParams, hasFormParams, hasFileParams, hasFormValueParams, hasBodyParams bool
   322  	paramsForOperation := b.Analyzed.ParamsFor(b.Method, b.Path)
   323  
   324  	idMapping, timeoutName := paramMappings(paramsForOperation)
   325  
   326  	for _, p := range paramsForOperation {
   327  		cp, err := b.MakeParameter(receiver, resolver, p, idMapping)
   328  
   329  		if err != nil {
   330  			return GenOperation{}, err
   331  		}
   332  		if cp.IsQueryParam() {
   333  			hasQueryParams = true
   334  			qp = append(qp, cp)
   335  		}
   336  		if cp.IsFormParam() {
   337  			if p.Type == file {
   338  				hasFileParams = true
   339  			}
   340  			if p.Type != file {
   341  				hasFormValueParams = true
   342  			}
   343  			hasFormParams = true
   344  			fp = append(fp, cp)
   345  		}
   346  		if cp.IsPathParam() {
   347  			hasPathParams = true
   348  			pp = append(pp, cp)
   349  		}
   350  		if cp.IsHeaderParam() {
   351  			hasHeaderParams = true
   352  			hp = append(hp, cp)
   353  		}
   354  		if cp.IsBodyParam() {
   355  			hasBodyParams = true
   356  		}
   357  		params = append(params, cp)
   358  	}
   359  	sort.Sort(params)
   360  	sort.Sort(qp)
   361  	sort.Sort(pp)
   362  	sort.Sort(hp)
   363  	sort.Sort(fp)
   364  
   365  	var srs responses
   366  	if operation.Responses != nil {
   367  		srs = sortedResponses(operation.Responses.StatusCodeResponses)
   368  	}
   369  	responses := make([]GenResponse, 0, len(srs))
   370  	var defaultResponse *GenResponse
   371  	var successResponses []GenResponse
   372  	if operation.Responses != nil {
   373  		for _, v := range srs {
   374  			name, ok := v.Response.Extensions.GetString(xGoName)
   375  			if !ok {
   376  				// look for name of well-known codes
   377  				name = runtime.Statuses[v.Code]
   378  				if name == "" {
   379  					// non-standard codes deserve some name
   380  					name = fmt.Sprintf("Status %d", v.Code)
   381  				}
   382  			}
   383  			name = swag.ToJSONName(b.Name + " " + name)
   384  			isSuccess := v.Code/100 == 2
   385  			gr, err := b.MakeResponse(receiver, name, isSuccess, resolver, v.Code, v.Response)
   386  			if err != nil {
   387  				return GenOperation{}, err
   388  			}
   389  			if isSuccess {
   390  				successResponses = append(successResponses, gr)
   391  			}
   392  			responses = append(responses, gr)
   393  		}
   394  
   395  		if operation.Responses.Default != nil {
   396  			gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, *operation.Responses.Default)
   397  			if err != nil {
   398  				return GenOperation{}, err
   399  			}
   400  			defaultResponse = &gr
   401  		}
   402  	}
   403  
   404  	// Always render a default response, even when no responses were defined
   405  	if operation.Responses == nil || (operation.Responses.Default == nil && len(srs) == 0) {
   406  		gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, spec.Response{})
   407  		if err != nil {
   408  			return GenOperation{}, err
   409  		}
   410  		defaultResponse = &gr
   411  	}
   412  
   413  	swsp := resolver.Doc.Spec()
   414  
   415  	schemes, extraSchemes := gatherURISchemes(swsp, operation)
   416  	originalSchemes := operation.Schemes
   417  	originalExtraSchemes := getExtraSchemes(operation.Extensions)
   418  
   419  	produces := producesOrDefault(operation.Produces, swsp.Produces, b.DefaultProduces)
   420  	sort.Strings(produces)
   421  
   422  	consumes := producesOrDefault(operation.Consumes, swsp.Consumes, b.DefaultConsumes)
   423  	sort.Strings(consumes)
   424  
   425  	var successResponse *GenResponse
   426  	for _, resp := range successResponses {
   427  		sr := resp
   428  		if sr.IsSuccess {
   429  			successResponse = &sr
   430  			break
   431  		}
   432  	}
   433  
   434  	var hasStreamingResponse bool
   435  	if defaultResponse != nil && defaultResponse.Schema != nil && defaultResponse.Schema.IsStream {
   436  		hasStreamingResponse = true
   437  	}
   438  
   439  	if !hasStreamingResponse {
   440  		for _, sr := range successResponses {
   441  			if !hasStreamingResponse && sr.Schema != nil && sr.Schema.IsStream {
   442  				hasStreamingResponse = true
   443  				break
   444  			}
   445  		}
   446  	}
   447  
   448  	if !hasStreamingResponse {
   449  		for _, r := range responses {
   450  			if r.Schema != nil && r.Schema.IsStream {
   451  				hasStreamingResponse = true
   452  				break
   453  			}
   454  		}
   455  	}
   456  
   457  	return GenOperation{
   458  		GenCommon: GenCommon{
   459  			Copyright:        b.GenOpts.Copyright,
   460  			TargetImportPath: b.GenOpts.LanguageOpts.baseImport(b.GenOpts.Target),
   461  		},
   462  		Package:              b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
   463  		PackageAlias:         b.APIPackageAlias,
   464  		RootPackage:          b.RootAPIPackage,
   465  		Name:                 b.Name,
   466  		Method:               b.Method,
   467  		Path:                 b.Path,
   468  		BasePath:             b.BasePath,
   469  		Tags:                 operation.Tags,
   470  		UseTags:              len(operation.Tags) > 0 && !b.GenOpts.SkipTagPackages,
   471  		Description:          trimBOM(operation.Description),
   472  		ReceiverName:         receiver,
   473  		DefaultImports:       b.DefaultImports,
   474  		Imports:              b.Imports,
   475  		Params:               params,
   476  		Summary:              trimBOM(operation.Summary),
   477  		QueryParams:          qp,
   478  		PathParams:           pp,
   479  		HeaderParams:         hp,
   480  		FormParams:           fp,
   481  		HasQueryParams:       hasQueryParams,
   482  		HasPathParams:        hasPathParams,
   483  		HasHeaderParams:      hasHeaderParams,
   484  		HasFormParams:        hasFormParams,
   485  		HasFormValueParams:   hasFormValueParams,
   486  		HasFileParams:        hasFileParams,
   487  		HasBodyParams:        hasBodyParams,
   488  		HasStreamingResponse: hasStreamingResponse,
   489  		Authorized:           b.Authed,
   490  		Security:             b.makeSecurityRequirements(receiver), // resolved security requirements, for codegen
   491  		SecurityDefinitions:  b.makeSecuritySchemes(receiver),
   492  		SecurityRequirements: securityRequirements(operation.Security), // raw security requirements, for doc
   493  		Principal:            b.Principal,
   494  		Responses:            responses,
   495  		DefaultResponse:      defaultResponse,
   496  		SuccessResponse:      successResponse,
   497  		SuccessResponses:     successResponses,
   498  		ExtraSchemas:         gatherExtraSchemas(b.ExtraSchemas),
   499  		Schemes:              schemeOrDefault(schemes, b.DefaultScheme),
   500  		SchemeOverrides:      originalSchemes,      // raw operation schemes, for doc
   501  		ProducesMediaTypes:   produces,             // resolved produces, for codegen
   502  		ConsumesMediaTypes:   consumes,             // resolved consumes, for codegen
   503  		Produces:             operation.Produces,   // for doc
   504  		Consumes:             operation.Consumes,   // for doc
   505  		ExtraSchemes:         extraSchemes,         // resolved schemes, for codegen
   506  		ExtraSchemeOverrides: originalExtraSchemes, // raw operation extra schemes, for doc
   507  		TimeoutName:          timeoutName,
   508  		Extensions:           operation.Extensions,
   509  		StrictResponders:     b.GenOpts.StrictResponders,
   510  
   511  		PrincipalIsNullable: b.GenOpts.PrincipalIsNullable(),
   512  		ExternalDocs:        trimExternalDoc(operation.ExternalDocs),
   513  	}, nil
   514  }
   515  
   516  func producesOrDefault(produces []string, fallback []string, defaultProduces string) []string {
   517  	if len(produces) > 0 {
   518  		return produces
   519  	}
   520  	if len(fallback) > 0 {
   521  		return fallback
   522  	}
   523  	return []string{defaultProduces}
   524  }
   525  
   526  func schemeOrDefault(schemes []string, defaultScheme string) []string {
   527  	if len(schemes) == 0 {
   528  		return []string{defaultScheme}
   529  	}
   530  	return schemes
   531  }
   532  
   533  func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, resolver *typeResolver, code int, resp spec.Response) (GenResponse, error) {
   534  	debugLog("[%s %s] making id %q", b.Method, b.Path, b.Operation.ID)
   535  
   536  	// assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema)
   537  	examples := make(GenResponseExamples, 0, len(resp.Examples))
   538  	for k, v := range resp.Examples {
   539  		examples = append(examples, GenResponseExample{MediaType: k, Example: v})
   540  	}
   541  	sort.Sort(examples)
   542  
   543  	res := GenResponse{
   544  		Package:          b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
   545  		ModelsPackage:    b.ModelsPackage,
   546  		ReceiverName:     receiver,
   547  		Name:             name,
   548  		Description:      trimBOM(resp.Description),
   549  		DefaultImports:   b.DefaultImports,
   550  		Imports:          b.Imports,
   551  		IsSuccess:        isSuccess,
   552  		Code:             code,
   553  		Method:           b.Method,
   554  		Path:             b.Path,
   555  		Extensions:       resp.Extensions,
   556  		StrictResponders: b.GenOpts.StrictResponders,
   557  		OperationName:    b.Name,
   558  		Examples:         examples,
   559  	}
   560  
   561  	// prepare response headers
   562  	for hName, header := range resp.Headers {
   563  		hdr, err := b.MakeHeader(receiver, hName, header)
   564  		if err != nil {
   565  			return GenResponse{}, err
   566  		}
   567  		res.Headers = append(res.Headers, hdr)
   568  	}
   569  	sort.Sort(res.Headers)
   570  
   571  	if resp.Schema != nil {
   572  		// resolve schema model
   573  		schema, ers := b.buildOperationSchema(fmt.Sprintf("%q", name), name+"Body", swag.ToGoName(name+"Body"), receiver, "i", resp.Schema, resolver)
   574  		if ers != nil {
   575  			return GenResponse{}, ers
   576  		}
   577  		res.Schema = &schema
   578  	}
   579  	return res, nil
   580  }
   581  
   582  func (b *codeGenOpBuilder) MakeHeader(receiver, name string, hdr spec.Header) (GenHeader, error) {
   583  	tpe := simpleResolvedType(hdr.Type, hdr.Format, hdr.Items, &hdr.CommonValidations)
   584  
   585  	id := swag.ToGoName(name)
   586  	res := GenHeader{
   587  		sharedValidations: sharedValidations{
   588  			Required:          true,
   589  			SchemaValidations: hdr.Validations(), // NOTE: Required is not defined by the Swagger schema for header. Set arbitrarily to true for convenience in templates.
   590  		},
   591  		resolvedType:     tpe,
   592  		Package:          b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget),
   593  		ReceiverName:     receiver,
   594  		ID:               id,
   595  		Name:             name,
   596  		Path:             fmt.Sprintf("%q", name),
   597  		ValueExpression:  fmt.Sprintf("%s.%s", receiver, id),
   598  		Description:      trimBOM(hdr.Description),
   599  		Default:          hdr.Default,
   600  		HasDefault:       hdr.Default != nil,
   601  		Converter:        stringConverters[tpe.GoType],
   602  		Formatter:        stringFormatters[tpe.GoType],
   603  		ZeroValue:        tpe.Zero(),
   604  		CollectionFormat: hdr.CollectionFormat,
   605  		IndexVar:         "i",
   606  	}
   607  	res.HasValidations, res.HasSliceValidations = b.HasValidations(hdr.CommonValidations, res.resolvedType)
   608  
   609  	hasChildValidations := false
   610  	if hdr.Items != nil {
   611  		pi, err := b.MakeHeaderItem(receiver, name+" "+res.IndexVar, res.IndexVar+"i", "fmt.Sprintf(\"%s.%v\", \"header\", "+res.IndexVar+")", res.Name+"I", hdr.Items, nil)
   612  		if err != nil {
   613  			return GenHeader{}, err
   614  		}
   615  		res.Child = &pi
   616  		hasChildValidations = pi.HasValidations
   617  	}
   618  	// we feed the GenHeader structure the same way as we do for
   619  	// GenParameter, even though there is currently no actual validation
   620  	// for response headers.
   621  	res.HasValidations = res.HasValidations || hasChildValidations
   622  
   623  	return res, nil
   624  }
   625  
   626  func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, valueExpression string, items, _ *spec.Items) (GenItems, error) {
   627  	var res GenItems
   628  	res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations)
   629  
   630  	res.sharedValidations = sharedValidations{
   631  		Required:          false,
   632  		SchemaValidations: items.Validations(),
   633  	}
   634  	res.Name = paramName
   635  	res.Path = path
   636  	res.Location = "header"
   637  	res.ValueExpression = swag.ToVarName(valueExpression)
   638  	res.CollectionFormat = items.CollectionFormat
   639  	res.Converter = stringConverters[res.GoType]
   640  	res.Formatter = stringFormatters[res.GoType]
   641  	res.IndexVar = indexVar
   642  	res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType)
   643  	res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(items.Extensions)
   644  
   645  	if items.Items != nil {
   646  		// Recursively follows nested arrays
   647  		// IMPORTANT! transmitting a ValueExpression consistent with the parent's one
   648  		hi, err := b.MakeHeaderItem(receiver, paramName+" "+indexVar, indexVar+"i", "fmt.Sprintf(\"%s.%v\", \"header\", "+indexVar+")", res.ValueExpression+"I", items.Items, items)
   649  		if err != nil {
   650  			return GenItems{}, err
   651  		}
   652  		res.Child = &hi
   653  		hi.Parent = &res
   654  		// Propagates HasValidations flag to outer Items definition (currently not in use: done to remain consistent with parameters)
   655  		res.HasValidations = res.HasValidations || hi.HasValidations
   656  	}
   657  
   658  	return res, nil
   659  }
   660  
   661  // HasValidations resolves the validation status for simple schema objects
   662  func (b *codeGenOpBuilder) HasValidations(sh spec.CommonValidations, rt resolvedType) (hasValidations bool, hasSliceValidations bool) {
   663  	hasSliceValidations = sh.HasArrayValidations() || sh.HasEnum()
   664  	hasValidations = sh.HasNumberValidations() || sh.HasStringValidations() || hasSliceValidations || hasFormatValidation(rt)
   665  	return
   666  }
   667  
   668  func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path, valueExpression, location string, resolver *typeResolver, items, _ *spec.Items) (GenItems, error) {
   669  	debugLog("making parameter item recv=%s param=%s index=%s valueExpr=%s path=%s location=%s", receiver, paramName, indexVar, valueExpression, path, location)
   670  	var res GenItems
   671  	res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items, &items.CommonValidations)
   672  
   673  	res.sharedValidations = sharedValidations{
   674  		Required:          false,
   675  		SchemaValidations: items.Validations(),
   676  	}
   677  	res.Name = paramName
   678  	res.Path = path
   679  	res.Location = location
   680  	res.ValueExpression = swag.ToVarName(valueExpression)
   681  	res.CollectionFormat = items.CollectionFormat
   682  	res.Converter = stringConverters[res.GoType]
   683  	res.Formatter = stringFormatters[res.GoType]
   684  	res.IndexVar = indexVar
   685  
   686  	res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType)
   687  	res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(items.Extensions)
   688  	res.NeedsIndex = res.HasValidations || res.Converter != "" || (res.IsCustomFormatter && !res.SkipParse)
   689  
   690  	if items.Items != nil {
   691  		// Recursively follows nested arrays
   692  		// IMPORTANT! transmitting a ValueExpression consistent with the parent's one
   693  		pi, err := b.MakeParameterItem(receiver, paramName+" "+indexVar, indexVar+"i", "fmt.Sprintf(\"%s.%v\", "+path+", "+indexVar+")", res.ValueExpression+"I", location, resolver, items.Items, items)
   694  		if err != nil {
   695  			return GenItems{}, err
   696  		}
   697  		res.Child = &pi
   698  		pi.Parent = &res
   699  		// Propagates HasValidations flag to outer Items definition
   700  		res.HasValidations = res.HasValidations || pi.HasValidations
   701  		res.NeedsIndex = res.NeedsIndex || pi.NeedsIndex
   702  	}
   703  
   704  	return res, nil
   705  }
   706  
   707  func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver, param spec.Parameter, idMapping map[string]map[string]string) (GenParameter, error) {
   708  	debugLog("[%s %s] making parameter %q", b.Method, b.Path, param.Name)
   709  
   710  	// assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema)
   711  
   712  	var child *GenItems
   713  	id := swag.ToGoName(param.Name)
   714  	if goName, ok := param.Extensions["x-go-name"]; ok {
   715  		id, ok = goName.(string)
   716  		if !ok {
   717  			return GenParameter{}, fmt.Errorf(`%s %s, parameter %q: "x-go-name" field must be a string, not a %T`,
   718  				b.Method, b.Path, param.Name, goName)
   719  		}
   720  	} else if len(idMapping) > 0 {
   721  		id = idMapping[param.In][param.Name]
   722  	}
   723  
   724  	res := GenParameter{
   725  		ID:               id,
   726  		Name:             param.Name,
   727  		ModelsPackage:    b.ModelsPackage,
   728  		Path:             fmt.Sprintf("%q", param.Name),
   729  		ValueExpression:  fmt.Sprintf("%s.%s", receiver, id),
   730  		IndexVar:         "i",
   731  		Default:          param.Default,
   732  		HasDefault:       param.Default != nil,
   733  		Description:      trimBOM(param.Description),
   734  		ReceiverName:     receiver,
   735  		CollectionFormat: param.CollectionFormat,
   736  		Child:            child,
   737  		Location:         param.In,
   738  		AllowEmptyValue:  (param.In == "query" || param.In == "formData") && param.AllowEmptyValue,
   739  		Extensions:       param.Extensions,
   740  	}
   741  
   742  	if goCustomTag, ok := param.Extensions["x-go-custom-tag"]; ok {
   743  		customTag, ok := goCustomTag.(string)
   744  		if !ok {
   745  			return GenParameter{}, fmt.Errorf(`%s %s, parameter %q: "x-go-custom-tag" field must be a string, not a %T`,
   746  				b.Method, b.Path, param.Name, goCustomTag)
   747  		}
   748  
   749  		res.CustomTag = customTag
   750  	}
   751  
   752  	if param.In == "body" {
   753  		// Process parameters declared in body (i.e. have a Schema)
   754  		res.Required = param.Required
   755  		if err := b.MakeBodyParameter(&res, resolver, param.Schema); err != nil {
   756  			return GenParameter{}, err
   757  		}
   758  	} else {
   759  		// Process parameters declared in other inputs: path, query, header (SimpleSchema)
   760  		res.resolvedType = simpleResolvedType(param.Type, param.Format, param.Items, &param.CommonValidations)
   761  		res.sharedValidations = sharedValidations{
   762  			Required:          param.Required,
   763  			SchemaValidations: param.Validations(),
   764  		}
   765  
   766  		res.ZeroValue = res.resolvedType.Zero()
   767  
   768  		hasChildValidations := false
   769  		if param.Items != nil {
   770  			// Follow Items definition for array parameters
   771  			pi, err := b.MakeParameterItem(receiver, param.Name+" "+res.IndexVar, res.IndexVar+"i", "fmt.Sprintf(\"%s.%v\", "+res.Path+", "+res.IndexVar+")", res.Name+"I", param.In, resolver, param.Items, nil)
   772  			if err != nil {
   773  				return GenParameter{}, err
   774  			}
   775  			res.Child = &pi
   776  			// Propagates HasValidations from from child array
   777  			hasChildValidations = pi.HasValidations
   778  		}
   779  		res.IsNullable = !param.Required && !param.AllowEmptyValue
   780  		res.HasValidations, res.HasSliceValidations = b.HasValidations(param.CommonValidations, res.resolvedType)
   781  		res.HasValidations = res.HasValidations || hasChildValidations
   782  		res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(param.Extensions)
   783  	}
   784  
   785  	// Select codegen strategy for body param validation
   786  	res.Converter = stringConverters[res.GoType]
   787  	res.Formatter = stringFormatters[res.GoType]
   788  	b.setBodyParamValidation(&res)
   789  
   790  	return res, nil
   791  }
   792  
   793  // MakeBodyParameter constructs a body parameter schema
   794  func (b *codeGenOpBuilder) MakeBodyParameter(res *GenParameter, resolver *typeResolver, sch *spec.Schema) error {
   795  	// resolve schema model
   796  	schema, ers := b.buildOperationSchema(res.Path, b.Operation.ID+"ParamsBody", swag.ToGoName(b.Operation.ID+" Body"), res.ReceiverName, res.IndexVar, sch, resolver)
   797  	if ers != nil {
   798  		return ers
   799  	}
   800  	res.Schema = &schema
   801  	res.Schema.Required = res.Required // Required in body is managed independently from validations
   802  
   803  	// build Child items for nested slices and maps
   804  	var items *GenItems
   805  	res.KeyVar = "k"
   806  	res.Schema.KeyVar = "k"
   807  	switch {
   808  	case schema.IsMap && !schema.IsInterface:
   809  		items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.AdditionalProperties)
   810  	case schema.IsArray:
   811  		items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.Items)
   812  	default:
   813  		items = new(GenItems)
   814  	}
   815  
   816  	// templates assume at least one .Child != nil
   817  	res.Child = items
   818  	schema.HasValidations = schema.HasValidations || items.HasValidations
   819  
   820  	res.resolvedType = schema.resolvedType
   821  
   822  	// simple and schema views share the same validations
   823  	res.sharedValidations = schema.sharedValidations
   824  	res.ZeroValue = schema.Zero()
   825  	return nil
   826  }
   827  
   828  // MakeBodyParameterItemsAndMaps clones the .Items schema structure (resp. .AdditionalProperties) as a .GenItems structure
   829  // for compatibility with simple param templates.
   830  //
   831  // Constructed children assume simple structures: any complex object is assumed to be resolved by a model or extra schema definition
   832  func (b *codeGenOpBuilder) MakeBodyParameterItemsAndMaps(res *GenParameter, it *GenSchema) *GenItems {
   833  	items := new(GenItems)
   834  	if it != nil {
   835  		var prev *GenItems
   836  		next := items
   837  		if res.Schema.IsArray {
   838  			next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.IndexVar + ")"
   839  		} else if res.Schema.IsMap {
   840  			next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.KeyVar + ")"
   841  		}
   842  		next.Name = res.Name + " " + res.Schema.IndexVar
   843  		next.IndexVar = res.Schema.IndexVar + "i"
   844  		next.KeyVar = res.Schema.KeyVar + "k"
   845  		next.ValueExpression = swag.ToVarName(res.Name + "I")
   846  		next.Location = "body"
   847  		for it != nil {
   848  			next.resolvedType = it.resolvedType
   849  			next.sharedValidations = it.sharedValidations
   850  			next.Formatter = stringFormatters[it.SwaggerFormat]
   851  			next.Converter = stringConverters[res.GoType]
   852  			next.Parent = prev
   853  			_, next.IsCustomFormatter = customFormatters[it.GoType]
   854  			next.IsCustomFormatter = next.IsCustomFormatter && !it.IsStream
   855  
   856  			// special instruction to avoid using CollectionFormat for body params
   857  			next.SkipParse = true
   858  
   859  			if prev != nil {
   860  				if prev.IsArray {
   861  					next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.IndexVar + ")"
   862  				} else if prev.IsMap {
   863  					next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.KeyVar + ")"
   864  				}
   865  				next.Name = prev.Name + prev.IndexVar
   866  				next.IndexVar = prev.IndexVar + "i"
   867  				next.KeyVar = prev.KeyVar + "k"
   868  				next.ValueExpression = swag.ToVarName(prev.ValueExpression + "I")
   869  				prev.Child = next
   870  			}
   871  
   872  			// found a complex or aliased thing
   873  			// hide details from the aliased type and stop recursing
   874  			if next.IsAliased || next.IsComplexObject {
   875  				next.IsArray = false
   876  				next.IsMap = false
   877  				next.IsCustomFormatter = false
   878  				next.IsComplexObject = true
   879  				next.IsAliased = true
   880  				break
   881  			}
   882  			if next.IsInterface || next.IsStream || next.IsBase64 {
   883  				next.HasValidations = false
   884  			}
   885  			next.NeedsIndex = next.HasValidations || next.Converter != "" || (next.IsCustomFormatter && !next.SkipParse)
   886  			prev = next
   887  			next = new(GenItems)
   888  
   889  			switch {
   890  			case it.Items != nil:
   891  				it = it.Items
   892  			case it.AdditionalProperties != nil:
   893  				it = it.AdditionalProperties
   894  			default:
   895  				it = nil
   896  			}
   897  		}
   898  		// propagate HasValidations
   899  		var propag func(child *GenItems) (bool, bool)
   900  		propag = func(child *GenItems) (bool, bool) {
   901  			if child == nil {
   902  				return false, false
   903  			}
   904  			cValidations, cIndex := propag(child.Child)
   905  			child.HasValidations = child.HasValidations || cValidations
   906  			child.NeedsIndex = child.HasValidations || child.Converter != "" || (child.IsCustomFormatter && !child.SkipParse) || cIndex
   907  			return child.HasValidations, child.NeedsIndex
   908  		}
   909  		items.HasValidations, items.NeedsIndex = propag(items)
   910  
   911  		// resolve nullability conflicts when declaring body as a map of array of an anonymous complex object
   912  		// (e.g. refer to an extra schema type, which is nullable, but not rendered as a pointer in arrays or maps)
   913  		// Rule: outer type rules (with IsMapNullOverride), inner types are fixed
   914  		var fixNullable func(child *GenItems) string
   915  		fixNullable = func(child *GenItems) string {
   916  			if !child.IsArray && !child.IsMap {
   917  				if child.IsComplexObject {
   918  					return child.GoType
   919  				}
   920  				return ""
   921  			}
   922  			if innerType := fixNullable(child.Child); innerType != "" {
   923  				if child.IsMapNullOverride && child.IsArray {
   924  					child.GoType = "[]" + innerType
   925  					return child.GoType
   926  				}
   927  			}
   928  			return ""
   929  		}
   930  		fixNullable(items)
   931  	}
   932  	return items
   933  }
   934  
   935  func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) {
   936  	// Determine validation strategy for body param.
   937  	//
   938  	// Here are the distinct strategies:
   939  	// - the body parameter is a model object => delegates
   940  	// - the body parameter is an array of model objects => carry on slice validations, then iterate and delegate
   941  	// - the body parameter is a map of model objects => iterate and delegate
   942  	// - the body parameter is an array of simple objects (including maps)
   943  	// - the body parameter is a map of simple objects (including arrays)
   944  	if p.IsBodyParam() {
   945  		var hasSimpleBodyParams, hasSimpleBodyItems, hasSimpleBodyMap, hasModelBodyParams, hasModelBodyItems, hasModelBodyMap bool
   946  		s := p.Schema
   947  		if s != nil {
   948  			doNot := s.IsInterface || s.IsStream || s.IsBase64
   949  			// composition of primitive fields must be properly identified: hack this through
   950  			_, isPrimitive := primitives[s.GoType]
   951  			_, isFormatter := customFormatters[s.GoType]
   952  			isComposedPrimitive := s.IsPrimitive && !(isPrimitive || isFormatter)
   953  
   954  			hasSimpleBodyParams = !s.IsComplexObject && !s.IsAliased && !isComposedPrimitive && !doNot
   955  			hasModelBodyParams = (s.IsComplexObject || s.IsAliased || isComposedPrimitive) && !doNot
   956  
   957  			if s.IsArray && s.Items != nil {
   958  				it := s.Items
   959  				doNot = it.IsInterface || it.IsStream || it.IsBase64
   960  				hasSimpleBodyItems = !it.IsComplexObject && !(it.IsAliased || doNot)
   961  				hasModelBodyItems = (it.IsComplexObject || it.IsAliased) && !doNot
   962  			}
   963  			if s.IsMap && s.AdditionalProperties != nil {
   964  				it := s.AdditionalProperties
   965  				hasSimpleBodyMap = !it.IsComplexObject && !(it.IsAliased || doNot)
   966  				hasModelBodyMap = !hasSimpleBodyMap && !doNot
   967  			}
   968  		}
   969  		// set validation strategy for body param
   970  		p.HasSimpleBodyParams = hasSimpleBodyParams
   971  		p.HasSimpleBodyItems = hasSimpleBodyItems
   972  		p.HasModelBodyParams = hasModelBodyParams
   973  		p.HasModelBodyItems = hasModelBodyItems
   974  		p.HasModelBodyMap = hasModelBodyMap
   975  		p.HasSimpleBodyMap = hasSimpleBodyMap
   976  	}
   977  
   978  }
   979  
   980  // makeSecuritySchemes produces a sorted list of security schemes for this operation
   981  func (b *codeGenOpBuilder) makeSecuritySchemes(receiver string) GenSecuritySchemes {
   982  	return gatherSecuritySchemes(b.SecurityDefinitions, b.Name, b.Principal, receiver, b.GenOpts.PrincipalIsNullable())
   983  }
   984  
   985  // makeSecurityRequirements produces a sorted list of security requirements for this operation.
   986  // As for current, these requirements are not used by codegen (sec. requirement is determined at runtime).
   987  // We keep the order of the slice from the original spec, but sort the inner slice which comes from a map,
   988  // as well as the map of scopes.
   989  func (b *codeGenOpBuilder) makeSecurityRequirements(_ string) []GenSecurityRequirements {
   990  	if b.Security == nil {
   991  		// nil (default requirement) is different than [] (no requirement)
   992  		return nil
   993  	}
   994  
   995  	securityRequirements := make([]GenSecurityRequirements, 0, len(b.Security))
   996  	for _, req := range b.Security {
   997  		jointReq := make(GenSecurityRequirements, 0, len(req))
   998  		for _, j := range req {
   999  			scopes := j.Scopes
  1000  			sort.Strings(scopes)
  1001  			jointReq = append(jointReq, GenSecurityRequirement{
  1002  				Name:   j.Name,
  1003  				Scopes: scopes,
  1004  			})
  1005  		}
  1006  		// sort joint requirements (come from a map in spec)
  1007  		sort.Sort(jointReq)
  1008  		securityRequirements = append(securityRequirements, jointReq)
  1009  	}
  1010  	return securityRequirements
  1011  }
  1012  
  1013  // cloneSchema returns a deep copy of a schema
  1014  func (b *codeGenOpBuilder) cloneSchema(schema *spec.Schema) *spec.Schema {
  1015  	savedSchema := &spec.Schema{}
  1016  	schemaRep, _ := json.Marshal(schema)
  1017  	_ = json.Unmarshal(schemaRep, savedSchema)
  1018  	return savedSchema
  1019  }
  1020  
  1021  // saveResolveContext keeps a copy of known definitions and schema to properly roll back on a makeGenSchema() call
  1022  // This uses a deep clone the spec document to construct a type resolver which knows about definitions when the making of this operation started,
  1023  // and only these definitions. We are not interested in the "original spec", but in the already transformed spec.
  1024  func (b *codeGenOpBuilder) saveResolveContext(resolver *typeResolver, schema *spec.Schema) (*typeResolver, *spec.Schema) {
  1025  	if b.PristineDoc == nil {
  1026  		b.PristineDoc = b.Doc.Pristine()
  1027  	}
  1028  	rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.PristineDoc)
  1029  
  1030  	return rslv, b.cloneSchema(schema)
  1031  }
  1032  
  1033  // liftExtraSchemas constructs the schema for an anonymous construct with some ExtraSchemas.
  1034  //
  1035  // When some ExtraSchemas are produced from something else than a definition,
  1036  // this indicates we are not running in fully flattened mode and we need to render
  1037  // these ExtraSchemas in the operation's package.
  1038  // We need to rebuild the schema with a new type resolver to reflect this change in the
  1039  // models package.
  1040  func (b *codeGenOpBuilder) liftExtraSchemas(resolver, rslv *typeResolver, bs *spec.Schema, sc *schemaGenContext) (schema *GenSchema, err error) {
  1041  	// restore resolving state before previous call to makeGenSchema()
  1042  	sc.Schema = *bs
  1043  
  1044  	pg := sc.shallowClone()
  1045  	pkg := b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget)
  1046  
  1047  	// make a resolver for current package (i.e. operations)
  1048  	pg.TypeResolver = newTypeResolver("", b.DefaultImports[b.APIPackage], rslv.Doc).
  1049  		withKeepDefinitionsPackage(pkg).
  1050  		withDefinitionPackage(b.APIPackageAlias) // all new extra schemas are going to be in api pkg
  1051  	pg.ExtraSchemas = make(map[string]GenSchema, len(sc.ExtraSchemas))
  1052  	pg.UseContainerInName = true
  1053  
  1054  	// rebuild schema within local package
  1055  	if err = pg.makeGenSchema(); err != nil {
  1056  		return
  1057  	}
  1058  
  1059  	// lift nested extra schemas (inlined types)
  1060  	if b.ExtraSchemas == nil {
  1061  		b.ExtraSchemas = make(map[string]GenSchema, len(pg.ExtraSchemas))
  1062  	}
  1063  	for _, v := range pg.ExtraSchemas {
  1064  		vv := v
  1065  		if !v.IsStream {
  1066  			b.ExtraSchemas[vv.Name] = vv
  1067  		}
  1068  	}
  1069  	schema = &pg.GenSchema
  1070  	return
  1071  }
  1072  
  1073  // buildOperationSchema constructs a schema for an operation (for body params or responses).
  1074  // It determines if the schema is readily available from the models package,
  1075  // or if a schema has to be generated in the operations package (i.e. is anonymous).
  1076  // Whenever an anonymous schema needs some extra schemas, we also determine if these extras are
  1077  // available from models or must be generated alongside the schema in the operations package.
  1078  //
  1079  // Duplicate extra schemas are pruned later on, when operations grouping in packages (e.g. from tags) takes place.
  1080  func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schemaName, receiverName, indexVar string, sch *spec.Schema, resolver *typeResolver) (GenSchema, error) {
  1081  	var schema GenSchema
  1082  
  1083  	if sch == nil {
  1084  		sch = &spec.Schema{}
  1085  	}
  1086  	shallowClonedResolver := *resolver
  1087  	shallowClonedResolver.ModelsFullPkg = b.DefaultImports[b.ModelsPackage]
  1088  	rslv := &shallowClonedResolver
  1089  
  1090  	sc := schemaGenContext{
  1091  		Path:                       schemaPath,
  1092  		Name:                       containerName,
  1093  		Receiver:                   receiverName,
  1094  		ValueExpr:                  receiverName,
  1095  		IndexVar:                   indexVar,
  1096  		Schema:                     *sch,
  1097  		Required:                   false,
  1098  		TypeResolver:               rslv,
  1099  		Named:                      false,
  1100  		IncludeModel:               true,
  1101  		IncludeValidator:           b.GenOpts.IncludeValidator,
  1102  		StrictAdditionalProperties: b.GenOpts.StrictAdditionalProperties,
  1103  		ExtraSchemas:               make(map[string]GenSchema),
  1104  		StructTags:                 b.GenOpts.StructTags,
  1105  	}
  1106  
  1107  	var (
  1108  		br *typeResolver
  1109  		bs *spec.Schema
  1110  	)
  1111  
  1112  	if sch.Ref.String() == "" {
  1113  		// backup the type resolver context
  1114  		// (not needed when the schema has a name)
  1115  		br, bs = b.saveResolveContext(rslv, sch)
  1116  	}
  1117  
  1118  	if err := sc.makeGenSchema(); err != nil {
  1119  		return GenSchema{}, err
  1120  	}
  1121  	for alias, pkg := range findImports(&sc.GenSchema) {
  1122  		b.Imports[alias] = pkg
  1123  	}
  1124  
  1125  	if sch.Ref.String() == "" && len(sc.ExtraSchemas) > 0 {
  1126  		newSchema, err := b.liftExtraSchemas(resolver, br, bs, &sc)
  1127  		if err != nil {
  1128  			return GenSchema{}, err
  1129  		}
  1130  		if newSchema != nil {
  1131  			schema = *newSchema
  1132  		}
  1133  	} else {
  1134  		schema = sc.GenSchema
  1135  	}
  1136  
  1137  	// new schemas will be in api pkg
  1138  	schemaPkg := b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "")
  1139  	schema.Pkg = schemaPkg
  1140  
  1141  	if schema.IsAnonymous {
  1142  		// a generated name for anonymous schema
  1143  		// TODO: support x-go-name
  1144  		hasProperties := len(schema.Properties) > 0
  1145  		isAllOf := len(schema.AllOf) > 0
  1146  		isInterface := schema.IsInterface
  1147  		hasValidations := schema.HasValidations
  1148  
  1149  		// for complex anonymous objects, produce an extra schema
  1150  		if hasProperties || isAllOf {
  1151  			if b.ExtraSchemas == nil {
  1152  				b.ExtraSchemas = make(map[string]GenSchema)
  1153  			}
  1154  			schema.Name = schemaName
  1155  			schema.GoType = schemaName
  1156  			schema.IsAnonymous = false
  1157  			b.ExtraSchemas[schemaName] = schema
  1158  
  1159  			// constructs new schema to refer to the newly created type
  1160  			schema = GenSchema{}
  1161  			schema.IsAnonymous = false
  1162  			schema.IsComplexObject = true
  1163  			schema.SwaggerType = schemaName
  1164  			schema.HasValidations = hasValidations
  1165  			schema.GoType = schemaName
  1166  			schema.Pkg = schemaPkg
  1167  		} else if isInterface {
  1168  			schema = GenSchema{}
  1169  			schema.IsAnonymous = false
  1170  			schema.IsComplexObject = false
  1171  			schema.IsInterface = true
  1172  			schema.HasValidations = false
  1173  			schema.GoType = iface
  1174  		}
  1175  	}
  1176  
  1177  	return schema, nil
  1178  }
  1179  
  1180  func intersectTags(left, right []string) []string {
  1181  	// dedupe
  1182  	uniqueTags := make(map[string]struct{}, maxInt(len(left), len(right)))
  1183  	for _, l := range left {
  1184  		if len(right) == 0 || swag.ContainsStrings(right, l) {
  1185  			uniqueTags[l] = struct{}{}
  1186  		}
  1187  	}
  1188  	filtered := make([]string, 0, len(uniqueTags))
  1189  	// stable output across generations, preserving original order
  1190  	for _, k := range left {
  1191  		if _, ok := uniqueTags[k]; !ok {
  1192  			continue
  1193  		}
  1194  		filtered = append(filtered, k)
  1195  		delete(uniqueTags, k)
  1196  	}
  1197  	return filtered
  1198  }
  1199  
  1200  // analyze tags for an operation
  1201  func (b *codeGenOpBuilder) analyzeTags() (string, []string, bool) {
  1202  	var (
  1203  		filter         []string
  1204  		tag            string
  1205  		hasTagOverride bool
  1206  	)
  1207  	if b.GenOpts != nil {
  1208  		filter = b.GenOpts.Tags
  1209  	}
  1210  	intersected := intersectTags(pruneEmpty(b.Operation.Tags), filter)
  1211  	if !b.GenOpts.SkipTagPackages && len(intersected) > 0 {
  1212  		// override generation with: x-go-operation-tag
  1213  		tag, hasTagOverride = b.Operation.Extensions.GetString(xGoOperationTag)
  1214  		if !hasTagOverride {
  1215  			// TODO(fred): this part should be delegated to some new TagsFor(operation) in go-openapi/analysis
  1216  			tag = intersected[0]
  1217  			gtags := b.Doc.Spec().Tags
  1218  			for _, gtag := range gtags {
  1219  				if gtag.Name != tag {
  1220  					continue
  1221  				}
  1222  				//  honor x-go-name in tag
  1223  				if name, hasGoName := gtag.Extensions.GetString(xGoName); hasGoName {
  1224  					tag = name
  1225  					break
  1226  				}
  1227  				//  honor x-go-operation-tag in tag
  1228  				if name, hasOpName := gtag.Extensions.GetString(xGoOperationTag); hasOpName {
  1229  					tag = name
  1230  					break
  1231  				}
  1232  			}
  1233  		}
  1234  	}
  1235  	if tag == b.APIPackage {
  1236  		// conflict with "operations" package is handled separately
  1237  		tag = renameOperationPackage(intersected, tag)
  1238  	}
  1239  	b.APIPackage = b.GenOpts.LanguageOpts.ManglePackageName(tag, b.APIPackage) // actual package name
  1240  	b.APIPackageAlias = deconflictTag(intersected, b.APIPackage)               // deconflicted import alias
  1241  	return tag, intersected, len(filter) == 0 || len(filter) > 0 && len(intersected) > 0
  1242  }
  1243  
  1244  func maxInt(a, b int) int {
  1245  	if a > b {
  1246  		return a
  1247  	}
  1248  	return b
  1249  }
  1250  
  1251  // deconflictTag ensures generated packages for operations based on tags do not conflict
  1252  // with other imports
  1253  func deconflictTag(seenTags []string, pkg string) string {
  1254  	return deconflictPkg(pkg, func(pkg string) string { return renameOperationPackage(seenTags, pkg) })
  1255  }
  1256  
  1257  // deconflictPrincipal ensures that whenever an external principal package is added, it doesn't conflict
  1258  // with standard imports
  1259  func deconflictPrincipal(pkg string) string {
  1260  	switch pkg {
  1261  	case "principal":
  1262  		return renamePrincipalPackage(pkg)
  1263  	default:
  1264  		return deconflictPkg(pkg, renamePrincipalPackage)
  1265  	}
  1266  }
  1267  
  1268  // deconflictPkg renames package names which conflict with standard imports
  1269  func deconflictPkg(pkg string, renamer func(string) string) string {
  1270  	switch pkg {
  1271  	// package conflict with variables
  1272  	case "api", "httptransport", "formats", "server":
  1273  		fallthrough
  1274  	// package conflict with go-openapi imports
  1275  	case "errors", "runtime", "middleware", "security", "spec", "strfmt", "loads", "swag", "validate":
  1276  		fallthrough
  1277  	// package conflict with stdlib/other lib imports
  1278  	case "tls", "http", "fmt", "strings", "log", "flags", "pflag", "json", "time":
  1279  		return renamer(pkg)
  1280  	}
  1281  	return pkg
  1282  }
  1283  
  1284  func renameOperationPackage(seenTags []string, pkg string) string {
  1285  	current := strings.ToLower(pkg) + "ops"
  1286  	if len(seenTags) == 0 {
  1287  		return current
  1288  	}
  1289  	for swag.ContainsStringsCI(seenTags, current) {
  1290  		current += "1"
  1291  	}
  1292  	return current
  1293  }
  1294  
  1295  func renamePrincipalPackage(_ string) string {
  1296  	// favors readability over perfect deconfliction
  1297  	return "auth"
  1298  }
  1299  
  1300  func renameServerPackage(pkg string) string {
  1301  	// favors readability over perfect deconfliction
  1302  	return "swagger" + pkg + "srv"
  1303  }
  1304  
  1305  func renameAPIPackage(pkg string) string {
  1306  	// favors readability over perfect deconfliction
  1307  	return "swagger" + pkg
  1308  }
  1309  
  1310  func renameImplementationPackage(pkg string) string {
  1311  	// favors readability over perfect deconfliction
  1312  	return "swagger" + pkg + "impl"
  1313  }