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