github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/generator/operation.go (about) 1 // Copyright 2015 go-swagger maintainers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package generator 16 17 import ( 18 "encoding/json" 19 "errors" 20 "fmt" 21 "path/filepath" 22 "sort" 23 "strings" 24 25 "github.com/go-openapi/analysis" 26 "github.com/go-openapi/loads" 27 "github.com/go-openapi/runtime" 28 "github.com/go-openapi/spec" 29 "github.com/go-openapi/swag" 30 ) 31 32 type respSort struct { 33 Code int 34 Response spec.Response 35 } 36 37 type responses []respSort 38 39 func (s responses) Len() int { return len(s) } 40 func (s responses) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 41 func (s responses) Less(i, j int) bool { return s[i].Code < s[j].Code } 42 43 // sortedResponses produces a sorted list of responses. 44 // TODO: this is redundant with the definition given in struct.go 45 func sortedResponses(input map[int]spec.Response) responses { 46 var res responses 47 for k, v := range input { 48 if k > 0 { 49 res = append(res, respSort{k, v}) 50 } 51 } 52 sort.Sort(res) 53 return res 54 } 55 56 // GenerateServerOperation generates a parameter model, parameter validator, http handler implementations for a given operation. 57 // 58 // It also generates an operation handler interface that uses the parameter model for handling a valid request. 59 // Allows for specifying a list of tags to include only certain tags for the generation 60 func GenerateServerOperation(operationNames []string, opts *GenOpts) error { 61 if err := opts.CheckOpts(); err != nil { 62 return err 63 } 64 65 if err := opts.setTemplates(); err != nil { 66 return err 67 } 68 69 specDoc, analyzed, err := opts.analyzeSpec() 70 if err != nil { 71 return err 72 } 73 74 ops := gatherOperations(analyzed, operationNames) 75 76 if len(ops) == 0 { 77 return errors.New("no operations were selected") 78 } 79 80 for operationName, opRef := range ops { 81 method, path, operation := opRef.Method, opRef.Path, opRef.Op 82 83 serverPackage := opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget) 84 generator := operationGenerator{ 85 Name: operationName, 86 Method: method, 87 Path: path, 88 BasePath: specDoc.BasePath(), 89 APIPackage: opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget), 90 ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget), 91 ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget), 92 ServerPackage: serverPackage, 93 Operation: *operation, 94 SecurityRequirements: analyzed.SecurityRequirementsFor(operation), 95 SecurityDefinitions: analyzed.SecurityDefinitionsFor(operation), 96 Principal: opts.PrincipalAlias(), 97 Target: filepath.Join(opts.Target, filepath.FromSlash(serverPackage)), 98 Base: opts.Target, 99 Tags: opts.Tags, 100 IncludeHandler: opts.IncludeHandler, 101 IncludeParameters: opts.IncludeParameters, 102 IncludeResponses: opts.IncludeResponses, 103 IncludeValidator: opts.IncludeValidator, 104 DumpData: opts.DumpData, 105 DefaultScheme: opts.DefaultScheme, 106 DefaultProduces: opts.DefaultProduces, 107 DefaultConsumes: opts.DefaultConsumes, 108 Doc: specDoc, 109 Analyzed: analyzed, 110 GenOpts: opts, 111 } 112 if err := generator.Generate(); err != nil { 113 return err 114 } 115 } 116 return nil 117 } 118 119 type operationGenerator struct { 120 Authorized bool 121 IncludeHandler bool 122 IncludeParameters bool 123 IncludeResponses bool 124 IncludeValidator bool 125 DumpData bool 126 127 Principal string 128 Target string 129 Base string 130 Name string 131 Method string 132 Path string 133 BasePath string 134 APIPackage string 135 ModelsPackage string 136 ServerPackage string 137 ClientPackage string 138 Operation spec.Operation 139 SecurityRequirements [][]analysis.SecurityRequirement 140 SecurityDefinitions map[string]spec.SecurityScheme 141 Tags []string 142 DefaultScheme string 143 DefaultProduces string 144 DefaultConsumes string 145 Doc *loads.Document 146 Analyzed *analysis.Spec 147 GenOpts *GenOpts 148 } 149 150 // Generate a single operation 151 func (o *operationGenerator) Generate() error { 152 153 defaultImports := o.GenOpts.defaultImports() 154 155 apiPackage := o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.APIPackage, defaultOperationsTarget) 156 imports := o.GenOpts.initImports( 157 filepath.Join(o.GenOpts.LanguageOpts.ManglePackagePath(o.GenOpts.ServerPackage, defaultServerTarget), apiPackage)) 158 159 bldr := codeGenOpBuilder{ 160 ModelsPackage: o.ModelsPackage, 161 Principal: o.GenOpts.PrincipalAlias(), 162 Target: o.Target, 163 DefaultImports: defaultImports, 164 Imports: imports, 165 DefaultScheme: o.DefaultScheme, 166 Doc: o.Doc, 167 Analyzed: o.Analyzed, 168 BasePath: o.BasePath, 169 GenOpts: o.GenOpts, 170 Name: o.Name, 171 Operation: o.Operation, 172 Method: o.Method, 173 Path: o.Path, 174 IncludeValidator: o.IncludeValidator, 175 APIPackage: o.APIPackage, // defaults to main operations package 176 DefaultProduces: o.DefaultProduces, 177 DefaultConsumes: o.DefaultConsumes, 178 Authed: len(o.Analyzed.SecurityRequirementsFor(&o.Operation)) > 0, 179 Security: o.Analyzed.SecurityRequirementsFor(&o.Operation), 180 SecurityDefinitions: o.Analyzed.SecurityDefinitionsFor(&o.Operation), 181 RootAPIPackage: o.GenOpts.LanguageOpts.ManglePackageName(o.ServerPackage, defaultServerTarget), 182 } 183 184 _, tags, _ := bldr.analyzeTags() 185 186 op, err := bldr.MakeOperation() 187 if err != nil { 188 return err 189 } 190 191 op.Tags = tags 192 operations := make(GenOperations, 0, 1) 193 operations = append(operations, op) 194 sort.Sort(operations) 195 196 for _, pp := range operations { 197 op := pp 198 if o.GenOpts.DumpData { 199 _ = dumpData(swag.ToDynamicJSON(op)) 200 continue 201 } 202 if err := o.GenOpts.renderOperation(&op); err != nil { 203 return err 204 } 205 } 206 207 return nil 208 } 209 210 type codeGenOpBuilder struct { 211 Authed bool 212 IncludeValidator bool 213 214 Name string 215 Method string 216 Path string 217 BasePath string 218 APIPackage string 219 APIPackageAlias string 220 RootAPIPackage string 221 ModelsPackage string 222 Principal string 223 Target string 224 Operation spec.Operation 225 Doc *loads.Document 226 PristineDoc *loads.Document 227 Analyzed *analysis.Spec 228 DefaultImports map[string]string 229 Imports map[string]string 230 DefaultScheme string 231 DefaultProduces string 232 DefaultConsumes string 233 Security [][]analysis.SecurityRequirement 234 SecurityDefinitions map[string]spec.SecurityScheme 235 ExtraSchemas map[string]GenSchema 236 GenOpts *GenOpts 237 } 238 239 // paramMappings yields a map of safe parameter names for an operation 240 func paramMappings(params map[string]spec.Parameter) (map[string]map[string]string, string) { 241 idMapping := map[string]map[string]string{ 242 "query": make(map[string]string, len(params)), 243 "path": make(map[string]string, len(params)), 244 "formData": make(map[string]string, len(params)), 245 "header": make(map[string]string, len(params)), 246 "body": make(map[string]string, len(params)), 247 } 248 249 // In order to avoid unstable generation, adopt same naming convention 250 // for all parameters with same name across locations. 251 seenIds := make(map[string]interface{}, len(params)) 252 for id, p := range params { 253 if val, ok := seenIds[p.Name]; ok { 254 previous := val.(struct{ id, in string }) 255 idMapping[p.In][p.Name] = swag.ToGoName(id) 256 // rewrite the previously found one 257 idMapping[previous.in][p.Name] = swag.ToGoName(previous.id) 258 } else { 259 idMapping[p.In][p.Name] = swag.ToGoName(p.Name) 260 } 261 seenIds[strings.ToLower(idMapping[p.In][p.Name])] = struct{ id, in string }{id: id, in: p.In} 262 } 263 264 // pick a deconflicted private name for timeout for this operation 265 timeoutName := renameTimeout(seenIds, "timeout") 266 267 return idMapping, timeoutName 268 } 269 270 // renameTimeout renames the variable in use by client template to avoid conflicting 271 // with param names. 272 // 273 // NOTE: this merely protects the timeout field in the client parameter struct, 274 // fields "Context" and "HTTPClient" remain exposed to name conflicts. 275 func renameTimeout(seenIds map[string]interface{}, timeoutName string) string { 276 if seenIds == nil { 277 return timeoutName 278 } 279 current := strings.ToLower(timeoutName) 280 if _, ok := seenIds[current]; !ok { 281 return timeoutName 282 } 283 var next string 284 switch current { 285 case "timeout": 286 next = "requestTimeout" 287 case "requesttimeout": 288 next = "httpRequestTimeout" 289 case "httprequesttimeout": 290 next = "swaggerTimeout" 291 case "swaggertimeout": 292 next = "operationTimeout" 293 case "operationtimeout": 294 next = "opTimeout" 295 case "optimeout": 296 next = "operTimeout" 297 default: 298 next = timeoutName + "1" 299 } 300 return renameTimeout(seenIds, next) 301 } 302 303 func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) { 304 debugLog("[%s %s] parsing operation (id: %q)", b.Method, b.Path, b.Operation.ID) 305 // NOTE: we assume flatten is enabled by default (i.e. complex constructs are resolved from the models package), 306 // but do not assume the spec is necessarily fully flattened (i.e. all schemas moved to definitions). 307 // 308 // Fully flattened means that all complex constructs are present as 309 // definitions and models produced accordingly in ModelsPackage, 310 // whereas minimal flatten simply ensures that there are no weird $ref's in the spec. 311 // 312 // When some complex anonymous constructs are specified, extra schemas are produced in the operations package. 313 // 314 // In all cases, resetting definitions to the _original_ (untransformed) spec is not an option: 315 // we take from there the spec possibly already transformed by the GenDefinitions stage. 316 resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, defaultModelsTarget), b.DefaultImports[b.ModelsPackage], b.Doc) 317 receiver := "o" 318 319 operation := b.Operation 320 var params, qp, pp, hp, fp GenParameters 321 var hasQueryParams, hasPathParams, hasHeaderParams, hasFormParams, hasFileParams, hasFormValueParams, hasBodyParams bool 322 paramsForOperation := b.Analyzed.ParamsFor(b.Method, b.Path) 323 324 idMapping, timeoutName := paramMappings(paramsForOperation) 325 326 for _, p := range paramsForOperation { 327 cp, err := b.MakeParameter(receiver, resolver, p, idMapping) 328 329 if err != nil { 330 return GenOperation{}, err 331 } 332 if cp.IsQueryParam() { 333 hasQueryParams = true 334 qp = append(qp, cp) 335 } 336 if cp.IsFormParam() { 337 if p.Type == file { 338 hasFileParams = true 339 } 340 if p.Type != file { 341 hasFormValueParams = true 342 } 343 hasFormParams = true 344 fp = append(fp, cp) 345 } 346 if cp.IsPathParam() { 347 hasPathParams = true 348 pp = append(pp, cp) 349 } 350 if cp.IsHeaderParam() { 351 hasHeaderParams = true 352 hp = append(hp, cp) 353 } 354 if cp.IsBodyParam() { 355 hasBodyParams = true 356 } 357 params = append(params, cp) 358 } 359 sort.Sort(params) 360 sort.Sort(qp) 361 sort.Sort(pp) 362 sort.Sort(hp) 363 sort.Sort(fp) 364 365 var srs responses 366 if operation.Responses != nil { 367 srs = sortedResponses(operation.Responses.StatusCodeResponses) 368 } 369 responses := make([]GenResponse, 0, len(srs)) 370 var defaultResponse *GenResponse 371 var successResponses []GenResponse 372 if operation.Responses != nil { 373 for _, v := range srs { 374 name, ok := v.Response.Extensions.GetString(xGoName) 375 if !ok { 376 // look for name of well-known codes 377 name = runtime.Statuses[v.Code] 378 if name == "" { 379 // non-standard codes deserve some name 380 name = fmt.Sprintf("Status %d", v.Code) 381 } 382 } 383 name = swag.ToJSONName(b.Name + " " + name) 384 isSuccess := v.Code/100 == 2 385 gr, err := b.MakeResponse(receiver, name, isSuccess, resolver, v.Code, v.Response) 386 if err != nil { 387 return GenOperation{}, err 388 } 389 if isSuccess { 390 successResponses = append(successResponses, gr) 391 } 392 responses = append(responses, gr) 393 } 394 395 if operation.Responses.Default != nil { 396 gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, *operation.Responses.Default) 397 if err != nil { 398 return GenOperation{}, err 399 } 400 defaultResponse = &gr 401 } 402 } 403 404 // Always render a default response, even when no responses were defined 405 if operation.Responses == nil || (operation.Responses.Default == nil && len(srs) == 0) { 406 gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, spec.Response{}) 407 if err != nil { 408 return GenOperation{}, err 409 } 410 defaultResponse = &gr 411 } 412 413 swsp := resolver.Doc.Spec() 414 415 schemes, extraSchemes := gatherURISchemes(swsp, operation) 416 originalSchemes := operation.Schemes 417 originalExtraSchemes := getExtraSchemes(operation.Extensions) 418 419 produces := producesOrDefault(operation.Produces, swsp.Produces, b.DefaultProduces) 420 sort.Strings(produces) 421 422 consumes := producesOrDefault(operation.Consumes, swsp.Consumes, b.DefaultConsumes) 423 sort.Strings(consumes) 424 425 var successResponse *GenResponse 426 for _, resp := range successResponses { 427 sr := resp 428 if sr.IsSuccess { 429 successResponse = &sr 430 break 431 } 432 } 433 434 var hasStreamingResponse bool 435 if defaultResponse != nil && defaultResponse.Schema != nil && defaultResponse.Schema.IsStream { 436 hasStreamingResponse = true 437 } 438 439 if !hasStreamingResponse { 440 for _, sr := range successResponses { 441 if !hasStreamingResponse && sr.Schema != nil && sr.Schema.IsStream { 442 hasStreamingResponse = true 443 break 444 } 445 } 446 } 447 448 if !hasStreamingResponse { 449 for _, r := range responses { 450 if r.Schema != nil && r.Schema.IsStream { 451 hasStreamingResponse = true 452 break 453 } 454 } 455 } 456 457 return GenOperation{ 458 GenCommon: GenCommon{ 459 Copyright: b.GenOpts.Copyright, 460 TargetImportPath: b.GenOpts.LanguageOpts.baseImport(b.GenOpts.Target), 461 }, 462 Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget), 463 PackageAlias: b.APIPackageAlias, 464 RootPackage: b.RootAPIPackage, 465 Name: b.Name, 466 Method: b.Method, 467 Path: b.Path, 468 BasePath: b.BasePath, 469 Tags: operation.Tags, 470 UseTags: len(operation.Tags) > 0 && !b.GenOpts.SkipTagPackages, 471 Description: trimBOM(operation.Description), 472 ReceiverName: receiver, 473 DefaultImports: b.DefaultImports, 474 Imports: b.Imports, 475 Params: params, 476 Summary: trimBOM(operation.Summary), 477 QueryParams: qp, 478 PathParams: pp, 479 HeaderParams: hp, 480 FormParams: fp, 481 HasQueryParams: hasQueryParams, 482 HasPathParams: hasPathParams, 483 HasHeaderParams: hasHeaderParams, 484 HasFormParams: hasFormParams, 485 HasFormValueParams: hasFormValueParams, 486 HasFileParams: hasFileParams, 487 HasBodyParams: hasBodyParams, 488 HasStreamingResponse: hasStreamingResponse, 489 Authorized: b.Authed, 490 Security: b.makeSecurityRequirements(receiver), // resolved security requirements, for codegen 491 SecurityDefinitions: b.makeSecuritySchemes(receiver), 492 SecurityRequirements: securityRequirements(operation.Security), // raw security requirements, for doc 493 Principal: b.Principal, 494 Responses: responses, 495 DefaultResponse: defaultResponse, 496 SuccessResponse: successResponse, 497 SuccessResponses: successResponses, 498 ExtraSchemas: gatherExtraSchemas(b.ExtraSchemas), 499 Schemes: schemeOrDefault(schemes, b.DefaultScheme), 500 SchemeOverrides: originalSchemes, // raw operation schemes, for doc 501 ProducesMediaTypes: produces, // resolved produces, for codegen 502 ConsumesMediaTypes: consumes, // resolved consumes, for codegen 503 Produces: operation.Produces, // for doc 504 Consumes: operation.Consumes, // for doc 505 ExtraSchemes: extraSchemes, // resolved schemes, for codegen 506 ExtraSchemeOverrides: originalExtraSchemes, // raw operation extra schemes, for doc 507 TimeoutName: timeoutName, 508 Extensions: operation.Extensions, 509 StrictResponders: b.GenOpts.StrictResponders, 510 511 PrincipalIsNullable: b.GenOpts.PrincipalIsNullable(), 512 ExternalDocs: trimExternalDoc(operation.ExternalDocs), 513 }, nil 514 } 515 516 func producesOrDefault(produces []string, fallback []string, defaultProduces string) []string { 517 if len(produces) > 0 { 518 return produces 519 } 520 if len(fallback) > 0 { 521 return fallback 522 } 523 return []string{defaultProduces} 524 } 525 526 func schemeOrDefault(schemes []string, defaultScheme string) []string { 527 if len(schemes) == 0 { 528 return []string{defaultScheme} 529 } 530 return schemes 531 } 532 533 func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, resolver *typeResolver, code int, resp spec.Response) (GenResponse, error) { 534 debugLog("[%s %s] making id %q", b.Method, b.Path, b.Operation.ID) 535 536 // assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema) 537 examples := make(GenResponseExamples, 0, len(resp.Examples)) 538 for k, v := range resp.Examples { 539 examples = append(examples, GenResponseExample{MediaType: k, Example: v}) 540 } 541 sort.Sort(examples) 542 543 res := GenResponse{ 544 Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget), 545 ModelsPackage: b.ModelsPackage, 546 ReceiverName: receiver, 547 Name: name, 548 Description: trimBOM(resp.Description), 549 DefaultImports: b.DefaultImports, 550 Imports: b.Imports, 551 IsSuccess: isSuccess, 552 Code: code, 553 Method: b.Method, 554 Path: b.Path, 555 Extensions: resp.Extensions, 556 StrictResponders: b.GenOpts.StrictResponders, 557 OperationName: b.Name, 558 Examples: examples, 559 } 560 561 // prepare response headers 562 for hName, header := range resp.Headers { 563 hdr, err := b.MakeHeader(receiver, hName, header) 564 if err != nil { 565 return GenResponse{}, err 566 } 567 res.Headers = append(res.Headers, hdr) 568 } 569 sort.Sort(res.Headers) 570 571 if resp.Schema != nil { 572 // resolve schema model 573 schema, ers := b.buildOperationSchema(fmt.Sprintf("%q", name), name+"Body", swag.ToGoName(name+"Body"), receiver, "i", resp.Schema, resolver) 574 if ers != nil { 575 return GenResponse{}, ers 576 } 577 res.Schema = &schema 578 } 579 return res, nil 580 } 581 582 func (b *codeGenOpBuilder) MakeHeader(receiver, name string, hdr spec.Header) (GenHeader, error) { 583 tpe := simpleResolvedType(hdr.Type, hdr.Format, hdr.Items, &hdr.CommonValidations) 584 585 id := swag.ToGoName(name) 586 res := GenHeader{ 587 sharedValidations: sharedValidations{ 588 Required: true, 589 SchemaValidations: hdr.Validations(), // NOTE: Required is not defined by the Swagger schema for header. Set arbitrarily to true for convenience in templates. 590 }, 591 resolvedType: tpe, 592 Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, defaultOperationsTarget), 593 ReceiverName: receiver, 594 ID: id, 595 Name: name, 596 Path: fmt.Sprintf("%q", name), 597 ValueExpression: fmt.Sprintf("%s.%s", receiver, id), 598 Description: trimBOM(hdr.Description), 599 Default: hdr.Default, 600 HasDefault: hdr.Default != nil, 601 Converter: stringConverters[tpe.GoType], 602 Formatter: stringFormatters[tpe.GoType], 603 ZeroValue: tpe.Zero(), 604 CollectionFormat: hdr.CollectionFormat, 605 IndexVar: "i", 606 } 607 res.HasValidations, res.HasSliceValidations = b.HasValidations(hdr.CommonValidations, res.resolvedType) 608 609 hasChildValidations := false 610 if hdr.Items != nil { 611 pi, err := b.MakeHeaderItem(receiver, name+" "+res.IndexVar, res.IndexVar+"i", "fmt.Sprintf(\"%s.%v\", \"header\", "+res.IndexVar+")", res.Name+"I", hdr.Items, nil) 612 if err != nil { 613 return GenHeader{}, err 614 } 615 res.Child = &pi 616 hasChildValidations = pi.HasValidations 617 } 618 // we feed the GenHeader structure the same way as we do for 619 // GenParameter, even though there is currently no actual validation 620 // for response headers. 621 res.HasValidations = res.HasValidations || hasChildValidations 622 623 return res, nil 624 } 625 626 func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, valueExpression string, items, 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 662 func (b *codeGenOpBuilder) HasValidations(sh spec.CommonValidations, rt resolvedType) (hasValidations bool, hasSliceValidations bool) { 663 hasSliceValidations = sh.HasArrayValidations() || sh.HasEnum() 664 hasValidations = sh.HasNumberValidations() || sh.HasStringValidations() || hasSliceValidations || hasFormatValidation(rt) 665 return 666 } 667 668 func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path, valueExpression, location string, resolver *typeResolver, items, 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 707 func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver, param spec.Parameter, idMapping map[string]map[string]string) (GenParameter, error) { 708 debugLog("[%s %s] making parameter %q", b.Method, b.Path, param.Name) 709 710 // assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema) 711 712 var child *GenItems 713 id := swag.ToGoName(param.Name) 714 if goName, ok := param.Extensions["x-go-name"]; ok { 715 id, ok = goName.(string) 716 if !ok { 717 return GenParameter{}, fmt.Errorf(`%s %s, parameter %q: "x-go-name" field must be a string, not a %T`, 718 b.Method, b.Path, param.Name, goName) 719 } 720 } else if len(idMapping) > 0 { 721 id = idMapping[param.In][param.Name] 722 } 723 724 res := GenParameter{ 725 ID: id, 726 Name: param.Name, 727 ModelsPackage: b.ModelsPackage, 728 Path: fmt.Sprintf("%q", param.Name), 729 ValueExpression: fmt.Sprintf("%s.%s", receiver, id), 730 IndexVar: "i", 731 Default: param.Default, 732 HasDefault: param.Default != nil, 733 Description: trimBOM(param.Description), 734 ReceiverName: receiver, 735 CollectionFormat: param.CollectionFormat, 736 Child: child, 737 Location: param.In, 738 AllowEmptyValue: (param.In == "query" || param.In == "formData") && param.AllowEmptyValue, 739 Extensions: param.Extensions, 740 } 741 742 if 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 784 func (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 822 func (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 925 func (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 971 func (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. 979 func (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 1004 func (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. 1014 func (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. 1030 func (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). 1039 withKeepDefinitionsPackage(pkg). 1040 withDefinitionPackage(b.APIPackageAlias) // all new extra schemas are going to be in api pkg 1041 pg.ExtraSchemas = make(map[string]GenSchema, len(sc.ExtraSchemas)) 1042 pg.UseContainerInName = true 1043 1044 // rebuild schema within local package 1045 if err = pg.makeGenSchema(); err != nil { 1046 return 1047 } 1048 1049 // lift nested extra schemas (inlined types) 1050 if b.ExtraSchemas == nil { 1051 b.ExtraSchemas = make(map[string]GenSchema, len(pg.ExtraSchemas)) 1052 } 1053 for _, v := range pg.ExtraSchemas { 1054 vv := v 1055 if !v.IsStream { 1056 b.ExtraSchemas[vv.Name] = vv 1057 } 1058 } 1059 schema = &pg.GenSchema 1060 return 1061 } 1062 1063 // buildOperationSchema constructs a schema for an operation (for body params or responses). 1064 // It determines if the schema is readily available from the models package, 1065 // or if a schema has to be generated in the operations package (i.e. is anonymous). 1066 // Whenever an anonymous schema needs some extra schemas, we also determine if these extras are 1067 // available from models or must be generated alongside the schema in the operations package. 1068 // 1069 // Duplicate extra schemas are pruned later on, when operations grouping in packages (e.g. from tags) takes place. 1070 func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schemaName, receiverName, indexVar string, sch *spec.Schema, resolver *typeResolver) (GenSchema, error) { 1071 var schema GenSchema 1072 1073 if sch == nil { 1074 sch = &spec.Schema{} 1075 } 1076 shallowClonedResolver := *resolver 1077 shallowClonedResolver.ModelsFullPkg = b.DefaultImports[b.ModelsPackage] 1078 rslv := &shallowClonedResolver 1079 1080 sc := schemaGenContext{ 1081 Path: schemaPath, 1082 Name: containerName, 1083 Receiver: receiverName, 1084 ValueExpr: receiverName, 1085 IndexVar: indexVar, 1086 Schema: *sch, 1087 Required: false, 1088 TypeResolver: rslv, 1089 Named: false, 1090 IncludeModel: true, 1091 IncludeValidator: b.GenOpts.IncludeValidator, 1092 StrictAdditionalProperties: b.GenOpts.StrictAdditionalProperties, 1093 ExtraSchemas: make(map[string]GenSchema), 1094 StructTags: b.GenOpts.StructTags, 1095 } 1096 1097 var ( 1098 br *typeResolver 1099 bs *spec.Schema 1100 ) 1101 1102 if sch.Ref.String() == "" { 1103 // backup the type resolver context 1104 // (not needed when the schema has a name) 1105 br, bs = b.saveResolveContext(rslv, sch) 1106 } 1107 1108 if err := sc.makeGenSchema(); err != nil { 1109 return GenSchema{}, err 1110 } 1111 for alias, pkg := range findImports(&sc.GenSchema) { 1112 b.Imports[alias] = pkg 1113 } 1114 1115 if sch.Ref.String() == "" && len(sc.ExtraSchemas) > 0 { 1116 newSchema, err := b.liftExtraSchemas(resolver, br, bs, &sc) 1117 if err != nil { 1118 return GenSchema{}, err 1119 } 1120 if newSchema != nil { 1121 schema = *newSchema 1122 } 1123 } else { 1124 schema = sc.GenSchema 1125 } 1126 1127 // new schemas will be in api pkg 1128 schemaPkg := b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "") 1129 schema.Pkg = schemaPkg 1130 1131 if schema.IsAnonymous { 1132 // a generated name for anonymous schema 1133 // TODO: support x-go-name 1134 hasProperties := len(schema.Properties) > 0 1135 isAllOf := len(schema.AllOf) > 0 1136 isInterface := schema.IsInterface 1137 hasValidations := schema.HasValidations 1138 1139 // for complex anonymous objects, produce an extra schema 1140 if hasProperties || isAllOf { 1141 if b.ExtraSchemas == nil { 1142 b.ExtraSchemas = make(map[string]GenSchema) 1143 } 1144 schema.Name = schemaName 1145 schema.GoType = schemaName 1146 schema.IsAnonymous = false 1147 b.ExtraSchemas[schemaName] = schema 1148 1149 // constructs new schema to refer to the newly created type 1150 schema = GenSchema{} 1151 schema.IsAnonymous = false 1152 schema.IsComplexObject = true 1153 schema.SwaggerType = schemaName 1154 schema.HasValidations = hasValidations 1155 schema.GoType = schemaName 1156 schema.Pkg = schemaPkg 1157 } else if isInterface { 1158 schema = GenSchema{} 1159 schema.IsAnonymous = false 1160 schema.IsComplexObject = false 1161 schema.IsInterface = true 1162 schema.HasValidations = false 1163 schema.GoType = iface 1164 } 1165 } 1166 1167 return schema, nil 1168 } 1169 1170 func intersectTags(left, right []string) []string { 1171 // dedupe 1172 uniqueTags := make(map[string]struct{}, maxInt(len(left), len(right))) 1173 for _, l := range left { 1174 if len(right) == 0 || swag.ContainsStrings(right, l) { 1175 uniqueTags[l] = struct{}{} 1176 } 1177 } 1178 filtered := make([]string, 0, len(uniqueTags)) 1179 // stable output across generations, preserving original order 1180 for _, k := range left { 1181 if _, ok := uniqueTags[k]; !ok { 1182 continue 1183 } 1184 filtered = append(filtered, k) 1185 delete(uniqueTags, k) 1186 } 1187 return filtered 1188 } 1189 1190 // analyze tags for an operation 1191 func (b *codeGenOpBuilder) analyzeTags() (string, []string, bool) { 1192 var ( 1193 filter []string 1194 tag string 1195 hasTagOverride bool 1196 ) 1197 if b.GenOpts != nil { 1198 filter = b.GenOpts.Tags 1199 } 1200 intersected := intersectTags(pruneEmpty(b.Operation.Tags), filter) 1201 if !b.GenOpts.SkipTagPackages && len(intersected) > 0 { 1202 // override generation with: x-go-operation-tag 1203 tag, hasTagOverride = b.Operation.Extensions.GetString(xGoOperationTag) 1204 if !hasTagOverride { 1205 // TODO(fred): this part should be delegated to some new TagsFor(operation) in go-openapi/analysis 1206 tag = intersected[0] 1207 gtags := b.Doc.Spec().Tags 1208 for _, gtag := range gtags { 1209 if gtag.Name != tag { 1210 continue 1211 } 1212 // honor x-go-name in tag 1213 if name, hasGoName := gtag.Extensions.GetString(xGoName); hasGoName { 1214 tag = name 1215 break 1216 } 1217 // honor x-go-operation-tag in tag 1218 if name, hasOpName := gtag.Extensions.GetString(xGoOperationTag); hasOpName { 1219 tag = name 1220 break 1221 } 1222 } 1223 } 1224 } 1225 if tag == b.APIPackage { 1226 // conflict with "operations" package is handled separately 1227 tag = renameOperationPackage(intersected, tag) 1228 } 1229 b.APIPackage = b.GenOpts.LanguageOpts.ManglePackageName(tag, b.APIPackage) // actual package name 1230 b.APIPackageAlias = deconflictTag(intersected, b.APIPackage) // deconflicted import alias 1231 return tag, intersected, len(filter) == 0 || len(filter) > 0 && len(intersected) > 0 1232 } 1233 1234 func maxInt(a, b int) int { 1235 if a > b { 1236 return a 1237 } 1238 return b 1239 } 1240 1241 // deconflictTag ensures generated packages for operations based on tags do not conflict 1242 // with other imports 1243 func deconflictTag(seenTags []string, pkg string) string { 1244 return deconflictPkg(pkg, func(pkg string) string { return renameOperationPackage(seenTags, pkg) }) 1245 } 1246 1247 // deconflictPrincipal ensures that whenever an external principal package is added, it doesn't conflict 1248 // with standard inports 1249 func deconflictPrincipal(pkg string) string { 1250 switch pkg { 1251 case "principal": 1252 return renamePrincipalPackage(pkg) 1253 default: 1254 return deconflictPkg(pkg, renamePrincipalPackage) 1255 } 1256 } 1257 1258 // deconflictPkg renames package names which conflict with standard imports 1259 func deconflictPkg(pkg string, renamer func(string) string) string { 1260 switch pkg { 1261 // package conflict with variables 1262 case "api", "httptransport", "formats", "server": 1263 fallthrough 1264 // package conflict with go-openapi imports 1265 case "errors", "runtime", "middleware", "security", "spec", "strfmt", "loads", "swag", "validate": 1266 fallthrough 1267 // package conflict with stdlib/other lib imports 1268 case "tls", "http", "fmt", "strings", "log", "flags", "pflag", "json", "time": 1269 return renamer(pkg) 1270 } 1271 return pkg 1272 } 1273 1274 func renameOperationPackage(seenTags []string, pkg string) string { 1275 current := strings.ToLower(pkg) + "ops" 1276 if len(seenTags) == 0 { 1277 return current 1278 } 1279 for swag.ContainsStringsCI(seenTags, current) { 1280 current += "1" 1281 } 1282 return current 1283 } 1284 1285 func renamePrincipalPackage(pkg string) string { 1286 // favors readability over perfect deconfliction 1287 return "auth" 1288 } 1289 1290 func renameServerPackage(pkg string) string { 1291 // favors readability over perfect deconfliction 1292 return "swagger" + pkg + "srv" 1293 } 1294 1295 func renameAPIPackage(pkg string) string { 1296 // favors readability over perfect deconfliction 1297 return "swagger" + pkg 1298 } 1299 1300 func renameImplementationPackage(pkg string) string { 1301 // favors readability over perfect deconfliction 1302 return "swagger" + pkg + "impl" 1303 }