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 15package generator 16 17import ( 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 32type respSort struct { 33 Code int 34 Response spec.Response 35} 36 37type responses []respSort 38 39func (s responses) Len() int { return len(s) } 40func (s responses) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 41func (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 45func 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 60func 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 119type 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 151func (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 210type 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 240func 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. 275func 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 303func (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 516func 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 526func schemeOrDefault(schemes []string, defaultScheme string) []string { 527 if len(schemes) == 0 { 528 return []string{defaultScheme} 529 } 530 return schemes 531} 532 533func (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 582func (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 626func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, valueExpression string, items, parent *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 662func (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 668func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path, valueExpression, location string, resolver *typeResolver, items, parent *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 707func (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 param.In == "body" { 743 // Process parameters declared in body (i.e. have a Schema) 744 res.Required = param.Required 745 if err := b.MakeBodyParameter(&res, resolver, param.Schema); err != nil { 746 return GenParameter{}, err 747 } 748 } else { 749 // Process parameters declared in other inputs: path, query, header (SimpleSchema) 750 res.resolvedType = simpleResolvedType(param.Type, param.Format, param.Items, ¶m.CommonValidations) 751 res.sharedValidations = sharedValidations{ 752 Required: param.Required, 753 SchemaValidations: param.Validations(), 754 } 755 756 res.ZeroValue = res.resolvedType.Zero() 757 758 hasChildValidations := false 759 if param.Items != nil { 760 // Follow Items definition for array parameters 761 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) 762 if err != nil { 763 return GenParameter{}, err 764 } 765 res.Child = &pi 766 // Propagates HasValidations from from child array 767 hasChildValidations = pi.HasValidations 768 } 769 res.IsNullable = !param.Required && !param.AllowEmptyValue 770 res.HasValidations, res.HasSliceValidations = b.HasValidations(param.CommonValidations, res.resolvedType) 771 res.HasValidations = res.HasValidations || hasChildValidations 772 res.IsEnumCI = b.GenOpts.AllowEnumCI || hasEnumCI(param.Extensions) 773 } 774 775 // Select codegen strategy for body param validation 776 res.Converter = stringConverters[res.GoType] 777 res.Formatter = stringFormatters[res.GoType] 778 b.setBodyParamValidation(&res) 779 780 return res, nil 781} 782 783// MakeBodyParameter constructs a body parameter schema 784func (b *codeGenOpBuilder) MakeBodyParameter(res *GenParameter, resolver *typeResolver, sch *spec.Schema) error { 785 // resolve schema model 786 schema, ers := b.buildOperationSchema(res.Path, b.Operation.ID+"ParamsBody", swag.ToGoName(b.Operation.ID+" Body"), res.ReceiverName, res.IndexVar, sch, resolver) 787 if ers != nil { 788 return ers 789 } 790 res.Schema = &schema 791 res.Schema.Required = res.Required // Required in body is managed independently from validations 792 793 // build Child items for nested slices and maps 794 var items *GenItems 795 res.KeyVar = "k" 796 res.Schema.KeyVar = "k" 797 switch { 798 case schema.IsMap && !schema.IsInterface: 799 items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.AdditionalProperties) 800 case schema.IsArray: 801 items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.Items) 802 default: 803 items = new(GenItems) 804 } 805 806 // templates assume at least one .Child != nil 807 res.Child = items 808 schema.HasValidations = schema.HasValidations || items.HasValidations 809 810 res.resolvedType = schema.resolvedType 811 812 // simple and schema views share the same validations 813 res.sharedValidations = schema.sharedValidations 814 res.ZeroValue = schema.Zero() 815 return nil 816} 817 818// MakeBodyParameterItemsAndMaps clones the .Items schema structure (resp. .AdditionalProperties) as a .GenItems structure 819// for compatibility with simple param templates. 820// 821// Constructed children assume simple structures: any complex object is assumed to be resolved by a model or extra schema definition 822func (b *codeGenOpBuilder) MakeBodyParameterItemsAndMaps(res *GenParameter, it *GenSchema) *GenItems { 823 items := new(GenItems) 824 if it != nil { 825 var prev *GenItems 826 next := items 827 if res.Schema.IsArray { 828 next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.IndexVar + ")" 829 } else if res.Schema.IsMap { 830 next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.KeyVar + ")" 831 } 832 next.Name = res.Name + " " + res.Schema.IndexVar 833 next.IndexVar = res.Schema.IndexVar + "i" 834 next.KeyVar = res.Schema.KeyVar + "k" 835 next.ValueExpression = swag.ToVarName(res.Name + "I") 836 next.Location = "body" 837 for it != nil { 838 next.resolvedType = it.resolvedType 839 next.sharedValidations = it.sharedValidations 840 next.Formatter = stringFormatters[it.SwaggerFormat] 841 next.Converter = stringConverters[res.GoType] 842 next.Parent = prev 843 _, next.IsCustomFormatter = customFormatters[it.GoType] 844 next.IsCustomFormatter = next.IsCustomFormatter && !it.IsStream 845 846 // special instruction to avoid using CollectionFormat for body params 847 next.SkipParse = true 848 849 if prev != nil { 850 if prev.IsArray { 851 next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.IndexVar + ")" 852 } else if prev.IsMap { 853 next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.KeyVar + ")" 854 } 855 next.Name = prev.Name + prev.IndexVar 856 next.IndexVar = prev.IndexVar + "i" 857 next.KeyVar = prev.KeyVar + "k" 858 next.ValueExpression = swag.ToVarName(prev.ValueExpression + "I") 859 prev.Child = next 860 } 861 862 // found a complex or aliased thing 863 // hide details from the aliased type and stop recursing 864 if next.IsAliased || next.IsComplexObject { 865 next.IsArray = false 866 next.IsMap = false 867 next.IsCustomFormatter = false 868 next.IsComplexObject = true 869 next.IsAliased = true 870 break 871 } 872 if next.IsInterface || next.IsStream || next.IsBase64 { 873 next.HasValidations = false 874 } 875 next.NeedsIndex = next.HasValidations || next.Converter != "" || (next.IsCustomFormatter && !next.SkipParse) 876 prev = next 877 next = new(GenItems) 878 879 switch { 880 case it.Items != nil: 881 it = it.Items 882 case it.AdditionalProperties != nil: 883 it = it.AdditionalProperties 884 default: 885 it = nil 886 } 887 } 888 // propagate HasValidations 889 var propag func(child *GenItems) (bool, bool) 890 propag = func(child *GenItems) (bool, bool) { 891 if child == nil { 892 return false, false 893 } 894 cValidations, cIndex := propag(child.Child) 895 child.HasValidations = child.HasValidations || cValidations 896 child.NeedsIndex = child.HasValidations || child.Converter != "" || (child.IsCustomFormatter && !child.SkipParse) || cIndex 897 return child.HasValidations, child.NeedsIndex 898 } 899 items.HasValidations, items.NeedsIndex = propag(items) 900 901 // resolve nullability conflicts when declaring body as a map of array of an anonymous complex object 902 // (e.g. refer to an extra schema type, which is nullable, but not rendered as a pointer in arrays or maps) 903 // Rule: outer type rules (with IsMapNullOverride), inner types are fixed 904 var fixNullable func(child *GenItems) string 905 fixNullable = func(child *GenItems) string { 906 if !child.IsArray && !child.IsMap { 907 if child.IsComplexObject { 908 return child.GoType 909 } 910 return "" 911 } 912 if innerType := fixNullable(child.Child); innerType != "" { 913 if child.IsMapNullOverride && child.IsArray { 914 child.GoType = "[]" + innerType 915 return child.GoType 916 } 917 } 918 return "" 919 } 920 fixNullable(items) 921 } 922 return items 923} 924 925func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) { 926 // Determine validation strategy for body param. 927 // 928 // Here are the distinct strategies: 929 // - the body parameter is a model object => delegates 930 // - the body parameter is an array of model objects => carry on slice validations, then iterate and delegate 931 // - the body parameter is a map of model objects => iterate and delegate 932 // - the body parameter is an array of simple objects (including maps) 933 // - the body parameter is a map of simple objects (including arrays) 934 if p.IsBodyParam() { 935 var hasSimpleBodyParams, hasSimpleBodyItems, hasSimpleBodyMap, hasModelBodyParams, hasModelBodyItems, hasModelBodyMap bool 936 s := p.Schema 937 if s != nil { 938 doNot := s.IsInterface || s.IsStream || s.IsBase64 939 // composition of primitive fields must be properly identified: hack this through 940 _, isPrimitive := primitives[s.GoType] 941 _, isFormatter := customFormatters[s.GoType] 942 isComposedPrimitive := s.IsPrimitive && !(isPrimitive || isFormatter) 943 944 hasSimpleBodyParams = !s.IsComplexObject && !s.IsAliased && !isComposedPrimitive && !doNot 945 hasModelBodyParams = (s.IsComplexObject || s.IsAliased || isComposedPrimitive) && !doNot 946 947 if s.IsArray && s.Items != nil { 948 it := s.Items 949 doNot = it.IsInterface || it.IsStream || it.IsBase64 950 hasSimpleBodyItems = !it.IsComplexObject && !(it.IsAliased || doNot) 951 hasModelBodyItems = (it.IsComplexObject || it.IsAliased) && !doNot 952 } 953 if s.IsMap && s.AdditionalProperties != nil { 954 it := s.AdditionalProperties 955 hasSimpleBodyMap = !it.IsComplexObject && !(it.IsAliased || doNot) 956 hasModelBodyMap = !hasSimpleBodyMap && !doNot 957 } 958 } 959 // set validation strategy for body param 960 p.HasSimpleBodyParams = hasSimpleBodyParams 961 p.HasSimpleBodyItems = hasSimpleBodyItems 962 p.HasModelBodyParams = hasModelBodyParams 963 p.HasModelBodyItems = hasModelBodyItems 964 p.HasModelBodyMap = hasModelBodyMap 965 p.HasSimpleBodyMap = hasSimpleBodyMap 966 } 967 968} 969 970// makeSecuritySchemes produces a sorted list of security schemes for this operation 971func (b *codeGenOpBuilder) makeSecuritySchemes(receiver string) GenSecuritySchemes { 972 return gatherSecuritySchemes(b.SecurityDefinitions, b.Name, b.Principal, receiver, b.GenOpts.PrincipalIsNullable()) 973} 974 975// makeSecurityRequirements produces a sorted list of security requirements for this operation. 976// As for current, these requirements are not used by codegen (sec. requirement is determined at runtime). 977// We keep the order of the slice from the original spec, but sort the inner slice which comes from a map, 978// as well as the map of scopes. 979func (b *codeGenOpBuilder) makeSecurityRequirements(receiver string) []GenSecurityRequirements { 980 if b.Security == nil { 981 // nil (default requirement) is different than [] (no requirement) 982 return nil 983 } 984 985 securityRequirements := make([]GenSecurityRequirements, 0, len(b.Security)) 986 for _, req := range b.Security { 987 jointReq := make(GenSecurityRequirements, 0, len(req)) 988 for _, j := range req { 989 scopes := j.Scopes 990 sort.Strings(scopes) 991 jointReq = append(jointReq, GenSecurityRequirement{ 992 Name: j.Name, 993 Scopes: scopes, 994 }) 995 } 996 // sort joint requirements (come from a map in spec) 997 sort.Sort(jointReq) 998 securityRequirements = append(securityRequirements, jointReq) 999 } 1000 return securityRequirements 1001} 1002 1003// cloneSchema returns a deep copy of a schema 1004func (b *codeGenOpBuilder) cloneSchema(schema *spec.Schema) *spec.Schema { 1005 savedSchema := &spec.Schema{} 1006 schemaRep, _ := json.Marshal(schema) 1007 _ = json.Unmarshal(schemaRep, savedSchema) 1008 return savedSchema 1009} 1010 1011// saveResolveContext keeps a copy of known definitions and schema to properly roll back on a makeGenSchema() call 1012// This uses a deep clone the spec document to construct a type resolver which knows about definitions when the making of this operation started, 1013// and only these definitions. We are not interested in the "original spec", but in the already transformed spec. 1014func (b *codeGenOpBuilder) saveResolveContext(resolver *typeResolver, schema *spec.Schema) (*typeResolver, *spec.Schema) { 1015 if b.PristineDoc == nil { 1016 b.PristineDoc = b.Doc.Pristine() 1017 } 1018 rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.PristineDoc) 1019 1020 return rslv, b.cloneSchema(schema) 1021} 1022 1023// liftExtraSchemas constructs the schema for an anonymous construct with some ExtraSchemas. 1024// 1025// When some ExtraSchemas are produced from something else than a definition, 1026// this indicates we are not running in fully flattened mode and we need to render 1027// these ExtraSchemas in the operation's package. 1028// We need to rebuild the schema with a new type resolver to reflect this change in the 1029// models package. 1030func (b *codeGenOpBuilder) liftExtraSchemas(resolver, rslv *typeResolver, bs *spec.Schema, sc *schemaGenContext) (schema *GenSchema, err error) { 1031 // restore resolving state before previous call to makeGenSchema() 1032 sc.Schema = *bs 1033 1034 pg := sc.shallowClone() 1035 pkg := b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, defaultModelsTarget) 1036 1037 // make a resolver for current package (i.e. operations) 1038 pg.TypeResolver = newTypeResolver("", b.DefaultImports[b.APIPackage], rslv.Doc).withKeepDefinitionsPackage(pkg) 1039 pg.ExtraSchemas = make(map[string]GenSchema, len(sc.ExtraSchemas)) 1040 pg.UseContainerInName = true 1041 1042 // rebuild schema within local package 1043 if err = pg.makeGenSchema(); err != nil { 1044 return 1045 } 1046 1047 // lift nested extra schemas (inlined types) 1048 if b.ExtraSchemas == nil { 1049 b.ExtraSchemas = make(map[string]GenSchema, len(pg.ExtraSchemas)) 1050 } 1051 for _, v := range pg.ExtraSchemas { 1052 vv := v 1053 if !v.IsStream { 1054 b.ExtraSchemas[vv.Name] = vv 1055 } 1056 } 1057 schema = &pg.GenSchema 1058 return 1059} 1060 1061// buildOperationSchema constructs a schema for an operation (for body params or responses). 1062// It determines if the schema is readily available from the models package, 1063// or if a schema has to be generated in the operations package (i.e. is anonymous). 1064// Whenever an anonymous schema needs some extra schemas, we also determine if these extras are 1065// available from models or must be generated alongside the schema in the operations package. 1066// 1067// Duplicate extra schemas are pruned later on, when operations grouping in packages (e.g. from tags) takes place. 1068func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schemaName, receiverName, indexVar string, sch *spec.Schema, resolver *typeResolver) (GenSchema, error) { 1069 var schema GenSchema 1070 1071 if sch == nil { 1072 sch = &spec.Schema{} 1073 } 1074 shallowClonedResolver := *resolver 1075 shallowClonedResolver.ModelsFullPkg = b.DefaultImports[b.ModelsPackage] 1076 rslv := &shallowClonedResolver 1077 1078 sc := schemaGenContext{ 1079 Path: schemaPath, 1080 Name: containerName, 1081 Receiver: receiverName, 1082 ValueExpr: receiverName, 1083 IndexVar: indexVar, 1084 Schema: *sch, 1085 Required: false, 1086 TypeResolver: rslv, 1087 Named: false, 1088 IncludeModel: true, 1089 IncludeValidator: b.GenOpts.IncludeValidator, 1090 StrictAdditionalProperties: b.GenOpts.StrictAdditionalProperties, 1091 ExtraSchemas: make(map[string]GenSchema), 1092 StructTags: b.GenOpts.StructTags, 1093 } 1094 1095 var ( 1096 br *typeResolver 1097 bs *spec.Schema 1098 ) 1099 1100 if sch.Ref.String() == "" { 1101 // backup the type resolver context 1102 // (not needed when the schema has a name) 1103 br, bs = b.saveResolveContext(rslv, sch) 1104 } 1105 1106 if err := sc.makeGenSchema(); err != nil { 1107 return GenSchema{}, err 1108 } 1109 for alias, pkg := range findImports(&sc.GenSchema) { 1110 b.Imports[alias] = pkg 1111 } 1112 1113 if sch.Ref.String() == "" && len(sc.ExtraSchemas) > 0 { 1114 newSchema, err := b.liftExtraSchemas(resolver, br, bs, &sc) 1115 if err != nil { 1116 return GenSchema{}, err 1117 } 1118 if newSchema != nil { 1119 schema = *newSchema 1120 } 1121 } else { 1122 schema = sc.GenSchema 1123 } 1124 1125 if schema.IsAnonymous { 1126 // a generated name for anonymous schema 1127 // TODO: support x-go-name 1128 hasProperties := len(schema.Properties) > 0 1129 isAllOf := len(schema.AllOf) > 0 1130 isInterface := schema.IsInterface 1131 hasValidations := schema.HasValidations 1132 1133 // TODO: remove this and find a better way to get package name for anonymous models 1134 // get the package that the param will be generated. Used by generate CLI 1135 pkg := "operations" 1136 if len(b.Operation.Tags) != 0 { 1137 pkg = b.Operation.Tags[0] 1138 } 1139 1140 // for complex anonymous objects, produce an extra schema 1141 if hasProperties || isAllOf { 1142 if b.ExtraSchemas == nil { 1143 b.ExtraSchemas = make(map[string]GenSchema) 1144 } 1145 schema.Name = schemaName 1146 schema.GoType = schemaName 1147 schema.IsAnonymous = false 1148 schema.Pkg = pkg 1149 b.ExtraSchemas[schemaName] = schema 1150 1151 // constructs new schema to refer to the newly created type 1152 schema = GenSchema{} 1153 schema.IsAnonymous = false 1154 schema.IsComplexObject = true 1155 schema.SwaggerType = schemaName 1156 schema.HasValidations = hasValidations 1157 schema.GoType = schemaName 1158 schema.Pkg = pkg 1159 } else if isInterface { 1160 schema = GenSchema{} 1161 schema.IsAnonymous = false 1162 schema.IsComplexObject = false 1163 schema.IsInterface = true 1164 schema.HasValidations = false 1165 schema.GoType = iface 1166 } 1167 } 1168 return schema, nil 1169} 1170 1171func intersectTags(left, right []string) []string { 1172 // dedupe 1173 uniqueTags := make(map[string]struct{}, maxInt(len(left), len(right))) 1174 for _, l := range left { 1175 if len(right) == 0 || swag.ContainsStrings(right, l) { 1176 uniqueTags[l] = struct{}{} 1177 } 1178 } 1179 filtered := make([]string, 0, len(uniqueTags)) 1180 // stable output across generations, preserving original order 1181 for _, k := range left { 1182 if _, ok := uniqueTags[k]; !ok { 1183 continue 1184 } 1185 filtered = append(filtered, k) 1186 delete(uniqueTags, k) 1187 } 1188 return filtered 1189} 1190 1191// analyze tags for an operation 1192func (b *codeGenOpBuilder) analyzeTags() (string, []string, bool) { 1193 var ( 1194 filter []string 1195 tag string 1196 hasTagOverride bool 1197 ) 1198 if b.GenOpts != nil { 1199 filter = b.GenOpts.Tags 1200 } 1201 intersected := intersectTags(pruneEmpty(b.Operation.Tags), filter) 1202 if !b.GenOpts.SkipTagPackages && len(intersected) > 0 { 1203 // override generation with: x-go-operation-tag 1204 tag, hasTagOverride = b.Operation.Extensions.GetString(xGoOperationTag) 1205 if !hasTagOverride { 1206 // TODO(fred): this part should be delegated to some new TagsFor(operation) in go-openapi/analysis 1207 tag = intersected[0] 1208 gtags := b.Doc.Spec().Tags 1209 for _, gtag := range gtags { 1210 if gtag.Name != tag { 1211 continue 1212 } 1213 // honor x-go-name in tag 1214 if name, hasGoName := gtag.Extensions.GetString(xGoName); hasGoName { 1215 tag = name 1216 break 1217 } 1218 // honor x-go-operation-tag in tag 1219 if name, hasOpName := gtag.Extensions.GetString(xGoOperationTag); hasOpName { 1220 tag = name 1221 break 1222 } 1223 } 1224 } 1225 } 1226 if tag == b.APIPackage { 1227 // conflict with "operations" package is handled separately 1228 tag = renameOperationPackage(intersected, tag) 1229 } 1230 b.APIPackage = b.GenOpts.LanguageOpts.ManglePackageName(tag, b.APIPackage) // actual package name 1231 b.APIPackageAlias = deconflictTag(intersected, b.APIPackage) // deconflicted import alias 1232 return tag, intersected, len(filter) == 0 || len(filter) > 0 && len(intersected) > 0 1233} 1234 1235func maxInt(a, b int) int { 1236 if a > b { 1237 return a 1238 } 1239 return b 1240} 1241 1242// deconflictTag ensures generated packages for operations based on tags do not conflict 1243// with other imports 1244func deconflictTag(seenTags []string, pkg string) string { 1245 return deconflictPkg(pkg, func(pkg string) string { return renameOperationPackage(seenTags, pkg) }) 1246} 1247 1248// deconflictPrincipal ensures that whenever an external principal package is added, it doesn't conflict 1249// with standard inports 1250func deconflictPrincipal(pkg string) string { 1251 switch pkg { 1252 case "principal": 1253 return renamePrincipalPackage(pkg) 1254 default: 1255 return deconflictPkg(pkg, renamePrincipalPackage) 1256 } 1257} 1258 1259// deconflictPkg renames package names which conflict with standard imports 1260func deconflictPkg(pkg string, renamer func(string) string) string { 1261 switch pkg { 1262 // package conflict with variables 1263 case "api", "httptransport", "formats", "server": 1264 fallthrough 1265 // package conflict with go-openapi imports 1266 case "errors", "runtime", "middleware", "security", "spec", "strfmt", "loads", "swag", "validate": 1267 fallthrough 1268 // package conflict with stdlib/other lib imports 1269 case "tls", "http", "fmt", "strings", "log", "flags", "pflag", "json", "time": 1270 return renamer(pkg) 1271 } 1272 return pkg 1273} 1274 1275func renameOperationPackage(seenTags []string, pkg string) string { 1276 current := strings.ToLower(pkg) + "ops" 1277 if len(seenTags) == 0 { 1278 return current 1279 } 1280 for swag.ContainsStringsCI(seenTags, current) { 1281 current += "1" 1282 } 1283 return current 1284} 1285 1286func renamePrincipalPackage(pkg string) string { 1287 // favors readability over perfect deconfliction 1288 return "auth" 1289} 1290 1291func renameServerPackage(pkg string) string { 1292 // favors readability over perfect deconfliction 1293 return "swagger" + pkg + "srv" 1294} 1295 1296func renameAPIPackage(pkg string) string { 1297 // favors readability over perfect deconfliction 1298 return "swagger" + pkg 1299} 1300 1301func renameImplementationPackage(pkg string) string { 1302 // favors readability over perfect deconfliction 1303 return "swagger" + pkg + "impl" 1304} 1305