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