github.com/emreu/go-swagger@v0.22.1/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 "os" 22 "path" 23 "path/filepath" 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 // It also generates an operation handler interface that uses the parameter model for handling a valid request. 60 // Allows for specifying a list of tags to include only certain tags for the generation 61 func GenerateServerOperation(operationNames []string, opts *GenOpts) error { 62 if opts == nil { 63 return errors.New("gen opts are required") 64 } 65 templates.LoadDefaults() 66 67 templates.SetAllowOverride(opts.AllowTemplateOverride) 68 69 if opts.TemplateDir != "" { 70 if err := templates.LoadDir(opts.TemplateDir); err != nil { 71 return err 72 } 73 } 74 75 if err := opts.CheckOpts(); err != nil { 76 return err 77 } 78 79 // Load the spec 80 _, specDoc, err := loadSpec(opts.Spec) 81 if err != nil { 82 return err 83 } 84 85 // Validate and Expand. specDoc is in/out param. 86 specDoc, err = validateAndFlattenSpec(opts, specDoc) 87 if err != nil { 88 return err 89 } 90 91 analyzed := analysis.New(specDoc.Spec()) 92 93 ops := gatherOperations(analyzed, operationNames) 94 if len(ops) == 0 { 95 return errors.New("no operations were selected") 96 } 97 98 for operationName, opRef := range ops { 99 method, path, operation := opRef.Method, opRef.Path, opRef.Op 100 defaultScheme := opts.DefaultScheme 101 if defaultScheme == "" { 102 defaultScheme = sHTTP 103 } 104 defaultProduces := opts.DefaultProduces 105 if defaultProduces == "" { 106 defaultProduces = runtime.JSONMime 107 } 108 defaultConsumes := opts.DefaultConsumes 109 if defaultConsumes == "" { 110 defaultConsumes = runtime.JSONMime 111 } 112 113 serverPackage := opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, "server") 114 generator := operationGenerator{ 115 Name: operationName, 116 Method: method, 117 Path: path, 118 BasePath: specDoc.BasePath(), 119 APIPackage: opts.LanguageOpts.ManglePackagePath(opts.APIPackage, "api"), 120 ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, "definitions"), 121 ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, "client"), 122 ServerPackage: serverPackage, 123 Operation: *operation, 124 SecurityRequirements: analyzed.SecurityRequirementsFor(operation), 125 SecurityDefinitions: analyzed.SecurityDefinitionsFor(operation), 126 Principal: opts.Principal, 127 Target: filepath.Join(opts.Target, filepath.FromSlash(serverPackage)), 128 Base: opts.Target, 129 Tags: opts.Tags, 130 IncludeHandler: opts.IncludeHandler, 131 IncludeParameters: opts.IncludeParameters, 132 IncludeResponses: opts.IncludeResponses, 133 IncludeValidator: true, // we no more support the CLI option to disable validation 134 DumpData: opts.DumpData, 135 DefaultScheme: defaultScheme, 136 DefaultProduces: defaultProduces, 137 DefaultConsumes: defaultConsumes, 138 Doc: specDoc, 139 Analyzed: analyzed, 140 GenOpts: opts, 141 } 142 if err := generator.Generate(); err != nil { 143 return err 144 } 145 } 146 return nil 147 } 148 149 type operationGenerator struct { 150 Authorized bool 151 IncludeHandler bool 152 IncludeParameters bool 153 IncludeResponses bool 154 IncludeValidator bool 155 DumpData bool 156 157 Principal string 158 Target string 159 Base string 160 Name string 161 Method string 162 Path string 163 BasePath string 164 APIPackage string 165 ModelsPackage string 166 ServerPackage string 167 ClientPackage string 168 Operation spec.Operation 169 SecurityRequirements [][]analysis.SecurityRequirement 170 SecurityDefinitions map[string]spec.SecurityScheme 171 Tags []string 172 DefaultScheme string 173 DefaultProduces string 174 DefaultConsumes string 175 Doc *loads.Document 176 Analyzed *analysis.Spec 177 GenOpts *GenOpts 178 } 179 180 func intersectTags(left, right []string) (filtered []string) { 181 if len(right) == 0 { 182 filtered = left 183 return 184 } 185 for _, l := range left { 186 if containsString(right, l) { 187 filtered = append(filtered, l) 188 } 189 } 190 return 191 } 192 193 func (o *operationGenerator) Generate() error { 194 // Build a list of codegen operations based on the tags, 195 // the tag decides the actual package for an operation 196 // the user specified package serves as root for generating the directory structure 197 var operations GenOperations 198 authed := len(o.SecurityRequirements) > 0 199 200 var bldr codeGenOpBuilder 201 bldr.Name = o.Name 202 bldr.Method = o.Method 203 bldr.Path = o.Path 204 bldr.BasePath = o.BasePath 205 bldr.ModelsPackage = o.ModelsPackage 206 bldr.Principal = o.Principal 207 bldr.Target = o.Target 208 bldr.Operation = o.Operation 209 bldr.Authed = authed 210 bldr.Security = o.SecurityRequirements 211 bldr.SecurityDefinitions = o.SecurityDefinitions 212 bldr.Doc = o.Doc 213 bldr.Analyzed = o.Analyzed 214 bldr.DefaultScheme = o.DefaultScheme 215 bldr.DefaultProduces = o.DefaultProduces 216 bldr.RootAPIPackage = o.GenOpts.LanguageOpts.ManglePackageName(o.ServerPackage, "server") 217 bldr.GenOpts = o.GenOpts 218 bldr.DefaultConsumes = o.DefaultConsumes 219 bldr.IncludeValidator = o.IncludeValidator 220 221 bldr.DefaultImports = []string{o.GenOpts.ExistingModels} 222 if o.GenOpts.ExistingModels == "" { 223 bldr.DefaultImports = []string{ 224 path.Join( 225 filepath.ToSlash(o.GenOpts.LanguageOpts.baseImport(o.Base)), 226 o.GenOpts.LanguageOpts.ManglePackagePath(o.ModelsPackage, "")), 227 } 228 } 229 230 bldr.APIPackage = o.APIPackage 231 st := o.Tags 232 if o.GenOpts != nil { 233 st = o.GenOpts.Tags 234 } 235 intersected := intersectTags(o.Operation.Tags, st) 236 if len(intersected) > 0 { 237 tag := intersected[0] 238 bldr.APIPackage = o.GenOpts.LanguageOpts.ManglePackagePath(tag, o.APIPackage) 239 } 240 op, err := bldr.MakeOperation() 241 if err != nil { 242 return err 243 } 244 op.Tags = intersected 245 operations = append(operations, op) 246 sort.Sort(operations) 247 248 for _, op := range operations { 249 if o.GenOpts.DumpData { 250 bb, _ := json.MarshalIndent(swag.ToDynamicJSON(op), "", " ") 251 fmt.Fprintln(os.Stdout, string(bb)) 252 continue 253 } 254 if err := o.GenOpts.renderOperation(&op); err != nil { 255 return err 256 } 257 } 258 259 return nil 260 } 261 262 type codeGenOpBuilder struct { 263 Authed bool 264 IncludeValidator bool 265 266 Name string 267 Method string 268 Path string 269 BasePath string 270 APIPackage string 271 RootAPIPackage string 272 ModelsPackage string 273 Principal string 274 Target string 275 Operation spec.Operation 276 Doc *loads.Document 277 Analyzed *analysis.Spec 278 DefaultImports []string 279 Imports map[string]string 280 DefaultScheme string 281 DefaultProduces string 282 DefaultConsumes string 283 Security [][]analysis.SecurityRequirement 284 SecurityDefinitions map[string]spec.SecurityScheme 285 ExtraSchemas map[string]GenSchema 286 GenOpts *GenOpts 287 } 288 289 // renameTimeout renames the variable in use by client template to avoid conflicting 290 // with param names. 291 func renameTimeout(seenIds map[string][]string, current string) string { 292 var next string 293 switch strings.ToLower(current) { 294 case "timeout": 295 next = "requestTimeout" 296 case "requesttimeout": 297 next = "httpRequestTimeout" 298 case "httptrequesttimeout": 299 next = "swaggerTimeout" 300 case "swaggertimeout": 301 next = "operationTimeout" 302 case "operationtimeout": 303 next = "opTimeout" 304 case "optimeout": 305 next = "operTimeout" 306 } 307 if _, ok := seenIds[next]; ok { 308 return renameTimeout(seenIds, next) 309 } 310 return next 311 } 312 313 func (b *codeGenOpBuilder) MakeOperation() (GenOperation, error) { 314 debugLog("[%s %s] parsing operation (id: %q)", b.Method, b.Path, b.Operation.ID) 315 // NOTE: we assume flatten is enabled by default (i.e. complex constructs are resolved from the models package), 316 // but do not assume the spec is necessarily fully flattened (i.e. all schemas moved to definitions). 317 // 318 // Fully flattened means that all complex constructs are present as 319 // definitions and models produced accordingly in ModelsPackage, 320 // whereas minimal flatten simply ensures that there are no weird $ref's in the spec. 321 // 322 // When some complex anonymous constructs are specified, extra schemas are produced in the operations package. 323 // 324 // In all cases, resetting definitions to the _original_ (untransformed) spec is not an option: 325 // we take from there the spec possibly already transformed by the GenDefinitions stage. 326 resolver := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(b.ModelsPackage, "models"), b.Doc) 327 receiver := "o" 328 329 operation := b.Operation 330 var params, qp, pp, hp, fp GenParameters 331 var hasQueryParams, hasPathParams, hasHeaderParams, hasFormParams, hasFileParams, hasFormValueParams, hasBodyParams bool 332 paramsForOperation := b.Analyzed.ParamsFor(b.Method, b.Path) 333 timeoutName := "timeout" 334 335 idMapping := map[string]map[string]string{ 336 "query": make(map[string]string, len(paramsForOperation)), 337 "path": make(map[string]string, len(paramsForOperation)), 338 "formData": make(map[string]string, len(paramsForOperation)), 339 "header": make(map[string]string, len(paramsForOperation)), 340 "body": make(map[string]string, len(paramsForOperation)), 341 } 342 343 seenIds := make(map[string][]string, len(paramsForOperation)) 344 for id, p := range paramsForOperation { 345 if _, ok := seenIds[p.Name]; ok { 346 idMapping[p.In][p.Name] = swag.ToGoName(id) 347 } else { 348 idMapping[p.In][p.Name] = swag.ToGoName(p.Name) 349 } 350 seenIds[p.Name] = append(seenIds[p.Name], p.In) 351 if strings.EqualFold(p.Name, timeoutName) { 352 timeoutName = renameTimeout(seenIds, timeoutName) 353 } 354 } 355 356 for _, p := range paramsForOperation { 357 cp, err := b.MakeParameter(receiver, resolver, p, idMapping) 358 359 if err != nil { 360 return GenOperation{}, err 361 } 362 if cp.IsQueryParam() { 363 hasQueryParams = true 364 qp = append(qp, cp) 365 } 366 if cp.IsFormParam() { 367 if p.Type == file { 368 hasFileParams = true 369 } 370 if p.Type != file { 371 hasFormValueParams = true 372 } 373 hasFormParams = true 374 fp = append(fp, cp) 375 } 376 if cp.IsPathParam() { 377 hasPathParams = true 378 pp = append(pp, cp) 379 } 380 if cp.IsHeaderParam() { 381 hasHeaderParams = true 382 hp = append(hp, cp) 383 } 384 if cp.IsBodyParam() { 385 hasBodyParams = true 386 } 387 params = append(params, cp) 388 } 389 sort.Sort(params) 390 sort.Sort(qp) 391 sort.Sort(pp) 392 sort.Sort(hp) 393 sort.Sort(fp) 394 395 var srs responses 396 if operation.Responses != nil { 397 srs = sortedResponses(operation.Responses.StatusCodeResponses) 398 } 399 responses := make([]GenResponse, 0, len(srs)) 400 var defaultResponse *GenResponse 401 var successResponses []GenResponse 402 if operation.Responses != nil { 403 for _, v := range srs { 404 name, ok := v.Response.Extensions.GetString(xGoName) 405 if !ok { 406 // look for name of well-known codes 407 name = runtime.Statuses[v.Code] 408 if name == "" { 409 // non-standard codes deserve some name 410 name = fmt.Sprintf("Status %d", v.Code) 411 } 412 } 413 name = swag.ToJSONName(b.Name + " " + name) 414 isSuccess := v.Code/100 == 2 415 gr, err := b.MakeResponse(receiver, name, isSuccess, resolver, v.Code, v.Response) 416 if err != nil { 417 return GenOperation{}, err 418 } 419 if isSuccess { 420 successResponses = append(successResponses, gr) 421 } 422 responses = append(responses, gr) 423 } 424 425 if operation.Responses.Default != nil { 426 gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, *operation.Responses.Default) 427 if err != nil { 428 return GenOperation{}, err 429 } 430 defaultResponse = &gr 431 } 432 } 433 // Always render a default response, even when no responses were defined 434 if operation.Responses == nil || (operation.Responses.Default == nil && len(srs) == 0) { 435 gr, err := b.MakeResponse(receiver, b.Name+" default", false, resolver, -1, spec.Response{}) 436 if err != nil { 437 return GenOperation{}, err 438 } 439 defaultResponse = &gr 440 } 441 442 if b.Principal == "" { 443 b.Principal = iface 444 } 445 446 swsp := resolver.Doc.Spec() 447 var extraSchemes []string 448 if ess, ok := operation.Extensions.GetStringSlice(xSchemes); ok { 449 extraSchemes = append(extraSchemes, ess...) 450 } 451 452 if ess1, ok := swsp.Extensions.GetStringSlice(xSchemes); ok { 453 extraSchemes = concatUnique(ess1, extraSchemes) 454 } 455 sort.Strings(extraSchemes) 456 schemes := concatUnique(swsp.Schemes, operation.Schemes) 457 sort.Strings(schemes) 458 produces := producesOrDefault(operation.Produces, swsp.Produces, b.DefaultProduces) 459 sort.Strings(produces) 460 consumes := producesOrDefault(operation.Consumes, swsp.Consumes, b.DefaultConsumes) 461 sort.Strings(consumes) 462 463 var hasStreamingResponse bool 464 if defaultResponse != nil && defaultResponse.Schema != nil && defaultResponse.Schema.IsStream { 465 hasStreamingResponse = true 466 } 467 var successResponse *GenResponse 468 for _, sr := range successResponses { 469 if sr.IsSuccess { 470 successResponse = &sr 471 break 472 } 473 } 474 for _, sr := range successResponses { 475 if !hasStreamingResponse && sr.Schema != nil && sr.Schema.IsStream { 476 hasStreamingResponse = true 477 break 478 } 479 } 480 if !hasStreamingResponse { 481 for _, r := range responses { 482 if r.Schema != nil && r.Schema.IsStream { 483 hasStreamingResponse = true 484 break 485 } 486 } 487 } 488 489 return GenOperation{ 490 GenCommon: GenCommon{ 491 Copyright: b.GenOpts.Copyright, 492 TargetImportPath: filepath.ToSlash(b.GenOpts.LanguageOpts.baseImport(b.GenOpts.Target)), 493 }, 494 Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "api"), 495 RootPackage: b.RootAPIPackage, 496 Name: b.Name, 497 Method: b.Method, 498 Path: b.Path, 499 BasePath: b.BasePath, 500 Tags: operation.Tags, 501 Description: trimBOM(operation.Description), 502 ReceiverName: receiver, 503 DefaultImports: b.DefaultImports, 504 Imports: b.Imports, 505 Params: params, 506 Summary: trimBOM(operation.Summary), 507 QueryParams: qp, 508 PathParams: pp, 509 HeaderParams: hp, 510 FormParams: fp, 511 HasQueryParams: hasQueryParams, 512 HasPathParams: hasPathParams, 513 HasHeaderParams: hasHeaderParams, 514 HasFormParams: hasFormParams, 515 HasFormValueParams: hasFormValueParams, 516 HasFileParams: hasFileParams, 517 HasBodyParams: hasBodyParams, 518 HasStreamingResponse: hasStreamingResponse, 519 Authorized: b.Authed, 520 Security: b.makeSecurityRequirements(receiver), 521 SecurityDefinitions: b.makeSecuritySchemes(receiver), 522 Principal: b.Principal, 523 Responses: responses, 524 DefaultResponse: defaultResponse, 525 SuccessResponse: successResponse, 526 SuccessResponses: successResponses, 527 ExtraSchemas: gatherExtraSchemas(b.ExtraSchemas), 528 Schemes: schemeOrDefault(schemes, b.DefaultScheme), 529 ProducesMediaTypes: produces, 530 ConsumesMediaTypes: consumes, 531 ExtraSchemes: extraSchemes, 532 TimeoutName: timeoutName, 533 Extensions: operation.Extensions, 534 }, nil 535 } 536 537 func producesOrDefault(produces []string, fallback []string, defaultProduces string) []string { 538 if len(produces) > 0 { 539 return produces 540 } 541 if len(fallback) > 0 { 542 return fallback 543 } 544 return []string{defaultProduces} 545 } 546 547 func schemeOrDefault(schemes []string, defaultScheme string) []string { 548 if len(schemes) == 0 { 549 return []string{defaultScheme} 550 } 551 return schemes 552 } 553 554 func concatUnique(collections ...[]string) []string { 555 resultSet := make(map[string]struct{}) 556 for _, c := range collections { 557 for _, i := range c { 558 if _, ok := resultSet[i]; !ok { 559 resultSet[i] = struct{}{} 560 } 561 } 562 } 563 var result []string 564 for k := range resultSet { 565 result = append(result, k) 566 } 567 return result 568 } 569 570 func (b *codeGenOpBuilder) MakeResponse(receiver, name string, isSuccess bool, resolver *typeResolver, code int, resp spec.Response) (GenResponse, error) { 571 debugLog("[%s %s] making id %q", b.Method, b.Path, b.Operation.ID) 572 573 // assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema) 574 575 res := GenResponse{ 576 Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "api"), 577 ModelsPackage: b.ModelsPackage, 578 ReceiverName: receiver, 579 Name: name, 580 Description: trimBOM(resp.Description), 581 DefaultImports: b.DefaultImports, 582 Imports: b.Imports, 583 IsSuccess: isSuccess, 584 Code: code, 585 Method: b.Method, 586 Path: b.Path, 587 Extensions: resp.Extensions, 588 } 589 590 // prepare response headers 591 for hName, header := range resp.Headers { 592 hdr, err := b.MakeHeader(receiver, hName, header) 593 if err != nil { 594 return GenResponse{}, err 595 } 596 res.Headers = append(res.Headers, hdr) 597 } 598 sort.Sort(res.Headers) 599 600 if resp.Schema != nil { 601 // resolve schema model 602 schema, ers := b.buildOperationSchema(fmt.Sprintf("%q", name), name+"Body", swag.ToGoName(name+"Body"), receiver, "i", resp.Schema, resolver) 603 if ers != nil { 604 return GenResponse{}, ers 605 } 606 res.Schema = &schema 607 } 608 return res, nil 609 } 610 611 func (b *codeGenOpBuilder) MakeHeader(receiver, name string, hdr spec.Header) (GenHeader, error) { 612 tpe := typeForHeader(hdr) //simpleResolvedType(hdr.Type, hdr.Format, hdr.Items) 613 614 id := swag.ToGoName(name) 615 res := GenHeader{ 616 sharedValidations: sharedValidationsFromSimple(hdr.CommonValidations, true), // NOTE: Required is not defined by the Swagger schema for header. Set arbitrarily to true for convenience in templates. 617 resolvedType: tpe, 618 Package: b.GenOpts.LanguageOpts.ManglePackageName(b.APIPackage, "api"), 619 ReceiverName: receiver, 620 ID: id, 621 Name: name, 622 Path: fmt.Sprintf("%q", name), 623 ValueExpression: fmt.Sprintf("%s.%s", receiver, id), 624 Description: trimBOM(hdr.Description), 625 Default: hdr.Default, 626 HasDefault: hdr.Default != nil, 627 Converter: stringConverters[tpe.GoType], 628 Formatter: stringFormatters[tpe.GoType], 629 ZeroValue: tpe.Zero(), 630 CollectionFormat: hdr.CollectionFormat, 631 IndexVar: "i", 632 } 633 res.HasValidations, res.HasSliceValidations = b.HasValidations(hdr.CommonValidations, res.resolvedType) 634 635 hasChildValidations := false 636 if hdr.Items != nil { 637 pi, err := b.MakeHeaderItem(receiver, name+" "+res.IndexVar, res.IndexVar+"i", "fmt.Sprintf(\"%s.%v\", \"header\", "+res.IndexVar+")", res.Name+"I", hdr.Items, nil) 638 if err != nil { 639 return GenHeader{}, err 640 } 641 res.Child = &pi 642 hasChildValidations = pi.HasValidations 643 } 644 // we feed the GenHeader structure the same way as we do for 645 // GenParameter, even though there is currently no actual validation 646 // for response headers. 647 res.HasValidations = res.HasValidations || hasChildValidations 648 649 return res, nil 650 } 651 652 func (b *codeGenOpBuilder) MakeHeaderItem(receiver, paramName, indexVar, path, valueExpression string, items, parent *spec.Items) (GenItems, error) { 653 var res GenItems 654 res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items) 655 res.sharedValidations = sharedValidationsFromSimple(items.CommonValidations, false) 656 res.Name = paramName 657 res.Path = path 658 res.Location = "header" 659 res.ValueExpression = swag.ToVarName(valueExpression) 660 res.CollectionFormat = items.CollectionFormat 661 res.Converter = stringConverters[res.GoType] 662 res.Formatter = stringFormatters[res.GoType] 663 res.IndexVar = indexVar 664 res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType) 665 666 if items.Items != nil { 667 // Recursively follows nested arrays 668 // IMPORTANT! transmitting a ValueExpression consistent with the parent's one 669 hi, err := b.MakeHeaderItem(receiver, paramName+" "+indexVar, indexVar+"i", "fmt.Sprintf(\"%s.%v\", \"header\", "+indexVar+")", res.ValueExpression+"I", items.Items, items) 670 if err != nil { 671 return GenItems{}, err 672 } 673 res.Child = &hi 674 hi.Parent = &res 675 // Propagates HasValidations flag to outer Items definition (currently not in use: done to remain consistent with parameters) 676 res.HasValidations = res.HasValidations || hi.HasValidations 677 } 678 679 return res, nil 680 } 681 682 // HasValidations resolves the validation status for simple schema objects 683 func (b *codeGenOpBuilder) HasValidations(sh spec.CommonValidations, rt resolvedType) (hasValidations bool, hasSliceValidations bool) { 684 hasNumberValidation := sh.Maximum != nil || sh.Minimum != nil || sh.MultipleOf != nil 685 hasStringValidation := sh.MaxLength != nil || sh.MinLength != nil || sh.Pattern != "" 686 hasSliceValidations = sh.MaxItems != nil || sh.MinItems != nil || sh.UniqueItems || len(sh.Enum) > 0 687 hasValidations = (hasNumberValidation || hasStringValidation || hasSliceValidations || rt.IsCustomFormatter) && !rt.IsStream && !rt.IsInterface 688 return 689 } 690 691 func (b *codeGenOpBuilder) MakeParameterItem(receiver, paramName, indexVar, path, valueExpression, location string, resolver *typeResolver, items, parent *spec.Items) (GenItems, error) { 692 debugLog("making parameter item recv=%s param=%s index=%s valueExpr=%s path=%s location=%s", receiver, paramName, indexVar, valueExpression, path, location) 693 var res GenItems 694 res.resolvedType = simpleResolvedType(items.Type, items.Format, items.Items) 695 res.sharedValidations = sharedValidationsFromSimple(items.CommonValidations, false) 696 res.Name = paramName 697 res.Path = path 698 res.Location = location 699 res.ValueExpression = swag.ToVarName(valueExpression) 700 res.CollectionFormat = items.CollectionFormat 701 res.Converter = stringConverters[res.GoType] 702 res.Formatter = stringFormatters[res.GoType] 703 res.IndexVar = indexVar 704 705 res.HasValidations, res.HasSliceValidations = b.HasValidations(items.CommonValidations, res.resolvedType) 706 707 if items.Items != nil { 708 // Recursively follows nested arrays 709 // IMPORTANT! transmitting a ValueExpression consistent with the parent's one 710 pi, err := b.MakeParameterItem(receiver, paramName+" "+indexVar, indexVar+"i", "fmt.Sprintf(\"%s.%v\", "+path+", "+indexVar+")", res.ValueExpression+"I", location, resolver, items.Items, items) 711 if err != nil { 712 return GenItems{}, err 713 } 714 res.Child = &pi 715 pi.Parent = &res 716 // Propagates HasValidations flag to outer Items definition 717 res.HasValidations = res.HasValidations || pi.HasValidations 718 } 719 720 return res, nil 721 } 722 723 func (b *codeGenOpBuilder) MakeParameter(receiver string, resolver *typeResolver, param spec.Parameter, idMapping map[string]map[string]string) (GenParameter, error) { 724 debugLog("[%s %s] making parameter %q", b.Method, b.Path, param.Name) 725 726 // assume minimal flattening has been carried on, so there is not $ref in response (but some may remain in response schema) 727 728 var child *GenItems 729 id := swag.ToGoName(param.Name) 730 if len(idMapping) > 0 { 731 id = idMapping[param.In][param.Name] 732 } 733 734 res := GenParameter{ 735 ID: id, 736 Name: param.Name, 737 ModelsPackage: b.ModelsPackage, 738 Path: fmt.Sprintf("%q", param.Name), 739 ValueExpression: fmt.Sprintf("%s.%s", receiver, id), 740 IndexVar: "i", 741 Default: param.Default, 742 HasDefault: param.Default != nil, 743 Description: trimBOM(param.Description), 744 ReceiverName: receiver, 745 CollectionFormat: param.CollectionFormat, 746 Child: child, 747 Location: param.In, 748 AllowEmptyValue: (param.In == "query" || param.In == "formData") && param.AllowEmptyValue, 749 Extensions: param.Extensions, 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) 761 res.sharedValidations = sharedValidationsFromSimple(param.CommonValidations, param.Required) 762 763 res.ZeroValue = res.resolvedType.Zero() 764 765 hasChildValidations := false 766 if param.Items != nil { 767 // Follow Items definition for array parameters 768 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) 769 if err != nil { 770 return GenParameter{}, err 771 } 772 res.Child = &pi 773 // Propagates HasValidations from from child array 774 hasChildValidations = pi.HasValidations 775 } 776 res.IsNullable = !param.Required && !param.AllowEmptyValue 777 res.HasValidations, res.HasSliceValidations = b.HasValidations(param.CommonValidations, res.resolvedType) 778 res.HasValidations = res.HasValidations || hasChildValidations 779 } 780 781 // Select codegen strategy for body param validation 782 res.Converter = stringConverters[res.GoType] 783 res.Formatter = stringFormatters[res.GoType] 784 b.setBodyParamValidation(&res) 785 786 return res, nil 787 } 788 789 // MakeBodyParameter constructs a body parameter schema 790 func (b *codeGenOpBuilder) MakeBodyParameter(res *GenParameter, resolver *typeResolver, sch *spec.Schema) error { 791 // resolve schema model 792 schema, ers := b.buildOperationSchema(res.Path, b.Operation.ID+"ParamsBody", swag.ToGoName(b.Operation.ID+" Body"), res.ReceiverName, res.IndexVar, sch, resolver) 793 if ers != nil { 794 return ers 795 } 796 res.Schema = &schema 797 res.Schema.Required = res.Required // Required in body is managed independently from validations 798 799 // build Child items for nested slices and maps 800 var items *GenItems 801 res.KeyVar = "k" 802 res.Schema.KeyVar = "k" 803 switch { 804 case schema.IsMap && !schema.IsInterface: 805 items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.AdditionalProperties) 806 case schema.IsArray: 807 items = b.MakeBodyParameterItemsAndMaps(res, res.Schema.Items) 808 default: 809 items = new(GenItems) 810 } 811 812 // templates assume at least one .Child != nil 813 res.Child = items 814 schema.HasValidations = schema.HasValidations || items.HasValidations 815 816 res.resolvedType = schema.resolvedType 817 818 // simple and schema views share the same validations 819 res.sharedValidations = schema.sharedValidations 820 res.ZeroValue = schema.Zero() 821 return nil 822 } 823 824 // MakeBodyParameterItemsAndMaps clones the .Items schema structure (resp. .AdditionalProperties) as a .GenItems structure 825 // for compatibility with simple param templates. 826 // 827 // Constructed children assume simple structures: any complex object is assumed to be resolved by a model or extra schema definition 828 func (b *codeGenOpBuilder) MakeBodyParameterItemsAndMaps(res *GenParameter, it *GenSchema) *GenItems { 829 items := new(GenItems) 830 if it != nil { 831 var prev *GenItems 832 next := items 833 if res.Schema.IsArray { 834 next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.IndexVar + ")" 835 } else if res.Schema.IsMap { 836 next.Path = "fmt.Sprintf(\"%s.%v\", " + res.Path + ", " + res.KeyVar + ")" 837 } 838 next.Name = res.Name + " " + res.Schema.IndexVar 839 next.IndexVar = res.Schema.IndexVar + "i" 840 next.KeyVar = res.Schema.KeyVar + "k" 841 next.ValueExpression = swag.ToVarName(res.Name + "I") 842 next.Location = "body" 843 for it != nil { 844 next.resolvedType = it.resolvedType 845 next.sharedValidations = it.sharedValidations 846 next.Formatter = stringFormatters[it.SwaggerFormat] 847 next.Converter = stringConverters[res.GoType] 848 next.Parent = prev 849 _, next.IsCustomFormatter = customFormatters[it.GoType] 850 next.IsCustomFormatter = next.IsCustomFormatter && !it.IsStream 851 852 // special instruction to avoid using CollectionFormat for body params 853 next.SkipParse = true 854 855 if prev != nil { 856 if prev.IsArray { 857 next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.IndexVar + ")" 858 } else if prev.IsMap { 859 next.Path = "fmt.Sprintf(\"%s.%v\", " + prev.Path + ", " + prev.KeyVar + ")" 860 } 861 next.Name = prev.Name + prev.IndexVar 862 next.IndexVar = prev.IndexVar + "i" 863 next.KeyVar = prev.KeyVar + "k" 864 next.ValueExpression = swag.ToVarName(prev.ValueExpression + "I") 865 prev.Child = next 866 } 867 868 // found a complex or aliased thing 869 // hide details from the aliased type and stop recursing 870 if next.IsAliased || next.IsComplexObject { 871 next.IsArray = false 872 next.IsMap = false 873 next.IsCustomFormatter = false 874 next.IsComplexObject = true 875 next.IsAliased = true 876 break 877 } 878 if next.IsInterface || next.IsStream { 879 next.HasValidations = false 880 } 881 prev = next 882 next = new(GenItems) 883 884 switch { 885 case it.Items != nil: 886 it = it.Items 887 case it.AdditionalProperties != nil: 888 it = it.AdditionalProperties 889 default: 890 it = nil 891 } 892 } 893 // propagate HasValidations 894 var propag func(child *GenItems) bool 895 propag = func(child *GenItems) bool { 896 if child == nil { 897 return false 898 } 899 child.HasValidations = child.HasValidations || propag(child.Child) 900 return child.HasValidations 901 } 902 items.HasValidations = propag(items) 903 904 // resolve nullability conflicts when declaring body as a map of array of an anonymous complex object 905 // (e.g. refer to an extra schema type, which is nullable, but not rendered as a pointer in arrays or maps) 906 // Rule: outer type rules (with IsMapNullOverride), inner types are fixed 907 var fixNullable func(child *GenItems) string 908 fixNullable = func(child *GenItems) string { 909 if !child.IsArray && !child.IsMap { 910 if child.IsComplexObject { 911 return child.GoType 912 } 913 return "" 914 } 915 if innerType := fixNullable(child.Child); innerType != "" { 916 if child.IsMapNullOverride && child.IsArray { 917 child.GoType = "[]" + innerType 918 return child.GoType 919 } 920 } 921 return "" 922 } 923 fixNullable(items) 924 } 925 return items 926 } 927 928 func (b *codeGenOpBuilder) setBodyParamValidation(p *GenParameter) { 929 // Determine validation strategy for body param. 930 // 931 // Here are the distinct strategies: 932 // - the body parameter is a model object => delegates 933 // - the body parameter is an array of model objects => carry on slice validations, then iterate and delegate 934 // - the body parameter is a map of model objects => iterate and delegate 935 // - the body parameter is an array of simple objects (including maps) 936 // - the body parameter is a map of simple objects (including arrays) 937 if p.IsBodyParam() { 938 var hasSimpleBodyParams, hasSimpleBodyItems, hasSimpleBodyMap, hasModelBodyParams, hasModelBodyItems, hasModelBodyMap bool 939 s := p.Schema 940 if s != nil { 941 doNot := s.IsInterface || s.IsStream 942 // composition of primitive fields must be properly identified: hack this through 943 _, isPrimitive := primitives[s.GoType] 944 _, isFormatter := customFormatters[s.GoType] 945 isComposedPrimitive := s.IsPrimitive && !(isPrimitive || isFormatter) 946 947 hasSimpleBodyParams = !s.IsComplexObject && !s.IsAliased && !isComposedPrimitive && !doNot 948 hasModelBodyParams = (s.IsComplexObject || s.IsAliased || isComposedPrimitive) && !doNot 949 950 if s.IsArray && s.Items != nil { 951 it := s.Items 952 doNot = it.IsInterface || it.IsStream 953 hasSimpleBodyItems = !it.IsComplexObject && !(it.IsAliased || doNot) 954 hasModelBodyItems = (it.IsComplexObject || it.IsAliased) && !doNot 955 } 956 if s.IsMap && s.AdditionalProperties != nil { 957 it := s.AdditionalProperties 958 hasSimpleBodyMap = !it.IsComplexObject && !(it.IsAliased || doNot) 959 hasModelBodyMap = !hasSimpleBodyMap && !doNot 960 } 961 } 962 // set validation strategy for body param 963 p.HasSimpleBodyParams = hasSimpleBodyParams 964 p.HasSimpleBodyItems = hasSimpleBodyItems 965 p.HasModelBodyParams = hasModelBodyParams 966 p.HasModelBodyItems = hasModelBodyItems 967 p.HasModelBodyMap = hasModelBodyMap 968 p.HasSimpleBodyMap = hasSimpleBodyMap 969 } 970 971 } 972 973 // makeSecuritySchemes produces a sorted list of security schemes for this operation 974 func (b *codeGenOpBuilder) makeSecuritySchemes(receiver string) GenSecuritySchemes { 975 return gatherSecuritySchemes(b.SecurityDefinitions, b.Name, b.Principal, receiver) 976 } 977 978 // makeSecurityRequirements produces a sorted list of security requirements for this operation. 979 // As for current, these requirements are not used by codegen (sec. requirement is determined at runtime). 980 // We keep the order of the slice from the original spec, but sort the inner slice which comes from a map, 981 // as well as the map of scopes. 982 func (b *codeGenOpBuilder) makeSecurityRequirements(receiver string) []GenSecurityRequirements { 983 if b.Security == nil { 984 // nil (default requirement) is different than [] (no requirement) 985 return nil 986 } 987 988 securityRequirements := make([]GenSecurityRequirements, 0, len(b.Security)) 989 for _, req := range b.Security { 990 jointReq := make(GenSecurityRequirements, 0, len(req)) 991 for _, j := range req { 992 scopes := j.Scopes 993 sort.Strings(scopes) 994 jointReq = append(jointReq, GenSecurityRequirement{ 995 Name: j.Name, 996 Scopes: scopes, 997 }) 998 } 999 // sort joint requirements (come from a map in spec) 1000 sort.Sort(jointReq) 1001 securityRequirements = append(securityRequirements, jointReq) 1002 } 1003 return securityRequirements 1004 } 1005 1006 // cloneSchema returns a deep copy of a schema 1007 func (b *codeGenOpBuilder) cloneSchema(schema *spec.Schema) *spec.Schema { 1008 savedSchema := &spec.Schema{} 1009 schemaRep, _ := json.Marshal(schema) 1010 _ = json.Unmarshal(schemaRep, savedSchema) 1011 return savedSchema 1012 } 1013 1014 // saveResolveContext keeps a copy of known definitions and schema to properly roll back on a makeGenSchema() call 1015 // This uses a deep clone the spec document to construct a type resolver which knows about definitions when the making of this operation started, 1016 // and only these definitions. We are not interested in the "original spec", but in the already transformed spec. 1017 func (b *codeGenOpBuilder) saveResolveContext(resolver *typeResolver, schema *spec.Schema) (*typeResolver, *spec.Schema) { 1018 rslv := newTypeResolver(b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, "models"), b.Doc.Pristine()) 1019 1020 return rslv, b.cloneSchema(schema) 1021 } 1022 1023 // liftExtraSchemas constructs the schema for an anonymous construct with some ExtraSchemas. 1024 // 1025 // When some ExtraSchemas are produced from something else than a definition, 1026 // this indicates we are not running in fully flattened mode and we need to render 1027 // these ExtraSchemas in the operation's package. 1028 // We need to rebuild the schema with a new type resolver to reflect this change in the 1029 // models package. 1030 func (b *codeGenOpBuilder) liftExtraSchemas(resolver, br *typeResolver, bs *spec.Schema, sc *schemaGenContext) (schema *GenSchema, err error) { 1031 // restore resolving state before previous call to makeGenSchema() 1032 rslv := br 1033 sc.Schema = *bs 1034 1035 pg := sc.shallowClone() 1036 pkg := b.GenOpts.LanguageOpts.ManglePackageName(resolver.ModelsPackage, "models") 1037 pg.TypeResolver = newTypeResolver("", rslv.Doc).withKeepDefinitionsPackage(pkg) 1038 pg.ExtraSchemas = make(map[string]GenSchema, len(sc.ExtraSchemas)) 1039 1040 if err = pg.makeGenSchema(); err != nil { 1041 return 1042 } 1043 // lift nested extra schemas (inlined types) 1044 if b.ExtraSchemas == nil { 1045 b.ExtraSchemas = make(map[string]GenSchema, len(pg.ExtraSchemas)) 1046 } 1047 for _, v := range pg.ExtraSchemas { 1048 vv := v 1049 if !v.IsStream { 1050 b.ExtraSchemas[vv.Name] = vv 1051 } 1052 } 1053 schema = &pg.GenSchema 1054 return 1055 } 1056 1057 // buildOperationSchema constructs a schema for an operation (for body params or responses). 1058 // It determines if the schema is readily available from the models package, 1059 // or if a schema has to be generated in the operations package (i.e. is anonymous). 1060 // Whenever an anonymous schema needs some extra schemas, we also determine if these extras are 1061 // available from models or must be generated alongside the schema in the operations package. 1062 // 1063 // Duplicate extra schemas are pruned later on, when operations grouping in packages (e.g. from tags) takes place. 1064 func (b *codeGenOpBuilder) buildOperationSchema(schemaPath, containerName, schemaName, receiverName, indexVar string, sch *spec.Schema, resolver *typeResolver) (GenSchema, error) { 1065 var schema GenSchema 1066 1067 if sch == nil { 1068 sch = &spec.Schema{} 1069 } 1070 rslv := resolver 1071 sc := schemaGenContext{ 1072 Path: schemaPath, 1073 Name: containerName, 1074 Receiver: receiverName, 1075 ValueExpr: receiverName, 1076 IndexVar: indexVar, 1077 Schema: *sch, 1078 Required: false, 1079 TypeResolver: rslv, 1080 Named: false, 1081 IncludeModel: true, 1082 IncludeValidator: true, 1083 StrictAdditionalProperties: b.GenOpts.StrictAdditionalProperties, 1084 ExtraSchemas: make(map[string]GenSchema), 1085 } 1086 1087 var ( 1088 br *typeResolver 1089 bs *spec.Schema 1090 ) 1091 // these backups are not needed when sch has name. 1092 if sch.Ref.String() == "" { 1093 br, bs = b.saveResolveContext(rslv, sch) 1094 } 1095 1096 if err := sc.makeGenSchema(); err != nil { 1097 return GenSchema{}, err 1098 } 1099 for alias, pkg := range findImports(&sc.GenSchema) { 1100 b.Imports[alias] = pkg 1101 } 1102 1103 if sch.Ref.String() == "" && len(sc.ExtraSchemas) > 0 { 1104 newSchema, err := b.liftExtraSchemas(resolver, br, bs, &sc) 1105 if err != nil { 1106 return GenSchema{}, err 1107 } 1108 if newSchema != nil { 1109 schema = *newSchema 1110 } 1111 } else { 1112 schema = sc.GenSchema 1113 } 1114 1115 if schema.IsAnonymous { 1116 // a generated name for anonymous schema 1117 // TODO: support x-go-name 1118 hasProperties := len(schema.Properties) > 0 1119 isAllOf := len(schema.AllOf) > 0 1120 isInterface := schema.IsInterface 1121 hasValidations := schema.HasValidations 1122 1123 // for complex anonymous objects, produce an extra schema 1124 if hasProperties || isAllOf { 1125 if b.ExtraSchemas == nil { 1126 b.ExtraSchemas = make(map[string]GenSchema) 1127 } 1128 schema.Name = schemaName 1129 schema.GoType = schemaName 1130 schema.IsAnonymous = false 1131 b.ExtraSchemas[schemaName] = schema 1132 1133 // constructs new schema to refer to the newly created type 1134 schema = GenSchema{} 1135 schema.IsAnonymous = false 1136 schema.IsComplexObject = true 1137 schema.SwaggerType = schemaName 1138 schema.HasValidations = hasValidations 1139 schema.GoType = schemaName 1140 } else if isInterface { 1141 schema = GenSchema{} 1142 schema.IsAnonymous = false 1143 schema.IsComplexObject = false 1144 schema.IsInterface = true 1145 schema.HasValidations = false 1146 schema.GoType = iface 1147 } 1148 } 1149 return schema, nil 1150 }