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