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