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