github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/generator/shared.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 "bytes" 19 "encoding/json" 20 "errors" 21 "fmt" 22 "log" 23 "os" 24 "path" 25 "path/filepath" 26 "reflect" 27 "sort" 28 "strings" 29 "text/template" 30 31 "github.com/go-openapi/analysis" 32 "github.com/go-openapi/loads" 33 "github.com/go-openapi/runtime" 34 "github.com/go-openapi/spec" 35 "github.com/go-openapi/swag" 36 ) 37 38 const ( 39 // default generation targets structure 40 defaultModelsTarget = "models" 41 defaultServerTarget = "restapi" 42 defaultClientTarget = "client" 43 defaultOperationsTarget = "operations" 44 defaultClientName = "rest" 45 defaultServerName = "swagger" 46 defaultScheme = "http" 47 defaultImplementationTarget = "implementation" 48 ) 49 50 func init() { 51 // all initializations for the generator package 52 debugOptions() 53 initLanguage() 54 initTemplateRepo() 55 initTypes() 56 } 57 58 // DefaultSectionOpts for a given opts, this is used when no config file is passed 59 // and uses the embedded templates when no local override can be found 60 func DefaultSectionOpts(gen *GenOpts) { 61 sec := gen.Sections 62 if len(sec.Models) == 0 { 63 opts := []TemplateOpts{ 64 { 65 Name: "definition", 66 Source: "asset:model", 67 Target: "{{ joinFilePath .Target (toPackagePath .ModelPackage) }}", 68 FileName: "{{ (snakize (pascalize .Name)) }}.go", 69 }, 70 } 71 if gen.IncludeCLi { 72 opts = append(opts, TemplateOpts{ 73 Name: "clidefinitionhook", 74 Source: "asset:cliModelcli", 75 Target: "{{ joinFilePath .Target (toPackagePath .CliPackage) }}", 76 FileName: "{{ (snakize (pascalize .Name)) }}_model.go", 77 }) 78 } 79 sec.Models = opts 80 } 81 82 if len(sec.Operations) == 0 { 83 if gen.IsClient { 84 opts := []TemplateOpts{ 85 { 86 Name: "parameters", 87 Source: "asset:clientParameter", 88 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}", 89 FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go", 90 }, 91 { 92 Name: "responses", 93 Source: "asset:clientResponse", 94 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}", 95 FileName: "{{ (snakize (pascalize .Name)) }}_responses.go", 96 }, 97 } 98 if gen.IncludeCLi { 99 opts = append(opts, TemplateOpts{ 100 Name: "clioperation", 101 Source: "asset:cliOperation", 102 Target: "{{ joinFilePath .Target (toPackagePath .CliPackage) }}", 103 FileName: "{{ (snakize (pascalize .Name)) }}_operation.go", 104 }) 105 } 106 sec.Operations = opts 107 } else { 108 ops := []TemplateOpts{} 109 if gen.IncludeParameters { 110 ops = append(ops, TemplateOpts{ 111 Name: "parameters", 112 Source: "asset:serverParameter", 113 Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 114 FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go", 115 }) 116 } 117 if gen.IncludeURLBuilder { 118 ops = append(ops, TemplateOpts{ 119 Name: "urlbuilder", 120 Source: "asset:serverUrlbuilder", 121 Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 122 FileName: "{{ (snakize (pascalize .Name)) }}_urlbuilder.go", 123 }) 124 } 125 if gen.IncludeResponses { 126 ops = append(ops, TemplateOpts{ 127 Name: "responses", 128 Source: "asset:serverResponses", 129 Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 130 FileName: "{{ (snakize (pascalize .Name)) }}_responses.go", 131 }) 132 } 133 if gen.IncludeHandler { 134 ops = append(ops, TemplateOpts{ 135 Name: "handler", 136 Source: "asset:serverOperation", 137 Target: "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 138 FileName: "{{ (snakize (pascalize .Name)) }}.go", 139 }) 140 } 141 sec.Operations = ops 142 } 143 } 144 145 if len(sec.OperationGroups) == 0 { 146 if gen.IsClient { 147 sec.OperationGroups = []TemplateOpts{ 148 { 149 Name: "client", 150 Source: "asset:clientClient", 151 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Name)}}", 152 FileName: "{{ (snakize (pascalize .Name)) }}_client.go", 153 }, 154 } 155 } else { 156 sec.OperationGroups = []TemplateOpts{} 157 } 158 } 159 160 if len(sec.Application) == 0 { 161 if gen.IsClient { 162 opts := []TemplateOpts{ 163 { 164 Name: "facade", 165 Source: "asset:clientFacade", 166 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) }}", 167 FileName: "{{ snakize .Name }}Client.go", 168 }, 169 } 170 if gen.IncludeCLi { 171 // include a commandline tool app 172 opts = append(opts, []TemplateOpts{{ 173 Name: "commandline", 174 Source: "asset:cliCli", 175 Target: "{{ joinFilePath .Target (toPackagePath .CliPackage) }}", 176 FileName: "cli.go", 177 }, { 178 Name: "climain", 179 Source: "asset:cliMain", 180 Target: "{{ joinFilePath .Target \"cmd\" (toPackagePath .CliAppName) }}", 181 FileName: "main.go", 182 }, { 183 Name: "cliAutoComplete", 184 Source: "asset:cliCompletion", 185 Target: "{{ joinFilePath .Target (toPackagePath .CliPackage) }}", 186 FileName: "autocomplete.go", 187 }}...) 188 } 189 sec.Application = opts 190 } else { 191 opts := []TemplateOpts{ 192 { 193 Name: "main", 194 Source: "asset:serverMain", 195 Target: "{{ joinFilePath .Target \"cmd\" .MainPackage }}", 196 FileName: "main.go", 197 }, 198 { 199 Name: "embedded_spec", 200 Source: "asset:swaggerJsonEmbed", 201 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 202 FileName: "embedded_spec.go", 203 }, 204 { 205 Name: "server", 206 Source: "asset:serverServer", 207 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 208 FileName: "server.go", 209 }, 210 { 211 Name: "builder", 212 Source: "asset:serverBuilder", 213 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) }}", 214 FileName: "{{ snakize (pascalize .Name) }}_api.go", 215 }, 216 { 217 Name: "doc", 218 Source: "asset:serverDoc", 219 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 220 FileName: "doc.go", 221 }, 222 } 223 if gen.ImplementationPackage != "" { 224 // Use auto configure template 225 opts = append(opts, TemplateOpts{ 226 Name: "autoconfigure", 227 Source: "asset:serverAutoconfigureapi", 228 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 229 FileName: "auto_configure_{{ (snakize (pascalize .Name)) }}.go", 230 }) 231 232 } else { 233 opts = append(opts, TemplateOpts{ 234 Name: "configure", 235 Source: "asset:serverConfigureapi", 236 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 237 FileName: "configure_{{ (snakize (pascalize .Name)) }}.go", 238 SkipExists: !gen.RegenerateConfigureAPI, 239 }) 240 } 241 sec.Application = opts 242 } 243 } 244 gen.Sections = sec 245 246 } 247 248 // MarkdownOpts for rendering a spec as markdown 249 func MarkdownOpts() *LanguageOpts { 250 opts := &LanguageOpts{} 251 opts.Init() 252 return opts 253 } 254 255 // MarkdownSectionOpts for a given opts and output file. 256 func MarkdownSectionOpts(gen *GenOpts, output string) { 257 gen.Sections.Models = nil 258 gen.Sections.OperationGroups = nil 259 gen.Sections.Operations = nil 260 gen.LanguageOpts = MarkdownOpts() 261 gen.Sections.Application = []TemplateOpts{ 262 { 263 Name: "markdowndocs", 264 Source: "asset:markdownDocs", 265 Target: filepath.Dir(output), 266 FileName: filepath.Base(output), 267 }, 268 } 269 } 270 271 // TemplateOpts allows for codegen customization 272 type TemplateOpts struct { 273 Name string `mapstructure:"name"` 274 Source string `mapstructure:"source"` 275 Target string `mapstructure:"target"` 276 FileName string `mapstructure:"file_name"` 277 SkipExists bool `mapstructure:"skip_exists"` 278 SkipFormat bool `mapstructure:"skip_format"` 279 } 280 281 // SectionOpts allows for specifying options to customize the templates used for generation 282 type SectionOpts struct { 283 Application []TemplateOpts `mapstructure:"application"` 284 Operations []TemplateOpts `mapstructure:"operations"` 285 OperationGroups []TemplateOpts `mapstructure:"operation_groups"` 286 Models []TemplateOpts `mapstructure:"models"` 287 } 288 289 // GenOptsCommon the options for the generator 290 type GenOptsCommon struct { 291 IncludeModel bool 292 IncludeValidator bool 293 IncludeHandler bool 294 IncludeParameters bool 295 IncludeResponses bool 296 IncludeURLBuilder bool 297 IncludeMain bool 298 IncludeSupport bool 299 IncludeCLi bool 300 ExcludeSpec bool 301 DumpData bool 302 ValidateSpec bool 303 FlattenOpts *analysis.FlattenOpts 304 IsClient bool 305 defaultsEnsured bool 306 PropertiesSpecOrder bool 307 StrictAdditionalProperties bool 308 AllowTemplateOverride bool 309 310 Spec string 311 APIPackage string 312 ModelPackage string 313 ServerPackage string 314 ClientPackage string 315 CliPackage string 316 CliAppName string // name of cli app. For example "dockerctl" 317 ImplementationPackage string 318 Principal string 319 PrincipalCustomIface bool // user-provided interface for Principal (non-nullable) 320 Target string // dir location where generated code is written to 321 Sections SectionOpts 322 LanguageOpts *LanguageOpts 323 TypeMapping map[string]string 324 Imports map[string]string 325 DefaultScheme string 326 DefaultProduces string 327 DefaultConsumes string 328 WithXML bool 329 TemplateDir string 330 Template string 331 RegenerateConfigureAPI bool 332 Operations []string 333 Models []string 334 Tags []string 335 StructTags []string 336 Name string 337 FlagStrategy string 338 CompatibilityMode string 339 ExistingModels string 340 Copyright string 341 SkipTagPackages bool 342 MainPackage string 343 IgnoreOperations bool 344 AllowEnumCI bool 345 StrictResponders bool 346 AcceptDefinitionsOnly bool 347 348 templates *Repository // a shallow clone of the global template repository 349 } 350 351 // CheckOpts carries out some global consistency checks on options. 352 func (g *GenOpts) CheckOpts() error { 353 if g == nil { 354 return errors.New("gen opts are required") 355 } 356 357 if !filepath.IsAbs(g.Target) { 358 if _, err := filepath.Abs(g.Target); err != nil { 359 return fmt.Errorf("could not locate target %s: %v", g.Target, err) 360 } 361 } 362 363 if filepath.IsAbs(g.ServerPackage) { 364 return fmt.Errorf("you shouldn't specify an absolute path in --server-package: %s", g.ServerPackage) 365 } 366 367 if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") { 368 return nil 369 } 370 371 pth, err := findSwaggerSpec(g.Spec) 372 if err != nil { 373 return err 374 } 375 376 // ensure spec path is absolute 377 g.Spec, err = filepath.Abs(pth) 378 if err != nil { 379 return fmt.Errorf("could not locate spec: %s", g.Spec) 380 } 381 382 return nil 383 } 384 385 // TargetPath returns the target generation path relative to the server package. 386 // This method is used by templates, e.g. with {{ .TargetPath }} 387 // 388 // Errors cases are prevented by calling CheckOpts beforehand. 389 // 390 // Example: 391 // Target: ${PWD}/tmp 392 // ServerPackage: abc/efg 393 // 394 // Server is generated in ${PWD}/tmp/abc/efg 395 // relative TargetPath returned: ../../../tmp 396 func (g *GenOpts) TargetPath() string { 397 var tgt string 398 if g.Target == "" { 399 tgt = "." // That's for windows 400 } else { 401 tgt = g.Target 402 } 403 tgtAbs, _ := filepath.Abs(tgt) 404 srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server")) 405 srvrAbs := filepath.Join(tgtAbs, srvPkg) 406 tgtRel, _ := filepath.Rel(srvrAbs, filepath.Dir(tgtAbs)) 407 tgtRel = filepath.Join(tgtRel, filepath.Base(tgtAbs)) 408 return tgtRel 409 } 410 411 // SpecPath returns the path to the spec relative to the server package. 412 // If the spec is remote keep this absolute location. 413 // 414 // If spec is not relative to server (e.g. lives on a different drive on windows), 415 // then the resolved path is absolute. 416 // 417 // This method is used by templates, e.g. with {{ .SpecPath }} 418 // 419 // Errors cases are prevented by calling CheckOpts beforehand. 420 func (g *GenOpts) SpecPath() string { 421 if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") { 422 return g.Spec 423 } 424 // Local specifications 425 specAbs, _ := filepath.Abs(g.Spec) 426 var tgt string 427 if g.Target == "" { 428 tgt = "." // That's for windows 429 } else { 430 tgt = g.Target 431 } 432 tgtAbs, _ := filepath.Abs(tgt) 433 srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server")) 434 srvAbs := filepath.Join(tgtAbs, srvPkg) 435 specRel, err := filepath.Rel(srvAbs, specAbs) 436 if err != nil { 437 return specAbs 438 } 439 return specRel 440 } 441 442 // PrincipalIsNullable indicates whether the principal type used for authentication 443 // may be used as a pointer 444 func (g *GenOpts) PrincipalIsNullable() bool { 445 debugLog("Principal: %s, %t, isnullable: %t", g.Principal, g.PrincipalCustomIface, g.Principal != iface && !g.PrincipalCustomIface) 446 return g.Principal != iface && !g.PrincipalCustomIface 447 } 448 449 // EnsureDefaults for these gen opts 450 func (g *GenOpts) EnsureDefaults() error { 451 if g.defaultsEnsured { 452 return nil 453 } 454 455 g.templates = templates.ShallowClone() 456 457 g.templates.LoadDefaults() 458 459 if g.LanguageOpts == nil { 460 g.LanguageOpts = DefaultLanguageFunc() 461 } 462 463 DefaultSectionOpts(g) 464 465 // set defaults for flattening options 466 if g.FlattenOpts == nil { 467 g.FlattenOpts = &analysis.FlattenOpts{ 468 Minimal: true, 469 Verbose: true, 470 RemoveUnused: false, 471 Expand: false, 472 } 473 } 474 475 if g.DefaultScheme == "" { 476 g.DefaultScheme = defaultScheme 477 } 478 479 if g.DefaultConsumes == "" { 480 g.DefaultConsumes = runtime.JSONMime 481 } 482 483 if g.DefaultProduces == "" { 484 g.DefaultProduces = runtime.JSONMime 485 } 486 487 // always include validator with models 488 g.IncludeValidator = true 489 490 if g.Principal == "" { 491 g.Principal = iface 492 g.PrincipalCustomIface = false 493 } 494 495 g.defaultsEnsured = true 496 return nil 497 } 498 499 func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, error) { 500 v := reflect.Indirect(reflect.ValueOf(data)) 501 fld := v.FieldByName("Name") 502 var name string 503 if fld.IsValid() { 504 log.Println("name field", fld.String()) 505 name = fld.String() 506 } 507 508 fldpack := v.FieldByName("Package") 509 pkg := g.APIPackage 510 if fldpack.IsValid() { 511 log.Println("package field", fldpack.String()) 512 pkg = fldpack.String() 513 } 514 515 var tags []string 516 tagsF := v.FieldByName("Tags") 517 if tagsF.IsValid() { 518 if tt, ok := tagsF.Interface().([]string); ok { 519 tags = tt 520 } 521 } 522 523 var useTags bool 524 useTagsF := v.FieldByName("UseTags") 525 if useTagsF.IsValid() { 526 useTags = useTagsF.Interface().(bool) 527 } 528 529 funcMap := FuncMapFunc(g.LanguageOpts) 530 531 pthTpl, err := template.New(t.Name + "-target").Funcs(funcMap).Parse(t.Target) 532 if err != nil { 533 return "", "", err 534 } 535 536 fNameTpl, err := template.New(t.Name + "-filename").Funcs(funcMap).Parse(t.FileName) 537 if err != nil { 538 return "", "", err 539 } 540 541 d := struct { 542 Name, CliAppName, Package, APIPackage, ServerPackage, ClientPackage, CliPackage, ModelPackage, MainPackage, Target string 543 Tags []string 544 UseTags bool 545 Context interface{} 546 }{ 547 Name: name, 548 CliAppName: g.CliAppName, 549 Package: pkg, 550 APIPackage: g.APIPackage, 551 ServerPackage: g.ServerPackage, 552 ClientPackage: g.ClientPackage, 553 CliPackage: g.CliPackage, 554 ModelPackage: g.ModelPackage, 555 MainPackage: g.MainPackage, 556 Target: g.Target, 557 Tags: tags, 558 UseTags: useTags, 559 Context: data, 560 } 561 562 var pthBuf bytes.Buffer 563 if e := pthTpl.Execute(&pthBuf, d); e != nil { 564 return "", "", e 565 } 566 567 var fNameBuf bytes.Buffer 568 if e := fNameTpl.Execute(&fNameBuf, d); e != nil { 569 return "", "", e 570 } 571 return pthBuf.String(), fileName(fNameBuf.String()), nil 572 } 573 574 func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) { 575 var templ *template.Template 576 577 if strings.HasPrefix(strings.ToLower(t.Source), "asset:") { 578 tt, err := g.templates.Get(strings.TrimPrefix(t.Source, "asset:")) 579 if err != nil { 580 return nil, err 581 } 582 templ = tt 583 } 584 585 if templ == nil { 586 // try to load from repository (and enable dependencies) 587 name := swag.ToJSONName(strings.TrimSuffix(t.Source, ".gotmpl")) 588 tt, err := g.templates.Get(name) 589 if err == nil { 590 templ = tt 591 } 592 } 593 594 if templ == nil { 595 // try to load template from disk, in TemplateDir if specified 596 // (dependencies resolution is limited to preloaded assets) 597 var templateFile string 598 if g.TemplateDir != "" { 599 templateFile = filepath.Join(g.TemplateDir, t.Source) 600 } else { 601 templateFile = t.Source 602 } 603 content, err := os.ReadFile(templateFile) 604 if err != nil { 605 return nil, fmt.Errorf("error while opening %s template file: %v", templateFile, err) 606 } 607 tt, err := template.New(t.Source).Funcs(FuncMapFunc(g.LanguageOpts)).Parse(string(content)) 608 if err != nil { 609 return nil, fmt.Errorf("template parsing failed on template %s: %v", t.Name, err) 610 } 611 templ = tt 612 } 613 614 if templ == nil { 615 return nil, fmt.Errorf("template %q not found", t.Source) 616 } 617 618 var tBuf bytes.Buffer 619 if err := templ.Execute(&tBuf, data); err != nil { 620 return nil, fmt.Errorf("template execution failed for template %s: %v", t.Name, err) 621 } 622 log.Printf("executed template %s", t.Source) 623 624 return tBuf.Bytes(), nil 625 } 626 627 // Render template and write generated source code 628 // generated code is reformatted ("linted"), which gives an 629 // additional level of checking. If this step fails, the generated 630 // code is still dumped, for template debugging purposes. 631 func (g *GenOpts) write(t *TemplateOpts, data interface{}) error { 632 dir, fname, err := g.location(t, data) 633 if err != nil { 634 return fmt.Errorf("failed to resolve template location for template %s: %v", t.Name, err) 635 } 636 637 if t.SkipExists && fileExists(dir, fname) { 638 debugLog("skipping generation of %s because it already exists and skip_exist directive is set for %s", 639 filepath.Join(dir, fname), t.Name) 640 return nil 641 } 642 643 log.Printf("creating generated file %q in %q as %s", fname, dir, t.Name) 644 content, err := g.render(t, data) 645 if err != nil { 646 return fmt.Errorf("failed rendering template data for %s: %v", t.Name, err) 647 } 648 649 if dir != "" { 650 _, exists := os.Stat(dir) 651 if os.IsNotExist(exists) { 652 debugLog("creating directory %q for \"%s\"", dir, t.Name) 653 // Directory settings consistent with file privileges. 654 // Environment's umask may alter this setup 655 if e := os.MkdirAll(dir, 0755); e != nil { 656 return e 657 } 658 } 659 } 660 661 // Conditionally format the code, unless the user wants to skip 662 formatted := content 663 var writeerr error 664 665 if !t.SkipFormat { 666 formatted, err = g.LanguageOpts.FormatContent(filepath.Join(dir, fname), content) 667 if err != nil { 668 log.Printf("source formatting failed on template-generated source (%q for %s). Check that your template produces valid code", filepath.Join(dir, fname), t.Name) 669 writeerr = os.WriteFile(filepath.Join(dir, fname), content, 0644) // #nosec 670 if writeerr != nil { 671 return fmt.Errorf("failed to write (unformatted) file %q in %q: %v", fname, dir, writeerr) 672 } 673 log.Printf("unformatted generated source %q has been dumped for template debugging purposes. DO NOT build on this source!", fname) 674 return fmt.Errorf("source formatting on generated source %q failed: %v", t.Name, err) 675 } 676 } 677 678 writeerr = os.WriteFile(filepath.Join(dir, fname), formatted, 0644) // #nosec 679 if writeerr != nil { 680 return fmt.Errorf("failed to write file %q in %q: %v", fname, dir, writeerr) 681 } 682 return err 683 } 684 685 func fileName(in string) string { 686 ext := filepath.Ext(in) 687 return swag.ToFileName(strings.TrimSuffix(in, ext)) + ext 688 } 689 690 func (g *GenOpts) shouldRenderApp(t *TemplateOpts, app *GenApp) bool { 691 switch swag.ToFileName(swag.ToGoName(t.Name)) { 692 case "main": 693 return g.IncludeMain 694 case "embedded_spec": 695 return !g.ExcludeSpec 696 default: 697 return true 698 } 699 } 700 701 func (g *GenOpts) shouldRenderOperations() bool { 702 return g.IncludeHandler || g.IncludeParameters || g.IncludeResponses 703 } 704 705 func (g *GenOpts) renderApplication(app *GenApp) error { 706 log.Printf("rendering %d templates for application %s", len(g.Sections.Application), app.Name) 707 for _, tp := range g.Sections.Application { 708 templ := tp 709 if !g.shouldRenderApp(&templ, app) { 710 continue 711 } 712 if err := g.write(&templ, app); err != nil { 713 return err 714 } 715 } 716 return nil 717 } 718 719 func (g *GenOpts) renderOperationGroup(gg *GenOperationGroup) error { 720 log.Printf("rendering %d templates for operation group %s", len(g.Sections.OperationGroups), g.Name) 721 for _, tp := range g.Sections.OperationGroups { 722 templ := tp 723 if !g.shouldRenderOperations() { 724 continue 725 } 726 727 if err := g.write(&templ, gg); err != nil { 728 return err 729 } 730 } 731 return nil 732 } 733 734 func (g *GenOpts) renderOperation(gg *GenOperation) error { 735 log.Printf("rendering %d templates for operation %s", len(g.Sections.Operations), g.Name) 736 for _, tp := range g.Sections.Operations { 737 templ := tp 738 if !g.shouldRenderOperations() { 739 continue 740 } 741 742 if err := g.write(&templ, gg); err != nil { 743 return err 744 } 745 } 746 return nil 747 } 748 749 func (g *GenOpts) renderDefinition(gg *GenDefinition) error { 750 log.Printf("rendering %d templates for model %s", len(g.Sections.Models), gg.Name) 751 for _, tp := range g.Sections.Models { 752 templ := tp 753 if !g.IncludeModel { 754 continue 755 } 756 757 if err := g.write(&templ, gg); err != nil { 758 return err 759 } 760 } 761 return nil 762 } 763 764 func (g *GenOptsCommon) setTemplates() error { 765 if g.Template != "" { 766 // set contrib templates 767 if err := g.templates.LoadContrib(g.Template); err != nil { 768 return err 769 } 770 } 771 772 g.templates.SetAllowOverride(g.AllowTemplateOverride) 773 774 if g.TemplateDir != "" { 775 // set custom templates 776 if err := g.templates.LoadDir(g.TemplateDir); err != nil { 777 return err 778 } 779 } 780 return nil 781 } 782 783 // defaultImports produces a default map for imports with models 784 func (g *GenOpts) defaultImports() map[string]string { 785 baseImport := g.LanguageOpts.baseImport(g.Target) 786 defaultImports := make(map[string]string, 50) 787 788 var modelsAlias, importPath string 789 if g.ExistingModels == "" { 790 // generated models 791 importPath = path.Join( 792 baseImport, 793 g.LanguageOpts.ManglePackagePath(g.ModelPackage, defaultModelsTarget)) 794 modelsAlias = g.LanguageOpts.ManglePackageName(g.ModelPackage, defaultModelsTarget) 795 } else { 796 // external models 797 importPath = g.LanguageOpts.ManglePackagePath(g.ExistingModels, "") 798 modelsAlias = path.Base(defaultModelsTarget) 799 } 800 defaultImports[modelsAlias] = importPath 801 802 // resolve model representing an authenticated principal 803 alias, _, target := g.resolvePrincipal() 804 if alias == "" || target == g.ModelPackage || path.Base(target) == modelsAlias { 805 // if principal is specified with the models generation package, do not import any extra package 806 return defaultImports 807 } 808 809 if pth, _ := path.Split(target); pth != "" { 810 // if principal is specified with a path, assume this is a fully qualified package and generate this import 811 defaultImports[alias] = target 812 } else { 813 // if principal is specified with a relative path (no "/", e.g. internal.Principal), assume it is located in generated target 814 defaultImports[alias] = path.Join(baseImport, target) 815 } 816 return defaultImports 817 } 818 819 // initImports produces a default map for import with the specified root for operations 820 func (g *GenOpts) initImports(operationsPackage string) map[string]string { 821 baseImport := g.LanguageOpts.baseImport(g.Target) 822 823 imports := make(map[string]string, 50) 824 imports[g.LanguageOpts.ManglePackageName(operationsPackage, defaultOperationsTarget)] = path.Join( 825 baseImport, 826 g.LanguageOpts.ManglePackagePath(operationsPackage, defaultOperationsTarget)) 827 return imports 828 } 829 830 // PrincipalAlias returns an aliased type to the principal 831 func (g *GenOpts) PrincipalAlias() string { 832 _, principal, _ := g.resolvePrincipal() 833 return principal 834 } 835 836 func (g *GenOpts) resolvePrincipal() (string, string, string) { 837 dotLocation := strings.LastIndex(g.Principal, ".") 838 if dotLocation < 0 { 839 return "", g.Principal, "" 840 } 841 842 // handle possible conflicts with injected principal package 843 // NOTE(fred): we do not check here for conflicts with packages created from operation tags, only standard imports 844 alias := deconflictPrincipal(importAlias(g.Principal[:dotLocation])) 845 return alias, alias + g.Principal[dotLocation:], g.Principal[:dotLocation] 846 } 847 848 func fileExists(target, name string) bool { 849 _, err := os.Stat(filepath.Join(target, name)) 850 return !os.IsNotExist(err) 851 } 852 853 func gatherModels(specDoc *loads.Document, modelNames []string) (map[string]spec.Schema, error) { 854 modelNames = pruneEmpty(modelNames) 855 models, mnc := make(map[string]spec.Schema), len(modelNames) 856 defs := specDoc.Spec().Definitions 857 858 if mnc > 0 { 859 var unknownModels []string 860 for _, m := range modelNames { 861 _, ok := defs[m] 862 if !ok { 863 unknownModels = append(unknownModels, m) 864 } 865 } 866 if len(unknownModels) != 0 { 867 return nil, fmt.Errorf("unknown models: %s", strings.Join(unknownModels, ", ")) 868 } 869 } 870 for k, v := range defs { 871 if mnc == 0 { 872 models[k] = v 873 } 874 for _, nm := range modelNames { 875 if k == nm { 876 models[k] = v 877 } 878 } 879 } 880 return models, nil 881 } 882 883 // titleOrDefault infers a name for the app from the title of the spec 884 func titleOrDefault(specDoc *loads.Document, name, defaultName string) string { 885 if strings.TrimSpace(name) == "" { 886 if specDoc.Spec().Info != nil && strings.TrimSpace(specDoc.Spec().Info.Title) != "" { 887 name = specDoc.Spec().Info.Title 888 } else { 889 name = defaultName 890 } 891 } 892 return swag.ToGoName(name) 893 } 894 895 func mainNameOrDefault(specDoc *loads.Document, name, defaultName string) string { 896 // *_test won't do as main server name 897 return strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test") 898 } 899 900 func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string { 901 // *_test won't do as app names 902 name = strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test") 903 if name == "" { 904 name = swag.ToGoName(defaultName) 905 } 906 return name 907 } 908 909 type opRef struct { 910 Method string 911 Path string 912 Key string 913 ID string 914 Op *spec.Operation 915 } 916 917 type opRefs []opRef 918 919 func (o opRefs) Len() int { return len(o) } 920 func (o opRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 921 func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key } 922 923 func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]opRef { 924 operationIDs = pruneEmpty(operationIDs) 925 var oprefs opRefs 926 927 for method, pathItem := range specDoc.Operations() { 928 for path, operation := range pathItem { 929 vv := *operation 930 oprefs = append(oprefs, opRef{ 931 Key: swag.ToGoName(strings.ToLower(method) + " " + swag.ToHumanNameTitle(path)), 932 Method: method, 933 Path: path, 934 ID: vv.ID, 935 Op: &vv, 936 }) 937 } 938 } 939 940 sort.Sort(oprefs) 941 942 operations := make(map[string]opRef) 943 for _, opr := range oprefs { 944 nm := opr.ID 945 if nm == "" { 946 nm = opr.Key 947 } 948 949 oo, found := operations[nm] 950 if found && oo.Method != opr.Method && oo.Path != opr.Path { 951 nm = opr.Key 952 } 953 if len(operationIDs) == 0 || swag.ContainsStrings(operationIDs, opr.ID) || swag.ContainsStrings(operationIDs, nm) { 954 opr.ID = nm 955 opr.Op.ID = nm 956 operations[nm] = opr 957 } 958 } 959 960 return operations 961 } 962 963 func pruneEmpty(in []string) (out []string) { 964 for _, v := range in { 965 if v != "" { 966 out = append(out, v) 967 } 968 } 969 return 970 } 971 972 func trimBOM(in string) string { 973 return strings.Trim(in, "\xef\xbb\xbf") 974 } 975 976 // gatherSecuritySchemes produces a sorted representation from a map of spec security schemes 977 func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string, nullable bool) (security GenSecuritySchemes) { 978 for scheme, req := range securitySchemes { 979 isOAuth2 := strings.ToLower(req.Type) == "oauth2" 980 scopes := make([]string, 0, len(req.Scopes)) 981 genScopes := make([]GenSecurityScope, 0, len(req.Scopes)) 982 if isOAuth2 { 983 for k, v := range req.Scopes { 984 scopes = append(scopes, k) 985 genScopes = append(genScopes, GenSecurityScope{Name: k, Description: v}) 986 } 987 sort.Strings(scopes) 988 } 989 990 security = append(security, GenSecurityScheme{ 991 AppName: appName, 992 ID: scheme, 993 ReceiverName: receiver, 994 Name: req.Name, 995 IsBasicAuth: strings.ToLower(req.Type) == "basic", 996 IsAPIKeyAuth: strings.ToLower(req.Type) == "apikey", 997 IsOAuth2: isOAuth2, 998 Scopes: scopes, 999 ScopesDesc: genScopes, 1000 Principal: principal, 1001 Source: req.In, 1002 // from original spec 1003 Description: req.Description, 1004 Type: strings.ToLower(req.Type), 1005 In: req.In, 1006 Flow: req.Flow, 1007 AuthorizationURL: req.AuthorizationURL, 1008 TokenURL: req.TokenURL, 1009 Extensions: req.Extensions, 1010 1011 PrincipalIsNullable: nullable, 1012 }) 1013 } 1014 sort.Sort(security) 1015 return 1016 } 1017 1018 // securityRequirements just clones the original SecurityRequirements from either the spec 1019 // or an operation, without any modification. This is used to generate documentation. 1020 func securityRequirements(orig []map[string][]string) (result []analysis.SecurityRequirement) { 1021 for _, r := range orig { 1022 for k, v := range r { 1023 result = append(result, analysis.SecurityRequirement{Name: k, Scopes: v}) 1024 } 1025 } 1026 // TODO(fred): sort this for stable generation 1027 return 1028 } 1029 1030 // gatherExtraSchemas produces a sorted list of extra schemas. 1031 // 1032 // ExtraSchemas are inlined types rendered in the same model file. 1033 func gatherExtraSchemas(extraMap map[string]GenSchema) (extras GenSchemaList) { 1034 var extraKeys []string 1035 for k := range extraMap { 1036 extraKeys = append(extraKeys, k) 1037 } 1038 sort.Strings(extraKeys) 1039 for _, k := range extraKeys { 1040 // figure out if top level validations are needed 1041 p := extraMap[k] 1042 p.HasValidations = shallowValidationLookup(p) 1043 extras = append(extras, p) 1044 } 1045 return 1046 } 1047 1048 func getExtraSchemes(ext spec.Extensions) []string { 1049 if ess, ok := ext.GetStringSlice(xSchemes); ok { 1050 return ess 1051 } 1052 return nil 1053 } 1054 1055 func gatherURISchemes(swsp *spec.Swagger, operation spec.Operation) ([]string, []string) { 1056 var extraSchemes []string 1057 extraSchemes = append(extraSchemes, getExtraSchemes(operation.Extensions)...) 1058 extraSchemes = concatUnique(getExtraSchemes(swsp.Extensions), extraSchemes) 1059 sort.Strings(extraSchemes) 1060 1061 schemes := concatUnique(swsp.Schemes, operation.Schemes) 1062 sort.Strings(schemes) 1063 1064 return schemes, extraSchemes 1065 } 1066 1067 func dumpData(data interface{}) error { 1068 bb, err := json.MarshalIndent(data, "", " ") 1069 if err != nil { 1070 return err 1071 } 1072 fmt.Fprintln(os.Stdout, string(bb)) 1073 return nil 1074 } 1075 1076 func importAlias(pkg string) string { 1077 _, k := path.Split(pkg) 1078 return k 1079 } 1080 1081 // concatUnique concatenate collections of strings with deduplication 1082 func concatUnique(collections ...[]string) []string { 1083 resultSet := make(map[string]struct{}) 1084 for _, c := range collections { 1085 for _, i := range c { 1086 if _, ok := resultSet[i]; !ok { 1087 resultSet[i] = struct{}{} 1088 } 1089 } 1090 } 1091 result := make([]string, 0, len(resultSet)) 1092 for k := range resultSet { 1093 result = append(result, k) 1094 } 1095 return result 1096 }