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