github.com/emreu/go-swagger@v0.22.1/generator/support.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 "regexp" 28 goruntime "runtime" 29 "sort" 30 "strings" 31 32 yaml "gopkg.in/yaml.v2" 33 34 "github.com/go-openapi/analysis" 35 "github.com/go-openapi/loads" 36 "github.com/go-openapi/runtime" 37 "github.com/go-openapi/spec" 38 "github.com/go-openapi/swag" 39 ) 40 41 // GenerateServer generates a server application 42 func GenerateServer(name string, modelNames, operationIDs []string, opts *GenOpts) error { 43 generator, err := newAppGenerator(name, modelNames, operationIDs, opts) 44 if err != nil { 45 return err 46 } 47 return generator.Generate() 48 } 49 50 // GenerateSupport generates the supporting files for an API 51 func GenerateSupport(name string, modelNames, operationIDs []string, opts *GenOpts) error { 52 generator, err := newAppGenerator(name, modelNames, operationIDs, opts) 53 if err != nil { 54 return err 55 } 56 return generator.GenerateSupport(nil) 57 } 58 59 func newAppGenerator(name string, modelNames, operationIDs []string, opts *GenOpts) (*appGenerator, error) { 60 if opts == nil { 61 return nil, errors.New("gen opts are required") 62 } 63 if err := opts.CheckOpts(); err != nil { 64 return nil, err 65 } 66 67 templates.LoadDefaults() 68 if opts.Template != "" { 69 if err := templates.LoadContrib(opts.Template); err != nil { 70 return nil, err 71 } 72 } 73 74 templates.SetAllowOverride(opts.AllowTemplateOverride) 75 76 if opts.TemplateDir != "" { 77 if err := templates.LoadDir(opts.TemplateDir); err != nil { 78 return nil, err 79 } 80 } 81 82 // Load the spec 83 var err error 84 var specDoc *loads.Document 85 86 opts.Spec, err = findSwaggerSpec(opts.Spec) 87 if err != nil { 88 return nil, err 89 } 90 91 if !filepath.IsAbs(opts.Spec) { 92 cwd, _ := os.Getwd() 93 opts.Spec = filepath.Join(cwd, opts.Spec) 94 } 95 96 if opts.PropertiesSpecOrder { 97 opts.Spec = WithAutoXOrder(opts.Spec) 98 } 99 100 opts.Spec, specDoc, err = loadSpec(opts.Spec) 101 if err != nil { 102 return nil, err 103 } 104 105 specDoc, err = validateAndFlattenSpec(opts, specDoc) 106 if err != nil { 107 return nil, err 108 } 109 110 analyzed := analysis.New(specDoc.Spec()) 111 112 models, err := gatherModels(specDoc, modelNames) 113 if err != nil { 114 return nil, err 115 } 116 117 operations := gatherOperations(analyzed, operationIDs) 118 if len(operations) == 0 { 119 return nil, errors.New("no operations were selected") 120 } 121 122 defaultScheme := opts.DefaultScheme 123 if defaultScheme == "" { 124 defaultScheme = "http" 125 } 126 127 defaultProduces := opts.DefaultProduces 128 if defaultProduces == "" { 129 defaultProduces = runtime.JSONMime 130 } 131 132 defaultConsumes := opts.DefaultConsumes 133 if defaultConsumes == "" { 134 defaultConsumes = runtime.JSONMime 135 } 136 137 opts.Name = appNameOrDefault(specDoc, name, "swagger") 138 apiPackage := opts.LanguageOpts.ManglePackagePath(opts.APIPackage, "api") 139 return &appGenerator{ 140 Name: opts.Name, 141 Receiver: "o", 142 SpecDoc: specDoc, 143 Analyzed: analyzed, 144 Models: models, 145 Operations: operations, 146 Target: opts.Target, 147 DumpData: opts.DumpData, 148 Package: opts.LanguageOpts.ManglePackageName(apiPackage, "api"), 149 APIPackage: apiPackage, 150 ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, "definitions"), 151 ServerPackage: opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, "server"), 152 ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, "client"), 153 OperationsPackage: filepath.Join(opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, "server"), apiPackage), 154 Principal: opts.Principal, 155 DefaultScheme: defaultScheme, 156 DefaultProduces: defaultProduces, 157 DefaultConsumes: defaultConsumes, 158 GenOpts: opts, 159 }, nil 160 } 161 162 type appGenerator struct { 163 Name string 164 Receiver string 165 SpecDoc *loads.Document 166 Analyzed *analysis.Spec 167 Package string 168 APIPackage string 169 ModelsPackage string 170 ServerPackage string 171 ClientPackage string 172 OperationsPackage string 173 Principal string 174 Models map[string]spec.Schema 175 Operations map[string]opRef 176 Target string 177 DumpData bool 178 DefaultScheme string 179 DefaultProduces string 180 DefaultConsumes string 181 GenOpts *GenOpts 182 } 183 184 func WithAutoXOrder(specPath string) string { 185 lookFor := func(ele interface{}, key string) (yaml.MapSlice, bool) { 186 if slice, ok := ele.(yaml.MapSlice); ok { 187 for _, v := range slice { 188 if v.Key == key { 189 if slice, ok := v.Value.(yaml.MapSlice); ok { 190 return slice, ok 191 } 192 } 193 } 194 } 195 return nil, false 196 } 197 198 var addXOrder func(interface{}) 199 addXOrder = func(element interface{}) { 200 if props, ok := lookFor(element, "properties"); ok { 201 for i, prop := range props { 202 if pSlice, ok := prop.Value.(yaml.MapSlice); ok { 203 isObject := false 204 xOrderIndex := -1 //Find if x-order already exists 205 206 for i, v := range pSlice { 207 if v.Key == "type" && v.Value == object { 208 isObject = true 209 } 210 if v.Key == xOrder { 211 xOrderIndex = i 212 break 213 } 214 } 215 216 if xOrderIndex > -1 { //Override existing x-order 217 pSlice[xOrderIndex] = yaml.MapItem{Key: xOrder, Value: i} 218 } else { // append new x-order 219 pSlice = append(pSlice, yaml.MapItem{Key: xOrder, Value: i}) 220 } 221 prop.Value = pSlice 222 props[i] = prop 223 224 if isObject { 225 addXOrder(pSlice) 226 } 227 } 228 } 229 } 230 } 231 232 yamlDoc, err := swag.YAMLData(specPath) 233 if err != nil { 234 panic(err) 235 } 236 237 if defs, ok := lookFor(yamlDoc, "definitions"); ok { 238 for _, def := range defs { 239 addXOrder(def.Value) 240 } 241 } 242 243 addXOrder(yamlDoc) 244 245 out, err := yaml.Marshal(yamlDoc) 246 if err != nil { 247 panic(err) 248 } 249 250 tmpFile, err := ioutil.TempFile("", filepath.Base(specPath)) 251 if err != nil { 252 panic(err) 253 } 254 if err := ioutil.WriteFile(tmpFile.Name(), out, 0); err != nil { 255 panic(err) 256 } 257 return tmpFile.Name() 258 } 259 260 // 1. Checks if the child path and parent path coincide. 261 // 2. If they do return child path relative to parent path. 262 // 3. Everything else return false 263 func checkPrefixAndFetchRelativePath(childpath string, parentpath string) (bool, string) { 264 // Windows (local) file systems - NTFS, as well as FAT and variants 265 // are case insensitive. 266 cp, pp := childpath, parentpath 267 if goruntime.GOOS == "windows" { 268 cp = strings.ToLower(cp) 269 pp = strings.ToLower(pp) 270 } 271 272 if strings.HasPrefix(cp, pp) { 273 pth, err := filepath.Rel(parentpath, childpath) 274 if err != nil { 275 log.Fatalln(err) 276 } 277 return true, pth 278 } 279 280 return false, "" 281 282 } 283 284 func (a *appGenerator) Generate() error { 285 286 app, err := a.makeCodegenApp() 287 if err != nil { 288 return err 289 } 290 291 if a.DumpData { 292 bb, err := json.MarshalIndent(app, "", " ") 293 if err != nil { 294 return err 295 } 296 fmt.Fprintln(os.Stdout, string(bb)) 297 return nil 298 } 299 300 // NOTE: relative to previous implem with chan. 301 // IPC removed concurrent execution because of the FuncMap that is being shared 302 // templates are now lazy loaded so there is concurrent map access I can't guard 303 if a.GenOpts.IncludeModel { 304 log.Printf("rendering %d models", len(app.Models)) 305 for _, mod := range app.Models { 306 modCopy := mod 307 modCopy.IncludeValidator = true // a.GenOpts.IncludeValidator 308 modCopy.IncludeModel = true 309 if err := a.GenOpts.renderDefinition(&modCopy); err != nil { 310 return err 311 } 312 } 313 } 314 315 if a.GenOpts.IncludeHandler { 316 log.Printf("rendering %d operation groups (tags)", app.OperationGroups.Len()) 317 for _, opg := range app.OperationGroups { 318 opgCopy := opg 319 log.Printf("rendering %d operations for %s", opg.Operations.Len(), opg.Name) 320 for _, op := range opgCopy.Operations { 321 opCopy := op 322 323 if err := a.GenOpts.renderOperation(&opCopy); err != nil { 324 return err 325 } 326 } 327 // Optional OperationGroups templates generation 328 opGroup := opg 329 opGroup.DefaultImports = app.DefaultImports 330 if err := a.GenOpts.renderOperationGroup(&opGroup); err != nil { 331 return fmt.Errorf("error while rendering operation group: %v", err) 332 } 333 } 334 } 335 336 if a.GenOpts.IncludeSupport { 337 log.Printf("rendering support") 338 if err := a.GenerateSupport(&app); err != nil { 339 return err 340 } 341 } 342 return nil 343 } 344 345 func (a *appGenerator) GenerateSupport(ap *GenApp) error { 346 app := ap 347 if ap == nil { 348 ca, err := a.makeCodegenApp() 349 if err != nil { 350 return err 351 } 352 app = &ca 353 } 354 baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target) 355 importPath := path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, "")) 356 app.DefaultImports = append( 357 app.DefaultImports, 358 path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.ServerPackage, "")), 359 importPath, 360 ) 361 362 return a.GenOpts.renderApplication(app) 363 } 364 365 var mediaTypeNames = map[*regexp.Regexp]string{ 366 regexp.MustCompile("application/.*json"): "json", 367 regexp.MustCompile("application/.*yaml"): "yaml", 368 regexp.MustCompile("application/.*protobuf"): "protobuf", 369 regexp.MustCompile("application/.*capnproto"): "capnproto", 370 regexp.MustCompile("application/.*thrift"): "thrift", 371 regexp.MustCompile("(?:application|text)/.*xml"): "xml", 372 regexp.MustCompile("text/.*markdown"): "markdown", 373 regexp.MustCompile("text/.*html"): "html", 374 regexp.MustCompile("text/.*csv"): "csv", 375 regexp.MustCompile("text/.*tsv"): "tsv", 376 regexp.MustCompile("text/.*javascript"): "js", 377 regexp.MustCompile("text/.*css"): "css", 378 regexp.MustCompile("text/.*plain"): "txt", 379 regexp.MustCompile("application/.*octet-stream"): "bin", 380 regexp.MustCompile("application/.*tar"): "tar", 381 regexp.MustCompile("application/.*gzip"): "gzip", 382 regexp.MustCompile("application/.*gz"): "gzip", 383 regexp.MustCompile("application/.*raw-stream"): "bin", 384 regexp.MustCompile("application/x-www-form-urlencoded"): "urlform", 385 regexp.MustCompile("multipart/form-data"): "multipartform", 386 } 387 388 var knownProducers = map[string]string{ 389 "json": "runtime.JSONProducer()", 390 "yaml": "yamlpc.YAMLProducer()", 391 "xml": "runtime.XMLProducer()", 392 "txt": "runtime.TextProducer()", 393 "bin": "runtime.ByteStreamProducer()", 394 "urlform": "runtime.DiscardProducer", 395 "multipartform": "runtime.DiscardProducer", 396 } 397 398 var knownConsumers = map[string]string{ 399 "json": "runtime.JSONConsumer()", 400 "yaml": "yamlpc.YAMLConsumer()", 401 "xml": "runtime.XMLConsumer()", 402 "txt": "runtime.TextConsumer()", 403 "bin": "runtime.ByteStreamConsumer()", 404 "urlform": "runtime.DiscardConsumer", 405 "multipartform": "runtime.DiscardConsumer", 406 } 407 408 func getSerializer(sers []GenSerGroup, ext string) (*GenSerGroup, bool) { 409 for i := range sers { 410 s := &sers[i] 411 if s.Name == ext { 412 return s, true 413 } 414 } 415 return nil, false 416 } 417 418 func mediaTypeName(tn string) (string, bool) { 419 for k, v := range mediaTypeNames { 420 if k.MatchString(tn) { 421 return v, true 422 } 423 } 424 return "", false 425 } 426 427 func (a *appGenerator) makeConsumes() (consumes GenSerGroups, consumesJSON bool) { 428 reqCons := a.Analyzed.RequiredConsumes() 429 sort.Strings(reqCons) 430 for _, cons := range reqCons { 431 cn, ok := mediaTypeName(cons) 432 if !ok { 433 nm := swag.ToJSONName(cons) 434 ser := GenSerializer{ 435 AppName: a.Name, 436 ReceiverName: a.Receiver, 437 Name: nm, 438 MediaType: cons, 439 Implementation: "", 440 } 441 442 consumes = append(consumes, GenSerGroup{ 443 AppName: ser.AppName, 444 ReceiverName: ser.ReceiverName, 445 Name: ser.Name, 446 MediaType: cons, 447 AllSerializers: []GenSerializer{ser}, 448 Implementation: ser.Implementation, 449 }) 450 continue 451 } 452 nm := swag.ToJSONName(cn) 453 if nm == "json" { 454 consumesJSON = true 455 } 456 457 if ser, ok := getSerializer(consumes, cn); ok { 458 ser.AllSerializers = append(ser.AllSerializers, GenSerializer{ 459 AppName: ser.AppName, 460 ReceiverName: ser.ReceiverName, 461 Name: ser.Name, 462 MediaType: cons, 463 Implementation: knownConsumers[nm], 464 }) 465 sort.Sort(ser.AllSerializers) 466 continue 467 } 468 469 ser := GenSerializer{ 470 AppName: a.Name, 471 ReceiverName: a.Receiver, 472 Name: nm, 473 MediaType: cons, 474 Implementation: knownConsumers[nm], 475 } 476 477 consumes = append(consumes, GenSerGroup{ 478 AppName: ser.AppName, 479 ReceiverName: ser.ReceiverName, 480 Name: ser.Name, 481 MediaType: cons, 482 AllSerializers: []GenSerializer{ser}, 483 Implementation: ser.Implementation, 484 }) 485 } 486 if len(consumes) == 0 { 487 consumes = append(consumes, GenSerGroup{ 488 AppName: a.Name, 489 ReceiverName: a.Receiver, 490 Name: "json", 491 MediaType: runtime.JSONMime, 492 AllSerializers: []GenSerializer{{ 493 AppName: a.Name, 494 ReceiverName: a.Receiver, 495 Name: "json", 496 MediaType: runtime.JSONMime, 497 Implementation: knownConsumers["json"], 498 }}, 499 Implementation: knownConsumers["json"], 500 }) 501 consumesJSON = true 502 } 503 sort.Sort(consumes) 504 return 505 } 506 507 func (a *appGenerator) makeProduces() (produces GenSerGroups, producesJSON bool) { 508 reqProds := a.Analyzed.RequiredProduces() 509 sort.Strings(reqProds) 510 for _, prod := range reqProds { 511 pn, ok := mediaTypeName(prod) 512 if !ok { 513 nm := swag.ToJSONName(prod) 514 ser := GenSerializer{ 515 AppName: a.Name, 516 ReceiverName: a.Receiver, 517 Name: nm, 518 MediaType: prod, 519 Implementation: "", 520 } 521 produces = append(produces, GenSerGroup{ 522 AppName: ser.AppName, 523 ReceiverName: ser.ReceiverName, 524 Name: ser.Name, 525 MediaType: prod, 526 Implementation: ser.Implementation, 527 AllSerializers: []GenSerializer{ser}, 528 }) 529 continue 530 } 531 nm := swag.ToJSONName(pn) 532 if nm == "json" { 533 producesJSON = true 534 } 535 536 if ser, ok := getSerializer(produces, pn); ok { 537 ser.AllSerializers = append(ser.AllSerializers, GenSerializer{ 538 AppName: ser.AppName, 539 ReceiverName: ser.ReceiverName, 540 Name: ser.Name, 541 MediaType: prod, 542 Implementation: knownProducers[nm], 543 }) 544 sort.Sort(ser.AllSerializers) 545 continue 546 } 547 548 ser := GenSerializer{ 549 AppName: a.Name, 550 ReceiverName: a.Receiver, 551 Name: nm, 552 MediaType: prod, 553 Implementation: knownProducers[nm], 554 } 555 produces = append(produces, GenSerGroup{ 556 AppName: ser.AppName, 557 ReceiverName: ser.ReceiverName, 558 Name: ser.Name, 559 MediaType: prod, 560 Implementation: ser.Implementation, 561 AllSerializers: []GenSerializer{ser}, 562 }) 563 } 564 if len(produces) == 0 { 565 produces = append(produces, GenSerGroup{ 566 AppName: a.Name, 567 ReceiverName: a.Receiver, 568 Name: "json", 569 MediaType: runtime.JSONMime, 570 AllSerializers: []GenSerializer{{ 571 AppName: a.Name, 572 ReceiverName: a.Receiver, 573 Name: "json", 574 MediaType: runtime.JSONMime, 575 Implementation: knownProducers["json"], 576 }}, 577 Implementation: knownProducers["json"], 578 }) 579 producesJSON = true 580 } 581 sort.Sort(produces) 582 return 583 } 584 585 func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes { 586 if a.Principal == "" { 587 a.Principal = "interface{}" 588 } 589 requiredSecuritySchemes := make(map[string]spec.SecurityScheme, len(a.Analyzed.RequiredSecuritySchemes())) 590 for _, scheme := range a.Analyzed.RequiredSecuritySchemes() { 591 if req, ok := a.SpecDoc.Spec().SecurityDefinitions[scheme]; ok && req != nil { 592 requiredSecuritySchemes[scheme] = *req 593 } 594 } 595 return gatherSecuritySchemes(requiredSecuritySchemes, a.Name, a.Principal, a.Receiver) 596 } 597 598 func (a *appGenerator) makeCodegenApp() (GenApp, error) { 599 log.Println("building a plan for generation") 600 sw := a.SpecDoc.Spec() 601 receiver := a.Receiver 602 603 var defaultImports []string 604 605 jsonb, _ := json.MarshalIndent(a.SpecDoc.OrigSpec(), "", " ") 606 flatjsonb, _ := json.MarshalIndent(a.SpecDoc.Spec(), "", " ") 607 608 consumes, _ := a.makeConsumes() 609 produces, _ := a.makeProduces() 610 sort.Sort(consumes) 611 sort.Sort(produces) 612 security := a.makeSecuritySchemes() 613 baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target) 614 var imports = make(map[string]string) 615 616 var genMods GenDefinitions 617 importPath := a.GenOpts.ExistingModels 618 if a.GenOpts.ExistingModels == "" { 619 imports[a.GenOpts.LanguageOpts.ManglePackageName(a.ModelsPackage, "models")] = path.Join( 620 filepath.ToSlash(baseImport), 621 a.GenOpts.LanguageOpts.ManglePackagePath(a.GenOpts.ModelPackage, "models")) 622 } 623 if importPath != "" { 624 defaultImports = append(defaultImports, importPath) 625 } 626 627 log.Println("planning definitions") 628 for mn, m := range a.Models { 629 mod, err := makeGenDefinition( 630 mn, 631 a.ModelsPackage, 632 m, 633 a.SpecDoc, 634 a.GenOpts, 635 ) 636 if err != nil { 637 return GenApp{}, fmt.Errorf("error in model %s while planning definitions: %v", mn, err) 638 } 639 if mod != nil { 640 if !mod.External { 641 genMods = append(genMods, *mod) 642 } 643 644 // Copy model imports to operation imports 645 for alias, pkg := range mod.Imports { 646 target := a.GenOpts.LanguageOpts.ManglePackageName(alias, "") 647 imports[target] = pkg 648 } 649 } 650 } 651 sort.Sort(genMods) 652 653 log.Println("planning operations") 654 tns := make(map[string]struct{}) 655 var genOps GenOperations 656 for on, opp := range a.Operations { 657 o := opp.Op 658 o.Tags = pruneEmpty(o.Tags) 659 o.ID = on 660 661 var bldr codeGenOpBuilder 662 bldr.ModelsPackage = a.ModelsPackage 663 bldr.Principal = a.Principal 664 bldr.Target = a.Target 665 bldr.DefaultImports = defaultImports 666 bldr.Imports = imports 667 bldr.DefaultScheme = a.DefaultScheme 668 bldr.Doc = a.SpecDoc 669 bldr.Analyzed = a.Analyzed 670 bldr.BasePath = a.SpecDoc.BasePath() 671 bldr.GenOpts = a.GenOpts 672 673 // TODO: change operation name to something safe 674 bldr.Name = on 675 bldr.Operation = *o 676 bldr.Method = opp.Method 677 bldr.Path = opp.Path 678 bldr.Authed = len(a.Analyzed.SecurityRequirementsFor(o)) > 0 679 bldr.Security = a.Analyzed.SecurityRequirementsFor(o) 680 bldr.SecurityDefinitions = a.Analyzed.SecurityDefinitionsFor(o) 681 bldr.RootAPIPackage = a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, "server") 682 bldr.IncludeValidator = true 683 684 bldr.APIPackage = a.APIPackage 685 st := o.Tags 686 if a.GenOpts != nil { 687 st = a.GenOpts.Tags 688 } 689 intersected := intersectTags(o.Tags, st) 690 if len(st) > 0 && len(intersected) == 0 { 691 continue 692 } 693 694 if len(intersected) > 0 { 695 tag := intersected[0] 696 bldr.APIPackage = a.GenOpts.LanguageOpts.ManglePackagePath(tag, a.APIPackage) 697 for _, t := range intersected { 698 tns[t] = struct{}{} 699 } 700 } 701 op, err := bldr.MakeOperation() 702 if err != nil { 703 return GenApp{}, err 704 } 705 op.ReceiverName = receiver 706 op.Tags = intersected 707 genOps = append(genOps, op) 708 709 } 710 for k := range tns { 711 importPath := filepath.ToSlash( 712 path.Join( 713 filepath.ToSlash(baseImport), 714 a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, ""), 715 swag.ToFileName(k))) 716 defaultImports = append(defaultImports, importPath) 717 } 718 sort.Sort(genOps) 719 720 log.Println("grouping operations into packages") 721 opsGroupedByPackage := make(map[string]GenOperations) 722 for _, operation := range genOps { 723 if operation.Package == "" { 724 operation.Package = a.Package 725 } 726 opsGroupedByPackage[operation.Package] = append(opsGroupedByPackage[operation.Package], operation) 727 } 728 729 var opGroups GenOperationGroups 730 for k, v := range opsGroupedByPackage { 731 sort.Sort(v) 732 // trim duplicate extra schemas within the same package 733 vv := make(GenOperations, 0, len(v)) 734 seenExtraSchema := make(map[string]bool) 735 for _, op := range v { 736 uniqueExtraSchemas := make(GenSchemaList, 0, len(op.ExtraSchemas)) 737 for _, xs := range op.ExtraSchemas { 738 if _, alreadyThere := seenExtraSchema[xs.Name]; !alreadyThere { 739 seenExtraSchema[xs.Name] = true 740 uniqueExtraSchemas = append(uniqueExtraSchemas, xs) 741 } 742 } 743 op.ExtraSchemas = uniqueExtraSchemas 744 vv = append(vv, op) 745 } 746 747 opGroup := GenOperationGroup{ 748 GenCommon: GenCommon{ 749 Copyright: a.GenOpts.Copyright, 750 TargetImportPath: filepath.ToSlash(baseImport), 751 }, 752 Name: k, 753 Operations: vv, 754 DefaultImports: defaultImports, 755 Imports: imports, 756 RootPackage: a.APIPackage, 757 GenOpts: a.GenOpts, 758 } 759 opGroups = append(opGroups, opGroup) 760 var importPath string 761 if k == a.APIPackage { 762 importPath = path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, "")) 763 } else { 764 importPath = path.Join(filepath.ToSlash(baseImport), a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, ""), k) 765 } 766 defaultImports = append(defaultImports, importPath) 767 } 768 sort.Sort(opGroups) 769 770 log.Println("planning meta data and facades") 771 772 var collectedSchemes []string 773 var extraSchemes []string 774 for _, op := range genOps { 775 collectedSchemes = concatUnique(collectedSchemes, op.Schemes) 776 extraSchemes = concatUnique(extraSchemes, op.ExtraSchemes) 777 } 778 sort.Strings(collectedSchemes) 779 sort.Strings(extraSchemes) 780 781 host := "localhost" 782 if sw.Host != "" { 783 host = sw.Host 784 } 785 786 basePath := "/" 787 if sw.BasePath != "" { 788 basePath = sw.BasePath 789 } 790 791 return GenApp{ 792 GenCommon: GenCommon{ 793 Copyright: a.GenOpts.Copyright, 794 TargetImportPath: filepath.ToSlash(baseImport), 795 }, 796 APIPackage: a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, "server"), 797 Package: a.Package, 798 ReceiverName: receiver, 799 Name: a.Name, 800 Host: host, 801 BasePath: basePath, 802 Schemes: schemeOrDefault(collectedSchemes, a.DefaultScheme), 803 ExtraSchemes: extraSchemes, 804 ExternalDocs: sw.ExternalDocs, 805 Info: sw.Info, 806 Consumes: consumes, 807 Produces: produces, 808 DefaultConsumes: a.DefaultConsumes, 809 DefaultProduces: a.DefaultProduces, 810 DefaultImports: defaultImports, 811 Imports: imports, 812 SecurityDefinitions: security, 813 Models: genMods, 814 Operations: genOps, 815 OperationGroups: opGroups, 816 Principal: a.Principal, 817 SwaggerJSON: generateReadableSpec(jsonb), 818 FlatSwaggerJSON: generateReadableSpec(flatjsonb), 819 ExcludeSpec: a.GenOpts != nil && a.GenOpts.ExcludeSpec, 820 GenOpts: a.GenOpts, 821 }, nil 822 } 823 824 // generateReadableSpec makes swagger json spec as a string instead of bytes 825 // the only character that needs to be escaped is '`' symbol, since it cannot be escaped in the GO string 826 // that is quoted as `string data`. The function doesn't care about the beginning or the ending of the 827 // string it escapes since all data that needs to be escaped is always in the middle of the swagger spec. 828 func generateReadableSpec(spec []byte) string { 829 buf := &bytes.Buffer{} 830 for _, b := range string(spec) { 831 if b == '`' { 832 buf.WriteString("`+\"`\"+`") 833 } else { 834 buf.WriteRune(b) 835 } 836 } 837 return buf.String() 838 }