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