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