github.com/ryanbennettvoid/go-swagger@v0.18.1-0.20190104015444-3854bbbe2392/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 "errors" 20 "fmt" 21 "io/ioutil" 22 "log" 23 "os" 24 "path" 25 "path/filepath" 26 "reflect" 27 "regexp" 28 "sort" 29 "strings" 30 "text/template" 31 32 swaggererrors "github.com/go-openapi/errors" 33 34 "github.com/go-openapi/analysis" 35 "github.com/go-openapi/loads" 36 "github.com/go-openapi/spec" 37 "github.com/go-openapi/strfmt" 38 "github.com/go-openapi/swag" 39 "github.com/go-openapi/validate" 40 "golang.org/x/tools/imports" 41 ) 42 43 //go:generate go-bindata -mode 420 -modtime 1482416923 -pkg=generator -ignore=.*\.sw? -ignore=.*\.md ./templates/... 44 45 // LanguageOpts to describe a language to the code generator 46 type LanguageOpts struct { 47 ReservedWords []string 48 BaseImportFunc func(string) string `json:"-"` 49 reservedWordsSet map[string]struct{} 50 initialized bool 51 formatFunc func(string, []byte) ([]byte, error) 52 fileNameFunc func(string) string 53 } 54 55 // Init the language option 56 func (l *LanguageOpts) Init() { 57 if !l.initialized { 58 l.initialized = true 59 l.reservedWordsSet = make(map[string]struct{}) 60 for _, rw := range l.ReservedWords { 61 l.reservedWordsSet[rw] = struct{}{} 62 } 63 } 64 } 65 66 // MangleName makes sure a reserved word gets a safe name 67 func (l *LanguageOpts) MangleName(name, suffix string) string { 68 if _, ok := l.reservedWordsSet[swag.ToFileName(name)]; !ok { 69 return name 70 } 71 return strings.Join([]string{name, suffix}, "_") 72 } 73 74 // MangleVarName makes sure a reserved word gets a safe name 75 func (l *LanguageOpts) MangleVarName(name string) string { 76 nm := swag.ToVarName(name) 77 if _, ok := l.reservedWordsSet[nm]; !ok { 78 return nm 79 } 80 return nm + "Var" 81 } 82 83 // MangleFileName makes sure a file name gets a safe name 84 func (l *LanguageOpts) MangleFileName(name string) string { 85 if l.fileNameFunc != nil { 86 return l.fileNameFunc(name) 87 } 88 return swag.ToFileName(name) 89 } 90 91 // ManglePackageName makes sure a package gets a safe name. 92 // In case of a file system path (e.g. name contains "/" or "\" on Windows), this return only the last element. 93 func (l *LanguageOpts) ManglePackageName(name, suffix string) string { 94 if name == "" { 95 return suffix 96 } 97 pth := filepath.ToSlash(filepath.Clean(name)) // preserve path 98 _, pkg := path.Split(pth) // drop path 99 return l.MangleName(swag.ToFileName(pkg), suffix) 100 } 101 102 // ManglePackagePath makes sure a full package path gets a safe name. 103 // Only the last part of the path is altered. 104 func (l *LanguageOpts) ManglePackagePath(name string, suffix string) string { 105 if name == "" { 106 return suffix 107 } 108 target := filepath.ToSlash(filepath.Clean(name)) // preserve path 109 parts := strings.Split(target, "/") 110 parts[len(parts)-1] = l.ManglePackageName(parts[len(parts)-1], suffix) 111 return strings.Join(parts, "/") 112 } 113 114 // FormatContent formats a file with a language specific formatter 115 func (l *LanguageOpts) FormatContent(name string, content []byte) ([]byte, error) { 116 if l.formatFunc != nil { 117 return l.formatFunc(name, content) 118 } 119 return content, nil 120 } 121 122 func (l *LanguageOpts) baseImport(tgt string) string { 123 if l.BaseImportFunc != nil { 124 return l.BaseImportFunc(tgt) 125 } 126 return "" 127 } 128 129 var golang = GoLangOpts() 130 131 // GoLangOpts for rendering items as golang code 132 func GoLangOpts() *LanguageOpts { 133 var goOtherReservedSuffixes = map[string]bool{ 134 // see: 135 // https://golang.org/src/go/build/syslist.go 136 // https://golang.org/doc/install/source#environment 137 138 // goos 139 "android": true, 140 "darwin": true, 141 "dragonfly": true, 142 "freebsd": true, 143 "js": true, 144 "linux": true, 145 "nacl": true, 146 "netbsd": true, 147 "openbsd": true, 148 "plan9": true, 149 "solaris": true, 150 "windows": true, 151 "zos": true, 152 153 // arch 154 "386": true, 155 "amd64": true, 156 "amd64p32": true, 157 "arm": true, 158 "armbe": true, 159 "arm64": true, 160 "arm64be": true, 161 "mips": true, 162 "mipsle": true, 163 "mips64": true, 164 "mips64le": true, 165 "mips64p32": true, 166 "mips64p32le": true, 167 "ppc": true, 168 "ppc64": true, 169 "ppc64le": true, 170 "riscv": true, 171 "riscv64": true, 172 "s390": true, 173 "s390x": true, 174 "sparc": true, 175 "sparc64": true, 176 "wasm": true, 177 178 // other reserved suffixes 179 "test": true, 180 } 181 182 opts := new(LanguageOpts) 183 opts.ReservedWords = []string{ 184 "break", "default", "func", "interface", "select", 185 "case", "defer", "go", "map", "struct", 186 "chan", "else", "goto", "package", "switch", 187 "const", "fallthrough", "if", "range", "type", 188 "continue", "for", "import", "return", "var", 189 } 190 opts.formatFunc = func(ffn string, content []byte) ([]byte, error) { 191 opts := new(imports.Options) 192 opts.TabIndent = true 193 opts.TabWidth = 2 194 opts.Fragment = true 195 opts.Comments = true 196 return imports.Process(ffn, content, opts) 197 } 198 opts.fileNameFunc = func(name string) string { 199 // whenever a generated file name ends with a suffix 200 // that is meaningful to go build, adds a "swagger" 201 // suffix 202 parts := strings.Split(swag.ToFileName(name), "_") 203 if goOtherReservedSuffixes[parts[len(parts)-1]] { 204 // file name ending with a reserved arch or os name 205 // are appended an innocuous suffix "swagger" 206 parts = append(parts, "swagger") 207 } 208 return strings.Join(parts, "_") 209 } 210 211 opts.BaseImportFunc = func(tgt string) string { 212 tgt = filepath.Clean(tgt) 213 // On Windows, filepath.Abs("") behaves differently than on Unix. 214 // Windows: yields an error, since Abs() does not know the volume. 215 // UNIX: returns current working directory 216 if tgt == "" { 217 tgt = "." 218 } 219 tgtAbsPath, err := filepath.Abs(tgt) 220 if err != nil { 221 log.Fatalf("could not evaluate base import path with target \"%s\": %v", tgt, err) 222 } 223 224 var tgtAbsPathExtended string 225 tgtAbsPathExtended, err = filepath.EvalSymlinks(tgtAbsPath) 226 if err != nil { 227 log.Fatalf("could not evaluate base import path with target \"%s\" (with symlink resolution): %v", tgtAbsPath, err) 228 } 229 230 gopath := os.Getenv("GOPATH") 231 if gopath == "" { 232 gopath = filepath.Join(os.Getenv("HOME"), "go") 233 } 234 235 var pth string 236 for _, gp := range filepath.SplitList(gopath) { 237 // EvalSymLinks also calls the Clean 238 gopathExtended, err := filepath.EvalSymlinks(gp) 239 if err != nil { 240 log.Fatalln(err) 241 } 242 gopathExtended = filepath.Join(gopathExtended, "src") 243 gp = filepath.Join(gp, "src") 244 245 // At this stage we have expanded and unexpanded target path. GOPATH is fully expanded. 246 // Expanded means symlink free. 247 // We compare both types of targetpath<s> with gopath. 248 // If any one of them coincides with gopath , it is imperative that 249 // target path lies inside gopath. How? 250 // - Case 1: Irrespective of symlinks paths coincide. Both non-expanded paths. 251 // - Case 2: Symlink in target path points to location inside GOPATH. (Expanded Target Path) 252 // - Case 3: Symlink in target path points to directory outside GOPATH (Unexpanded target path) 253 254 // Case 1: - Do nothing case. If non-expanded paths match just genrate base import path as if 255 // there are no symlinks. 256 257 // Case 2: - Symlink in target path points to location inside GOPATH. (Expanded Target Path) 258 // First if will fail. Second if will succeed. 259 260 // Case 3: - Symlink in target path points to directory outside GOPATH (Unexpanded target path) 261 // First if will succeed and break. 262 263 //compares non expanded path for both 264 if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gp); ok { 265 pth = relativepath 266 break 267 } 268 269 // Compares non-expanded target path 270 if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPath, gopathExtended); ok { 271 pth = relativepath 272 break 273 } 274 275 // Compares expanded target path. 276 if ok, relativepath := checkPrefixAndFetchRelativePath(tgtAbsPathExtended, gopathExtended); ok { 277 pth = relativepath 278 break 279 } 280 281 } 282 283 mod, goModuleAbsPath, err := tryResolveModule(tgtAbsPath) 284 switch { 285 case err != nil: 286 log.Fatalf("Failed to resolve module using go.mod file: %s", err) 287 case mod != "": 288 relTgt := relPathToRelGoPath(goModuleAbsPath, tgtAbsPath) 289 if !strings.HasSuffix(mod, relTgt) { 290 return mod + relTgt 291 } 292 return mod 293 } 294 295 if pth == "" { 296 log.Fatalln("target must reside inside a location in the $GOPATH/src or be a module") 297 } 298 return pth 299 } 300 opts.Init() 301 return opts 302 } 303 304 var moduleRe = regexp.MustCompile(`module[ \t]+([^\s]+)`) 305 306 // resolveGoModFile walks up the directory tree starting from 'dir' until it 307 // finds a go.mod file. If go.mod is found it will return the related file 308 // object. If no go.mod file is found it will return an error. 309 func resolveGoModFile(dir string) (*os.File, string, error) { 310 goModPath := filepath.Join(dir, "go.mod") 311 f, err := os.Open(goModPath) 312 if err != nil { 313 if os.IsNotExist(err) && dir != filepath.Dir(dir) { 314 return resolveGoModFile(filepath.Dir(dir)) 315 } 316 return nil, "", err 317 } 318 return f, dir, nil 319 } 320 321 // relPathToRelGoPath takes a relative os path and returns the relative go 322 // package path. For unix nothing will change but for windows \ will be 323 // converted to /. 324 func relPathToRelGoPath(modAbsPath, absPath string) string { 325 if absPath == "." { 326 return "" 327 } 328 329 path := strings.TrimPrefix(absPath, modAbsPath) 330 pathItems := strings.Split(path, string(filepath.Separator)) 331 return strings.Join(pathItems, "/") 332 } 333 334 func tryResolveModule(baseTargetPath string) (string, string, error) { 335 f, goModAbsPath, err := resolveGoModFile(baseTargetPath) 336 switch { 337 case os.IsNotExist(err): 338 return "", "", nil 339 case err != nil: 340 return "", "", err 341 } 342 343 src, err := ioutil.ReadAll(f) 344 if err != nil { 345 return "", "", err 346 } 347 348 match := moduleRe.FindSubmatch(src) 349 if len(match) != 2 { 350 return "", "", nil 351 } 352 353 return string(match[1]), goModAbsPath, nil 354 } 355 356 func findSwaggerSpec(nm string) (string, error) { 357 specs := []string{"swagger.json", "swagger.yml", "swagger.yaml"} 358 if nm != "" { 359 specs = []string{nm} 360 } 361 var name string 362 for _, nn := range specs { 363 f, err := os.Stat(nn) 364 if err != nil && !os.IsNotExist(err) { 365 return "", err 366 } 367 if err != nil && os.IsNotExist(err) { 368 continue 369 } 370 if f.IsDir() { 371 return "", fmt.Errorf("%s is a directory", nn) 372 } 373 name = nn 374 break 375 } 376 if name == "" { 377 return "", errors.New("couldn't find a swagger spec") 378 } 379 return name, nil 380 } 381 382 // DefaultSectionOpts for a given opts, this is used when no config file is passed 383 // and uses the embedded templates when no local override can be found 384 func DefaultSectionOpts(gen *GenOpts) { 385 sec := gen.Sections 386 if len(sec.Models) == 0 { 387 sec.Models = []TemplateOpts{ 388 { 389 Name: "definition", 390 Source: "asset:model", 391 Target: "{{ joinFilePath .Target (toPackagePath .ModelPackage) }}", 392 FileName: "{{ (snakize (pascalize .Name)) }}.go", 393 }, 394 } 395 } 396 397 if len(sec.Operations) == 0 { 398 if gen.IsClient { 399 sec.Operations = []TemplateOpts{ 400 { 401 Name: "parameters", 402 Source: "asset:clientParameter", 403 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}", 404 FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go", 405 }, 406 { 407 Name: "responses", 408 Source: "asset:clientResponse", 409 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}", 410 FileName: "{{ (snakize (pascalize .Name)) }}_responses.go", 411 }, 412 } 413 414 } else { 415 ops := []TemplateOpts{} 416 if gen.IncludeParameters { 417 ops = append(ops, TemplateOpts{ 418 Name: "parameters", 419 Source: "asset:serverParameter", 420 Target: "{{ if eq (len .Tags) 1 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 421 FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go", 422 }) 423 } 424 if gen.IncludeURLBuilder { 425 ops = append(ops, TemplateOpts{ 426 Name: "urlbuilder", 427 Source: "asset:serverUrlbuilder", 428 Target: "{{ if eq (len .Tags) 1 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 429 FileName: "{{ (snakize (pascalize .Name)) }}_urlbuilder.go", 430 }) 431 } 432 if gen.IncludeResponses { 433 ops = append(ops, TemplateOpts{ 434 Name: "responses", 435 Source: "asset:serverResponses", 436 Target: "{{ if eq (len .Tags) 1 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 437 FileName: "{{ (snakize (pascalize .Name)) }}_responses.go", 438 }) 439 } 440 if gen.IncludeHandler { 441 ops = append(ops, TemplateOpts{ 442 Name: "handler", 443 Source: "asset:serverOperation", 444 Target: "{{ if eq (len .Tags) 1 }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}", 445 FileName: "{{ (snakize (pascalize .Name)) }}.go", 446 }) 447 } 448 sec.Operations = ops 449 } 450 } 451 452 if len(sec.OperationGroups) == 0 { 453 if gen.IsClient { 454 sec.OperationGroups = []TemplateOpts{ 455 { 456 Name: "client", 457 Source: "asset:clientClient", 458 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Name)}}", 459 FileName: "{{ (snakize (pascalize .Name)) }}_client.go", 460 }, 461 } 462 } else { 463 sec.OperationGroups = []TemplateOpts{} 464 } 465 } 466 467 if len(sec.Application) == 0 { 468 if gen.IsClient { 469 sec.Application = []TemplateOpts{ 470 { 471 Name: "facade", 472 Source: "asset:clientFacade", 473 Target: "{{ joinFilePath .Target (toPackagePath .ClientPackage) }}", 474 FileName: "{{ snakize .Name }}Client.go", 475 }, 476 } 477 } else { 478 sec.Application = []TemplateOpts{ 479 { 480 Name: "configure", 481 Source: "asset:serverConfigureapi", 482 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 483 FileName: "configure_{{ (snakize (pascalize .Name)) }}.go", 484 SkipExists: !gen.RegenerateConfigureAPI, 485 }, 486 { 487 Name: "main", 488 Source: "asset:serverMain", 489 Target: "{{ joinFilePath .Target \"cmd\" (dasherize (pascalize .Name)) }}-server", 490 FileName: "main.go", 491 }, 492 { 493 Name: "embedded_spec", 494 Source: "asset:swaggerJsonEmbed", 495 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 496 FileName: "embedded_spec.go", 497 }, 498 { 499 Name: "server", 500 Source: "asset:serverServer", 501 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 502 FileName: "server.go", 503 }, 504 { 505 Name: "builder", 506 Source: "asset:serverBuilder", 507 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) }}", 508 FileName: "{{ snakize (pascalize .Name) }}_api.go", 509 }, 510 { 511 Name: "doc", 512 Source: "asset:serverDoc", 513 Target: "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}", 514 FileName: "doc.go", 515 }, 516 } 517 } 518 } 519 gen.Sections = sec 520 521 } 522 523 // TemplateOpts allows 524 type TemplateOpts struct { 525 Name string `mapstructure:"name"` 526 Source string `mapstructure:"source"` 527 Target string `mapstructure:"target"` 528 FileName string `mapstructure:"file_name"` 529 SkipExists bool `mapstructure:"skip_exists"` 530 SkipFormat bool `mapstructure:"skip_format"` 531 } 532 533 // SectionOpts allows for specifying options to customize the templates used for generation 534 type SectionOpts struct { 535 Application []TemplateOpts `mapstructure:"application"` 536 Operations []TemplateOpts `mapstructure:"operations"` 537 OperationGroups []TemplateOpts `mapstructure:"operation_groups"` 538 Models []TemplateOpts `mapstructure:"models"` 539 } 540 541 // GenOpts the options for the generator 542 type GenOpts struct { 543 IncludeModel bool 544 IncludeValidator bool 545 IncludeHandler bool 546 IncludeParameters bool 547 IncludeResponses bool 548 IncludeURLBuilder bool 549 IncludeMain bool 550 IncludeSupport bool 551 ExcludeSpec bool 552 DumpData bool 553 ValidateSpec bool 554 FlattenOpts *analysis.FlattenOpts 555 IsClient bool 556 defaultsEnsured bool 557 558 Spec string 559 APIPackage string 560 ModelPackage string 561 ServerPackage string 562 ClientPackage string 563 Principal string 564 Target string 565 Sections SectionOpts 566 LanguageOpts *LanguageOpts 567 TypeMapping map[string]string 568 Imports map[string]string 569 DefaultScheme string 570 DefaultProduces string 571 DefaultConsumes string 572 TemplateDir string 573 Template string 574 RegenerateConfigureAPI bool 575 Operations []string 576 Models []string 577 Tags []string 578 Name string 579 FlagStrategy string 580 CompatibilityMode string 581 ExistingModels string 582 Copyright string 583 } 584 585 // CheckOpts carries out some global consistency checks on options. 586 // 587 // At the moment, these checks simply protect TargetPath() and SpecPath() 588 // functions. More checks may be added here. 589 func (g *GenOpts) CheckOpts() error { 590 if !filepath.IsAbs(g.Target) { 591 if _, err := filepath.Abs(g.Target); err != nil { 592 return fmt.Errorf("could not locate target %s: %v", g.Target, err) 593 } 594 } 595 if filepath.IsAbs(g.ServerPackage) { 596 return fmt.Errorf("you shouldn't specify an absolute path in --server-package: %s", g.ServerPackage) 597 } 598 if !filepath.IsAbs(g.Spec) && !strings.HasPrefix(g.Spec, "http://") && !strings.HasPrefix(g.Spec, "https://") { 599 if _, err := filepath.Abs(g.Spec); err != nil { 600 return fmt.Errorf("could not locate spec: %s", g.Spec) 601 } 602 } 603 return nil 604 } 605 606 // TargetPath returns the target generation path relative to the server package. 607 // This method is used by templates, e.g. with {{ .TargetPath }} 608 // 609 // Errors cases are prevented by calling CheckOpts beforehand. 610 // 611 // Example: 612 // Target: ${PWD}/tmp 613 // ServerPackage: abc/efg 614 // 615 // Server is generated in ${PWD}/tmp/abc/efg 616 // relative TargetPath returned: ../../../tmp 617 // 618 func (g *GenOpts) TargetPath() string { 619 var tgt string 620 if g.Target == "" { 621 tgt = "." // That's for windows 622 } else { 623 tgt = g.Target 624 } 625 tgtAbs, _ := filepath.Abs(tgt) 626 srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server")) 627 srvrAbs := filepath.Join(tgtAbs, srvPkg) 628 tgtRel, _ := filepath.Rel(srvrAbs, filepath.Dir(tgtAbs)) 629 tgtRel = filepath.Join(tgtRel, filepath.Base(tgtAbs)) 630 return tgtRel 631 } 632 633 // SpecPath returns the path to the spec relative to the server package. 634 // If the spec is remote keep this absolute location. 635 // 636 // If spec is not relative to server (e.g. lives on a different drive on windows), 637 // then the resolved path is absolute. 638 // 639 // This method is used by templates, e.g. with {{ .SpecPath }} 640 // 641 // Errors cases are prevented by calling CheckOpts beforehand. 642 func (g *GenOpts) SpecPath() string { 643 if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") { 644 return g.Spec 645 } 646 // Local specifications 647 specAbs, _ := filepath.Abs(g.Spec) 648 var tgt string 649 if g.Target == "" { 650 tgt = "." // That's for windows 651 } else { 652 tgt = g.Target 653 } 654 tgtAbs, _ := filepath.Abs(tgt) 655 srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server")) 656 srvAbs := filepath.Join(tgtAbs, srvPkg) 657 specRel, err := filepath.Rel(srvAbs, specAbs) 658 if err != nil { 659 return specAbs 660 } 661 return specRel 662 } 663 664 // EnsureDefaults for these gen opts 665 func (g *GenOpts) EnsureDefaults() error { 666 if g.defaultsEnsured { 667 return nil 668 } 669 DefaultSectionOpts(g) 670 if g.LanguageOpts == nil { 671 g.LanguageOpts = GoLangOpts() 672 } 673 // set defaults for flattening options 674 g.FlattenOpts = &analysis.FlattenOpts{ 675 Minimal: true, 676 Verbose: true, 677 RemoveUnused: false, 678 Expand: false, 679 } 680 g.defaultsEnsured = true 681 return nil 682 } 683 684 func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, error) { 685 v := reflect.Indirect(reflect.ValueOf(data)) 686 fld := v.FieldByName("Name") 687 var name string 688 if fld.IsValid() { 689 log.Println("name field", fld.String()) 690 name = fld.String() 691 } 692 693 fldpack := v.FieldByName("Package") 694 pkg := g.APIPackage 695 if fldpack.IsValid() { 696 log.Println("package field", fldpack.String()) 697 pkg = fldpack.String() 698 } 699 700 var tags []string 701 tagsF := v.FieldByName("Tags") 702 if tagsF.IsValid() { 703 tags = tagsF.Interface().([]string) 704 } 705 706 pthTpl, err := template.New(t.Name + "-target").Funcs(FuncMap).Parse(t.Target) 707 if err != nil { 708 return "", "", err 709 } 710 711 fNameTpl, err := template.New(t.Name + "-filename").Funcs(FuncMap).Parse(t.FileName) 712 if err != nil { 713 return "", "", err 714 } 715 716 d := struct { 717 Name, Package, APIPackage, ServerPackage, ClientPackage, ModelPackage, Target string 718 Tags []string 719 }{ 720 Name: name, 721 Package: pkg, 722 APIPackage: g.APIPackage, 723 ServerPackage: g.ServerPackage, 724 ClientPackage: g.ClientPackage, 725 ModelPackage: g.ModelPackage, 726 Target: g.Target, 727 Tags: tags, 728 } 729 730 // pretty.Println(data) 731 var pthBuf bytes.Buffer 732 if e := pthTpl.Execute(&pthBuf, d); e != nil { 733 return "", "", e 734 } 735 736 var fNameBuf bytes.Buffer 737 if e := fNameTpl.Execute(&fNameBuf, d); e != nil { 738 return "", "", e 739 } 740 return pthBuf.String(), fileName(fNameBuf.String()), nil 741 } 742 743 func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) { 744 var templ *template.Template 745 746 if strings.HasPrefix(strings.ToLower(t.Source), "asset:") { 747 tt, err := templates.Get(strings.TrimPrefix(t.Source, "asset:")) 748 if err != nil { 749 return nil, err 750 } 751 templ = tt 752 } 753 754 if templ == nil { 755 // try to load from repository (and enable dependencies) 756 name := swag.ToJSONName(strings.TrimSuffix(t.Source, ".gotmpl")) 757 tt, err := templates.Get(name) 758 if err == nil { 759 templ = tt 760 } 761 } 762 763 if templ == nil { 764 // try to load template from disk, in TemplateDir if specified 765 // (dependencies resolution is limited to preloaded assets) 766 var templateFile string 767 if g.TemplateDir != "" { 768 templateFile = filepath.Join(g.TemplateDir, t.Source) 769 } else { 770 templateFile = t.Source 771 } 772 content, err := ioutil.ReadFile(templateFile) 773 if err != nil { 774 return nil, fmt.Errorf("error while opening %s template file: %v", templateFile, err) 775 } 776 tt, err := template.New(t.Source).Funcs(FuncMap).Parse(string(content)) 777 if err != nil { 778 return nil, fmt.Errorf("template parsing failed on template %s: %v", t.Name, err) 779 } 780 templ = tt 781 } 782 783 if templ == nil { 784 return nil, fmt.Errorf("template %q not found", t.Source) 785 } 786 787 var tBuf bytes.Buffer 788 if err := templ.Execute(&tBuf, data); err != nil { 789 return nil, fmt.Errorf("template execution failed for template %s: %v", t.Name, err) 790 } 791 log.Printf("executed template %s", t.Source) 792 793 return tBuf.Bytes(), nil 794 } 795 796 // Render template and write generated source code 797 // generated code is reformatted ("linted"), which gives an 798 // additional level of checking. If this step fails, the generated 799 // code is still dumped, for template debugging purposes. 800 func (g *GenOpts) write(t *TemplateOpts, data interface{}) error { 801 dir, fname, err := g.location(t, data) 802 if err != nil { 803 return fmt.Errorf("failed to resolve template location for template %s: %v", t.Name, err) 804 } 805 806 if t.SkipExists && fileExists(dir, fname) { 807 debugLog("skipping generation of %s because it already exists and skip_exist directive is set for %s", 808 filepath.Join(dir, fname), t.Name) 809 return nil 810 } 811 812 log.Printf("creating generated file %q in %q as %s", fname, dir, t.Name) 813 content, err := g.render(t, data) 814 if err != nil { 815 return fmt.Errorf("failed rendering template data for %s: %v", t.Name, err) 816 } 817 818 if dir != "" { 819 _, exists := os.Stat(dir) 820 if os.IsNotExist(exists) { 821 debugLog("creating directory %q for \"%s\"", dir, t.Name) 822 // Directory settings consistent with file privileges. 823 // Environment's umask may alter this setup 824 if e := os.MkdirAll(dir, 0755); e != nil { 825 return e 826 } 827 } 828 } 829 830 // Conditionally format the code, unless the user wants to skip 831 formatted := content 832 var writeerr error 833 834 if !t.SkipFormat { 835 formatted, err = g.LanguageOpts.FormatContent(fname, content) 836 if err != nil { 837 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) 838 writeerr = ioutil.WriteFile(filepath.Join(dir, fname), content, 0644) 839 if writeerr != nil { 840 return fmt.Errorf("failed to write (unformatted) file %q in %q: %v", fname, dir, writeerr) 841 } 842 log.Printf("unformatted generated source %q has been dumped for template debugging purposes. DO NOT build on this source!", fname) 843 return fmt.Errorf("source formatting on generated source %q failed: %v", t.Name, err) 844 } 845 } 846 847 writeerr = ioutil.WriteFile(filepath.Join(dir, fname), formatted, 0644) 848 if writeerr != nil { 849 return fmt.Errorf("failed to write file %q in %q: %v", fname, dir, writeerr) 850 } 851 return err 852 } 853 854 func fileName(in string) string { 855 ext := filepath.Ext(in) 856 return swag.ToFileName(strings.TrimSuffix(in, ext)) + ext 857 } 858 859 func (g *GenOpts) shouldRenderApp(t *TemplateOpts, app *GenApp) bool { 860 switch swag.ToFileName(swag.ToGoName(t.Name)) { 861 case "main": 862 return g.IncludeMain 863 case "embedded_spec": 864 return !g.ExcludeSpec 865 default: 866 return true 867 } 868 } 869 870 func (g *GenOpts) shouldRenderOperations() bool { 871 return g.IncludeHandler || g.IncludeParameters || g.IncludeResponses 872 } 873 874 func (g *GenOpts) renderApplication(app *GenApp) error { 875 log.Printf("rendering %d templates for application %s", len(g.Sections.Application), app.Name) 876 for _, templ := range g.Sections.Application { 877 if !g.shouldRenderApp(&templ, app) { 878 continue 879 } 880 if err := g.write(&templ, app); err != nil { 881 return err 882 } 883 } 884 return nil 885 } 886 887 func (g *GenOpts) renderOperationGroup(gg *GenOperationGroup) error { 888 log.Printf("rendering %d templates for operation group %s", len(g.Sections.OperationGroups), g.Name) 889 for _, templ := range g.Sections.OperationGroups { 890 if !g.shouldRenderOperations() { 891 continue 892 } 893 894 if err := g.write(&templ, gg); err != nil { 895 return err 896 } 897 } 898 return nil 899 } 900 901 func (g *GenOpts) renderOperation(gg *GenOperation) error { 902 log.Printf("rendering %d templates for operation %s", len(g.Sections.Operations), g.Name) 903 for _, templ := range g.Sections.Operations { 904 if !g.shouldRenderOperations() { 905 continue 906 } 907 908 if err := g.write(&templ, gg); err != nil { 909 return err 910 } 911 } 912 return nil 913 } 914 915 func (g *GenOpts) renderDefinition(gg *GenDefinition) error { 916 log.Printf("rendering %d templates for model %s", len(g.Sections.Models), gg.Name) 917 for _, templ := range g.Sections.Models { 918 if !g.IncludeModel { 919 continue 920 } 921 922 if err := g.write(&templ, gg); err != nil { 923 return err 924 } 925 } 926 return nil 927 } 928 929 func validateSpec(path string, doc *loads.Document) (err error) { 930 if doc == nil { 931 if path, doc, err = loadSpec(path); err != nil { 932 return err 933 } 934 } 935 936 result := validate.Spec(doc, strfmt.Default) 937 if result == nil { 938 return nil 939 } 940 941 str := fmt.Sprintf("The swagger spec at %q is invalid against swagger specification %s. see errors :\n", path, doc.Version()) 942 for _, desc := range result.(*swaggererrors.CompositeError).Errors { 943 str += fmt.Sprintf("- %s\n", desc) 944 } 945 return errors.New(str) 946 } 947 948 func loadSpec(specFile string) (string, *loads.Document, error) { 949 // find swagger spec document, verify it exists 950 specPath := specFile 951 var err error 952 if !strings.HasPrefix(specPath, "http") { 953 specPath, err = findSwaggerSpec(specFile) 954 if err != nil { 955 return "", nil, err 956 } 957 } 958 959 // load swagger spec 960 specDoc, err := loads.Spec(specPath) 961 if err != nil { 962 return "", nil, err 963 } 964 return specPath, specDoc, nil 965 } 966 967 func fileExists(target, name string) bool { 968 _, err := os.Stat(filepath.Join(target, name)) 969 return !os.IsNotExist(err) 970 } 971 972 func gatherModels(specDoc *loads.Document, modelNames []string) (map[string]spec.Schema, error) { 973 models, mnc := make(map[string]spec.Schema), len(modelNames) 974 defs := specDoc.Spec().Definitions 975 976 if mnc > 0 { 977 var unknownModels []string 978 for _, m := range modelNames { 979 _, ok := defs[m] 980 if !ok { 981 unknownModels = append(unknownModels, m) 982 } 983 } 984 if len(unknownModels) != 0 { 985 return nil, fmt.Errorf("unknown models: %s", strings.Join(unknownModels, ", ")) 986 } 987 } 988 for k, v := range defs { 989 if mnc == 0 { 990 models[k] = v 991 } 992 for _, nm := range modelNames { 993 if k == nm { 994 models[k] = v 995 } 996 } 997 } 998 return models, nil 999 } 1000 1001 func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string { 1002 if strings.TrimSpace(name) == "" { 1003 if specDoc.Spec().Info != nil && strings.TrimSpace(specDoc.Spec().Info.Title) != "" { 1004 name = specDoc.Spec().Info.Title 1005 } else { 1006 name = defaultName 1007 } 1008 } 1009 return strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(swag.ToGoName(name), "Test"), "API"), "Test") 1010 } 1011 1012 func containsString(names []string, name string) bool { 1013 for _, nm := range names { 1014 if nm == name { 1015 return true 1016 } 1017 } 1018 return false 1019 } 1020 1021 type opRef struct { 1022 Method string 1023 Path string 1024 Key string 1025 ID string 1026 Op *spec.Operation 1027 } 1028 1029 type opRefs []opRef 1030 1031 func (o opRefs) Len() int { return len(o) } 1032 func (o opRefs) Swap(i, j int) { o[i], o[j] = o[j], o[i] } 1033 func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key } 1034 1035 func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]opRef { 1036 var oprefs opRefs 1037 1038 for method, pathItem := range specDoc.Operations() { 1039 for path, operation := range pathItem { 1040 // nm := ensureUniqueName(operation.ID, method, path, operations) 1041 vv := *operation 1042 oprefs = append(oprefs, opRef{ 1043 Key: swag.ToGoName(strings.ToLower(method) + " " + path), 1044 Method: method, 1045 Path: path, 1046 ID: vv.ID, 1047 Op: &vv, 1048 }) 1049 } 1050 } 1051 1052 sort.Sort(oprefs) 1053 1054 operations := make(map[string]opRef) 1055 for _, opr := range oprefs { 1056 nm := opr.ID 1057 if nm == "" { 1058 nm = opr.Key 1059 } 1060 1061 oo, found := operations[nm] 1062 if found && oo.Method != opr.Method && oo.Path != opr.Path { 1063 nm = opr.Key 1064 } 1065 if len(operationIDs) == 0 || containsString(operationIDs, opr.ID) || containsString(operationIDs, nm) { 1066 opr.ID = nm 1067 opr.Op.ID = nm 1068 operations[nm] = opr 1069 } 1070 } 1071 1072 return operations 1073 } 1074 1075 func pascalize(arg string) string { 1076 if len(arg) == 0 || arg[0] > '9' { 1077 return swag.ToGoName(arg) 1078 } 1079 if arg[0] == '+' { 1080 return swag.ToGoName("Plus " + arg[1:]) 1081 } 1082 if arg[0] == '-' { 1083 return swag.ToGoName("Minus " + arg[1:]) 1084 } 1085 1086 return swag.ToGoName("Nr " + arg) 1087 } 1088 1089 func pruneEmpty(in []string) (out []string) { 1090 for _, v := range in { 1091 if v != "" { 1092 out = append(out, v) 1093 } 1094 } 1095 return 1096 } 1097 1098 func trimBOM(in string) string { 1099 return strings.Trim(in, "\xef\xbb\xbf") 1100 } 1101 1102 func validateAndFlattenSpec(opts *GenOpts, specDoc *loads.Document) (*loads.Document, error) { 1103 1104 var err error 1105 1106 // Validate if needed 1107 if opts.ValidateSpec { 1108 log.Printf("validating spec %v", opts.Spec) 1109 if erv := validateSpec(opts.Spec, specDoc); erv != nil { 1110 return specDoc, erv 1111 } 1112 } 1113 1114 // Restore spec to original 1115 opts.Spec, specDoc, err = loadSpec(opts.Spec) 1116 if err != nil { 1117 return nil, err 1118 } 1119 1120 absBasePath := specDoc.SpecFilePath() 1121 if !filepath.IsAbs(absBasePath) { 1122 cwd, _ := os.Getwd() 1123 absBasePath = filepath.Join(cwd, absBasePath) 1124 } 1125 1126 // Some preprocessing is required before codegen 1127 // 1128 // This ensures at least that $ref's in the spec document are canonical, 1129 // i.e all $ref are local to this file and point to some uniquely named definition. 1130 // 1131 // Default option is to ensure minimal flattening of $ref, bundling remote $refs and relocating arbitrary JSON 1132 // pointers as definitions. 1133 // This preprocessing may introduce duplicate names (e.g. remote $ref with same name). In this case, a definition 1134 // suffixed with "OAIGen" is produced. 1135 // 1136 // Full flattening option farther transforms the spec by moving every complex object (e.g. with some properties) 1137 // as a standalone definition. 1138 // 1139 // Eventually, an "expand spec" option is available. It is essentially useful for testing purposes. 1140 // 1141 // NOTE(fredbi): spec expansion may produce some unsupported constructs and is not yet protected against the 1142 // following cases: 1143 // - polymorphic types generation may fail with expansion (expand destructs the reuse intent of the $ref in allOf) 1144 // - name duplicates may occur and result in compilation failures 1145 // The right place to fix these shortcomings is go-openapi/analysis. 1146 1147 opts.FlattenOpts.BasePath = absBasePath // BasePath must be absolute 1148 opts.FlattenOpts.Spec = analysis.New(specDoc.Spec()) 1149 1150 var preprocessingOption string 1151 if opts.FlattenOpts.Expand { 1152 preprocessingOption = "expand" 1153 } else if opts.FlattenOpts.Minimal { 1154 preprocessingOption = "minimal flattening" 1155 } else { 1156 preprocessingOption = "full flattening" 1157 } 1158 log.Printf("preprocessing spec with option: %s", preprocessingOption) 1159 1160 if err = analysis.Flatten(*opts.FlattenOpts); err != nil { 1161 return nil, err 1162 } 1163 1164 // yields the preprocessed spec document 1165 return specDoc, nil 1166 } 1167 1168 // gatherSecuritySchemes produces a sorted representation from a map of spec security schemes 1169 func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string) (security GenSecuritySchemes) { 1170 for scheme, req := range securitySchemes { 1171 isOAuth2 := strings.ToLower(req.Type) == "oauth2" 1172 var scopes []string 1173 if isOAuth2 { 1174 for k := range req.Scopes { 1175 scopes = append(scopes, k) 1176 } 1177 } 1178 sort.Strings(scopes) 1179 1180 security = append(security, GenSecurityScheme{ 1181 AppName: appName, 1182 ID: scheme, 1183 ReceiverName: receiver, 1184 Name: req.Name, 1185 IsBasicAuth: strings.ToLower(req.Type) == "basic", 1186 IsAPIKeyAuth: strings.ToLower(req.Type) == "apikey", 1187 IsOAuth2: isOAuth2, 1188 Scopes: scopes, 1189 Principal: principal, 1190 Source: req.In, 1191 // from original spec 1192 Description: req.Description, 1193 Type: strings.ToLower(req.Type), 1194 In: req.In, 1195 Flow: req.Flow, 1196 AuthorizationURL: req.AuthorizationURL, 1197 TokenURL: req.TokenURL, 1198 Extensions: req.Extensions, 1199 }) 1200 } 1201 sort.Sort(security) 1202 return 1203 } 1204 1205 // gatherExtraSchemas produces a sorted list of extra schemas. 1206 // 1207 // ExtraSchemas are inlined types rendered in the same model file. 1208 func gatherExtraSchemas(extraMap map[string]GenSchema) (extras GenSchemaList) { 1209 var extraKeys []string 1210 for k := range extraMap { 1211 extraKeys = append(extraKeys, k) 1212 } 1213 sort.Strings(extraKeys) 1214 for _, k := range extraKeys { 1215 // figure out if top level validations are needed 1216 p := extraMap[k] 1217 p.HasValidations = shallowValidationLookup(p) 1218 extras = append(extras, p) 1219 } 1220 return 1221 } 1222 1223 func sharedValidationsFromSimple(v spec.CommonValidations, isRequired bool) (sh sharedValidations) { 1224 sh = sharedValidations{ 1225 Required: isRequired, 1226 Maximum: v.Maximum, 1227 ExclusiveMaximum: v.ExclusiveMaximum, 1228 Minimum: v.Minimum, 1229 ExclusiveMinimum: v.ExclusiveMinimum, 1230 MaxLength: v.MaxLength, 1231 MinLength: v.MinLength, 1232 Pattern: v.Pattern, 1233 MaxItems: v.MaxItems, 1234 MinItems: v.MinItems, 1235 UniqueItems: v.UniqueItems, 1236 MultipleOf: v.MultipleOf, 1237 Enum: v.Enum, 1238 } 1239 return 1240 } 1241 1242 func sharedValidationsFromSchema(v spec.Schema, isRequired bool) (sh sharedValidations) { 1243 sh = sharedValidations{ 1244 Required: isRequired, 1245 Maximum: v.Maximum, 1246 ExclusiveMaximum: v.ExclusiveMaximum, 1247 Minimum: v.Minimum, 1248 ExclusiveMinimum: v.ExclusiveMinimum, 1249 MaxLength: v.MaxLength, 1250 MinLength: v.MinLength, 1251 Pattern: v.Pattern, 1252 MaxItems: v.MaxItems, 1253 MinItems: v.MinItems, 1254 UniqueItems: v.UniqueItems, 1255 MultipleOf: v.MultipleOf, 1256 Enum: v.Enum, 1257 } 1258 return 1259 }