github.com/go-swagger/go-swagger@v0.31.0/generator/support.go (about) 1 // Copyright 2015 go-swagger maintainers 2 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package generator 17 18 import ( 19 "bytes" 20 "encoding/json" 21 "errors" 22 "fmt" 23 "log" 24 "path" 25 "path/filepath" 26 "sort" 27 28 "github.com/go-openapi/analysis" 29 "github.com/go-openapi/loads" 30 "github.com/go-openapi/spec" 31 "github.com/go-openapi/swag" 32 ) 33 34 // GenerateServer generates a server application 35 func GenerateServer(name string, modelNames, operationIDs []string, opts *GenOpts) error { 36 generator, err := newAppGenerator(name, modelNames, operationIDs, opts) 37 if err != nil { 38 return err 39 } 40 return generator.Generate() 41 } 42 43 // GenerateSupport generates the supporting files for an API 44 func GenerateSupport(name string, modelNames, operationIDs []string, opts *GenOpts) error { 45 generator, err := newAppGenerator(name, modelNames, operationIDs, opts) 46 if err != nil { 47 return err 48 } 49 return generator.GenerateSupport(nil) 50 } 51 52 // GenerateMarkdown documentation for a swagger specification 53 func GenerateMarkdown(output string, modelNames, operationIDs []string, opts *GenOpts) error { 54 if output == "." || output == "" { 55 output = "markdown.md" 56 } 57 58 if err := opts.EnsureDefaults(); err != nil { 59 return err 60 } 61 if opts.Target != "" && opts.Target != "." { 62 output = filepath.Join(opts.Target, output) 63 } 64 MarkdownSectionOpts(opts, output) 65 66 generator, err := newAppGenerator("", modelNames, operationIDs, opts) 67 if err != nil { 68 return err 69 } 70 71 return generator.GenerateMarkdown() 72 } 73 74 func newAppGenerator(name string, modelNames, operationIDs []string, opts *GenOpts) (*appGenerator, error) { 75 if err := opts.CheckOpts(); err != nil { 76 return nil, err 77 } 78 79 if err := opts.setTemplates(); err != nil { 80 return nil, err 81 } 82 83 specDoc, analyzed, err := opts.analyzeSpec() 84 if err != nil { 85 return nil, err 86 } 87 88 models, err := gatherModels(specDoc, modelNames) 89 if err != nil { 90 return nil, err 91 } 92 93 operations := gatherOperations(analyzed, operationIDs) 94 95 if len(operations) == 0 && !opts.IgnoreOperations { 96 return nil, errors.New("no operations were selected") 97 } 98 99 opts.Name = appNameOrDefault(specDoc, name, defaultServerName) 100 if opts.IncludeMain && opts.MainPackage == "" { 101 // default target for the generated main 102 opts.MainPackage = swag.ToCommandName(mainNameOrDefault(specDoc, name, defaultServerName) + "-server") 103 } 104 105 apiPackage := opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget) 106 return &appGenerator{ 107 Name: opts.Name, 108 Receiver: "o", 109 SpecDoc: specDoc, 110 Analyzed: analyzed, 111 Models: models, 112 Operations: operations, 113 Target: opts.Target, 114 DumpData: opts.DumpData, 115 Package: opts.LanguageOpts.ManglePackageName(apiPackage, defaultOperationsTarget), 116 APIPackage: apiPackage, 117 ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget), 118 ServerPackage: opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget), 119 ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget), 120 OperationsPackage: filepath.Join(opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget), apiPackage), 121 Principal: opts.PrincipalAlias(), 122 DefaultScheme: opts.DefaultScheme, 123 DefaultProduces: opts.DefaultProduces, 124 DefaultConsumes: opts.DefaultConsumes, 125 GenOpts: opts, 126 }, nil 127 } 128 129 type appGenerator struct { 130 Name string 131 Receiver string 132 SpecDoc *loads.Document 133 Analyzed *analysis.Spec 134 Package string 135 APIPackage string 136 ModelsPackage string 137 ServerPackage string 138 ClientPackage string 139 OperationsPackage string 140 MainPackage string 141 Principal string 142 Models map[string]spec.Schema 143 Operations map[string]opRef 144 Target string 145 DumpData bool 146 DefaultScheme string 147 DefaultProduces string 148 DefaultConsumes string 149 GenOpts *GenOpts 150 } 151 152 func (a *appGenerator) Generate() error { 153 app, err := a.makeCodegenApp() 154 if err != nil { 155 return err 156 } 157 158 if a.DumpData { 159 return dumpData(app) 160 } 161 162 // NOTE: relative to previous implem with chan. 163 // IPC removed concurrent execution because of the FuncMap that is being shared 164 // templates are now lazy loaded so there is concurrent map access I can't guard 165 if a.GenOpts.IncludeModel { 166 log.Printf("rendering %d models", len(app.Models)) 167 for _, md := range app.Models { 168 mod := md 169 mod.IncludeModel = true 170 mod.IncludeValidator = a.GenOpts.IncludeValidator 171 if err := a.GenOpts.renderDefinition(&mod); err != nil { 172 return err 173 } 174 } 175 } 176 177 if a.GenOpts.IncludeHandler { 178 log.Printf("rendering %d operation groups (tags)", app.OperationGroups.Len()) 179 for _, g := range app.OperationGroups { 180 opg := g 181 log.Printf("rendering %d operations for %s", opg.Operations.Len(), opg.Name) 182 for _, p := range opg.Operations { 183 op := p 184 if err := a.GenOpts.renderOperation(&op); err != nil { 185 return err 186 } 187 } 188 // optional OperationGroups templates generation 189 if err := a.GenOpts.renderOperationGroup(&opg); err != nil { 190 return fmt.Errorf("error while rendering operation group: %w", err) 191 } 192 } 193 } 194 195 if a.GenOpts.IncludeSupport { 196 log.Printf("rendering support") 197 if err := a.GenerateSupport(&app); err != nil { 198 return err 199 } 200 } 201 return nil 202 } 203 204 func (a *appGenerator) GenerateSupport(ap *GenApp) error { 205 app := ap 206 if ap == nil { 207 // allows for calling GenerateSupport standalone 208 ca, err := a.makeCodegenApp() 209 if err != nil { 210 return err 211 } 212 app = &ca 213 } 214 215 baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target) 216 serverPath := path.Join(baseImport, 217 a.GenOpts.LanguageOpts.ManglePackagePath(a.ServerPackage, defaultServerTarget)) 218 219 pkgAlias := deconflictPkg(importAlias(serverPath), renameServerPackage) 220 app.DefaultImports[pkgAlias] = serverPath 221 app.ServerPackageAlias = pkgAlias 222 223 if a.GenOpts.IncludeCLi { // no need to add this import when there is no CLI 224 // add client import for cli generation 225 clientPath := path.Join(baseImport, 226 a.GenOpts.LanguageOpts.ManglePackagePath(a.ClientPackage, defaultClientTarget)) 227 clientPkgAlias := importAlias(clientPath) 228 app.DefaultImports[clientPkgAlias] = clientPath 229 } 230 231 return a.GenOpts.renderApplication(app) 232 } 233 234 func (a *appGenerator) GenerateMarkdown() error { 235 app, err := a.makeCodegenApp() 236 if err != nil { 237 return err 238 } 239 240 return a.GenOpts.renderApplication(&app) 241 } 242 243 func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes { 244 requiredSecuritySchemes := make(map[string]spec.SecurityScheme, len(a.Analyzed.RequiredSecuritySchemes())) 245 for _, scheme := range a.Analyzed.RequiredSecuritySchemes() { 246 if req, ok := a.SpecDoc.Spec().SecurityDefinitions[scheme]; ok && req != nil { 247 requiredSecuritySchemes[scheme] = *req 248 } 249 } 250 return gatherSecuritySchemes(requiredSecuritySchemes, a.Name, a.Principal, a.Receiver, a.GenOpts.PrincipalIsNullable()) 251 } 252 253 func (a *appGenerator) makeCodegenApp() (GenApp, error) { 254 log.Println("building a plan for generation") 255 256 sw := a.SpecDoc.Spec() 257 receiver := a.Receiver 258 259 consumes, _ := a.makeConsumes() 260 produces, _ := a.makeProduces() 261 security := a.makeSecuritySchemes() 262 263 log.Println("generation target", a.Target) 264 265 baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target) 266 defaultImports := a.GenOpts.defaultImports() 267 268 imports := make(map[string]string, 50) 269 alias := deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.OperationsPackage, defaultOperationsTarget), renameAPIPackage) 270 if !a.GenOpts.IsClient { // we don't want to inject this import for clients 271 imports[alias] = path.Join( 272 baseImport, 273 a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget)) 274 } 275 276 implAlias := "" 277 if a.GenOpts.ImplementationPackage != "" { 278 implAlias = deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.GenOpts.ImplementationPackage, defaultImplementationTarget), renameImplementationPackage) 279 imports[implAlias] = a.GenOpts.ImplementationPackage 280 } 281 282 log.Printf("planning definitions (found: %d)", len(a.Models)) 283 284 genModels := make(GenDefinitions, 0, len(a.Models)) 285 for mn, m := range a.Models { 286 model, err := makeGenDefinition( 287 mn, 288 a.ModelsPackage, 289 m, 290 a.SpecDoc, 291 a.GenOpts, 292 ) 293 if err != nil { 294 return GenApp{}, fmt.Errorf("error in model %s while planning definitions: %w", mn, err) 295 } 296 if model != nil { 297 if !model.External { 298 genModels = append(genModels, *model) 299 } 300 301 // Copy model imports to operation imports 302 // TODO(fredbi): mangle model pkg aliases 303 for alias, pkg := range model.Imports { 304 target := a.GenOpts.LanguageOpts.ManglePackageName(alias, "") 305 imports[target] = pkg 306 } 307 } 308 } 309 sort.Sort(genModels) 310 311 log.Printf("planning operations (found: %d)", len(a.Operations)) 312 313 genOps := make(GenOperations, 0, len(a.Operations)) 314 consumesIndex := make(map[string][]string) 315 producesIndex := make(map[string][]string) 316 pristineDoc := a.SpecDoc.Pristine() 317 318 for operationName, opp := range a.Operations { 319 o := opp.Op 320 o.ID = operationName 321 322 bldr := codeGenOpBuilder{ 323 ModelsPackage: a.ModelsPackage, 324 Principal: a.GenOpts.PrincipalAlias(), 325 Target: a.Target, 326 DefaultImports: defaultImports, 327 Imports: imports, 328 DefaultScheme: a.DefaultScheme, 329 Doc: a.SpecDoc, 330 PristineDefs: pristineDoc, 331 Analyzed: a.Analyzed, 332 BasePath: a.SpecDoc.BasePath(), 333 GenOpts: a.GenOpts, 334 Name: operationName, 335 Operation: *o, 336 Method: opp.Method, 337 Path: opp.Path, 338 IncludeValidator: a.GenOpts.IncludeValidator, 339 APIPackage: a.APIPackage, // defaults to main operations package 340 DefaultProduces: a.DefaultProduces, 341 DefaultConsumes: a.DefaultConsumes, 342 } 343 344 tag, tags, ok := bldr.analyzeTags() 345 if !ok { 346 continue // operation filtered according to CLI params 347 } 348 349 bldr.Authed = len(a.Analyzed.SecurityRequirementsFor(o)) > 0 350 bldr.Security = a.Analyzed.SecurityRequirementsFor(o) 351 bldr.SecurityDefinitions = a.Analyzed.SecurityDefinitionsFor(o) 352 bldr.RootAPIPackage = a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget) 353 354 st := o.Tags 355 if a.GenOpts != nil { 356 st = a.GenOpts.Tags 357 } 358 intersected := intersectTags(o.Tags, st) 359 if len(st) > 0 && len(intersected) == 0 { 360 continue 361 } 362 363 op, err := bldr.MakeOperation() 364 if err != nil { 365 return GenApp{}, err 366 } 367 368 op.ReceiverName = receiver 369 op.Tags = tags // ordered tags for this operation, possibly filtered by CLI params 370 371 allConsumes := pruneEmpty(op.ConsumesMediaTypes) 372 if bldr.DefaultConsumes != "" { 373 allConsumes = append(allConsumes, bldr.DefaultConsumes) 374 } 375 consumesIndex[bldr.Name] = allConsumes 376 377 allProduces := pruneEmpty(op.ProducesMediaTypes) 378 if bldr.DefaultProduces != "" { 379 allProduces = append(allProduces, bldr.DefaultProduces) 380 } 381 producesIndex[bldr.Name] = allProduces 382 383 if !a.GenOpts.SkipTagPackages && tag != "" { 384 importPath := filepath.ToSlash( 385 path.Join( 386 baseImport, 387 a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget), 388 a.GenOpts.LanguageOpts.ManglePackageName(bldr.APIPackage, defaultOperationsTarget), 389 )) 390 391 // check for possible conflicts that requires import aliasing 392 pth, aliasUsed := defaultImports[bldr.APIPackageAlias] 393 if (a.GenOpts.IsClient && bldr.APIPackageAlias == a.GenOpts.ClientPackage) || // we don't want import to shadow the current package 394 (a.GenOpts.IncludeCLi && bldr.APIPackageAlias == a.GenOpts.CliPackage) || 395 (aliasUsed && pth != importPath) { // was already imported with a different target 396 op.PackageAlias = renameOperationPackage(tags, bldr.APIPackageAlias) 397 bldr.APIPackageAlias = op.PackageAlias 398 } 399 defaultImports[bldr.APIPackageAlias] = importPath 400 } 401 402 genOps = append(genOps, op) 403 } 404 sort.Sort(genOps) 405 406 opsGroupedByPackage := make(map[string]GenOperations, len(genOps)) 407 for _, operation := range genOps { 408 opsGroupedByPackage[operation.PackageAlias] = append(opsGroupedByPackage[operation.PackageAlias], operation) 409 } 410 411 log.Printf("grouping operations into packages (packages: %d)", len(opsGroupedByPackage)) 412 413 opGroups := make(GenOperationGroups, 0, len(opsGroupedByPackage)) 414 for k, v := range opsGroupedByPackage { 415 log.Printf("operations for package %q (found: %d)", k, len(v)) 416 sort.Sort(v) 417 418 consumesInGroup := make([]string, 0, 2) 419 producesInGroup := make([]string, 0, 2) 420 421 // trim duplicate extra schemas within the same package 422 vv := make(GenOperations, 0, len(v)) 423 seenExtraSchema := make(map[string]bool) 424 for _, op := range v { 425 uniqueExtraSchemas := make(GenSchemaList, 0, len(op.ExtraSchemas)) 426 for _, xs := range op.ExtraSchemas { 427 if _, alreadyThere := seenExtraSchema[xs.Name]; !alreadyThere { 428 seenExtraSchema[xs.Name] = true 429 uniqueExtraSchemas = append(uniqueExtraSchemas, xs) 430 } 431 } 432 op.ExtraSchemas = uniqueExtraSchemas 433 vv = append(vv, op) 434 435 consumesInGroup = concatUnique(consumesInGroup, consumesIndex[op.Name]) 436 producesInGroup = concatUnique(producesInGroup, producesIndex[op.Name]) 437 } 438 var pkg string 439 if len(vv) > 0 { 440 pkg = vv[0].Package 441 } else { 442 pkg = k 443 } 444 445 opGroup := GenOperationGroup{ 446 GenCommon: GenCommon{ 447 Copyright: a.GenOpts.Copyright, 448 TargetImportPath: baseImport, 449 }, 450 Name: pkg, 451 PackageAlias: k, 452 Operations: vv, 453 DefaultImports: defaultImports, 454 Imports: imports, 455 RootPackage: a.APIPackage, 456 GenOpts: a.GenOpts, 457 } 458 459 if a.GenOpts.IsClient { 460 // generating extra options to switch media type in client 461 if len(consumesInGroup) > 1 || len(producesInGroup) > 1 { 462 sort.Strings(producesInGroup) 463 sort.Strings(consumesInGroup) 464 options := &GenClientOptions{ 465 ProducesMediaTypes: producesInGroup, 466 ConsumesMediaTypes: consumesInGroup, 467 } 468 opGroup.ClientOptions = options 469 } 470 } 471 opGroups = append(opGroups, opGroup) 472 } 473 sort.Sort(opGroups) 474 475 log.Println("planning meta data and facades") 476 477 var collectedSchemes, extraSchemes []string 478 for _, op := range genOps { 479 collectedSchemes = concatUnique(collectedSchemes, op.Schemes) 480 extraSchemes = concatUnique(extraSchemes, op.ExtraSchemes) 481 } 482 sort.Strings(collectedSchemes) 483 sort.Strings(extraSchemes) 484 485 host := "localhost" 486 if sw.Host != "" { 487 host = sw.Host 488 } 489 490 basePath := "/" 491 if sw.BasePath != "" { 492 basePath = sw.BasePath 493 } 494 495 jsonb, _ := json.MarshalIndent(a.SpecDoc.OrigSpec(), "", " ") 496 flatjsonb, _ := json.MarshalIndent(a.SpecDoc.Spec(), "", " ") 497 498 return GenApp{ 499 GenCommon: GenCommon{ 500 Copyright: a.GenOpts.Copyright, 501 TargetImportPath: baseImport, 502 }, 503 APIPackage: a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget), 504 APIPackageAlias: alias, 505 ImplementationPackageAlias: implAlias, 506 Package: a.Package, 507 ReceiverName: receiver, 508 Name: a.Name, 509 Host: host, 510 BasePath: basePath, 511 Schemes: schemeOrDefault(collectedSchemes, a.DefaultScheme), 512 ExtraSchemes: extraSchemes, 513 ExternalDocs: trimExternalDoc(sw.ExternalDocs), 514 Tags: trimTags(sw.Tags), 515 Info: trimInfo(sw.Info), 516 Consumes: consumes, 517 Produces: produces, 518 DefaultConsumes: a.DefaultConsumes, 519 DefaultProduces: a.DefaultProduces, 520 DefaultImports: defaultImports, 521 Imports: imports, 522 SecurityDefinitions: security, 523 SecurityRequirements: securityRequirements(a.SpecDoc.Spec().Security), // top level securityRequirements 524 Models: genModels, 525 Operations: genOps, 526 OperationGroups: opGroups, 527 Principal: a.GenOpts.PrincipalAlias(), 528 SwaggerJSON: generateReadableSpec(jsonb), 529 FlatSwaggerJSON: generateReadableSpec(flatjsonb), 530 ExcludeSpec: a.GenOpts.ExcludeSpec, 531 GenOpts: a.GenOpts, 532 533 PrincipalIsNullable: a.GenOpts.PrincipalIsNullable(), 534 }, nil 535 } 536 537 // generateReadableSpec makes swagger json spec as a string instead of bytes 538 // the only character that needs to be escaped is '`' symbol, since it cannot be escaped in the GO string 539 // that is quoted as `string data`. The function doesn't care about the beginning or the ending of the 540 // string it escapes since all data that needs to be escaped is always in the middle of the swagger spec. 541 func generateReadableSpec(spec []byte) string { 542 buf := &bytes.Buffer{} 543 for _, b := range string(spec) { 544 if b == '`' { 545 buf.WriteString("`+\"`\"+`") 546 } else { 547 buf.WriteRune(b) 548 } 549 } 550 return buf.String() 551 } 552 553 func trimExternalDoc(in *spec.ExternalDocumentation) *spec.ExternalDocumentation { 554 if in == nil { 555 return nil 556 } 557 558 return &spec.ExternalDocumentation{ 559 URL: in.URL, 560 Description: trimBOM(in.Description), 561 } 562 } 563 564 func trimInfo(in *spec.Info) *spec.Info { 565 if in == nil { 566 return nil 567 } 568 569 return &spec.Info{ 570 InfoProps: spec.InfoProps{ 571 Contact: in.Contact, 572 Title: trimBOM(in.Title), 573 Description: trimBOM(in.Description), 574 TermsOfService: trimBOM(in.TermsOfService), 575 License: in.License, 576 Version: in.Version, 577 }, 578 VendorExtensible: in.VendorExtensible, 579 } 580 } 581 582 func trimTags(in []spec.Tag) []spec.Tag { 583 if in == nil { 584 return nil 585 } 586 587 tags := make([]spec.Tag, 0, len(in)) 588 589 for _, tag := range in { 590 tags = append(tags, spec.Tag{ 591 TagProps: spec.TagProps{ 592 Name: tag.Name, 593 Description: trimBOM(tag.Description), 594 ExternalDocs: trimExternalDoc(tag.ExternalDocs), 595 }, 596 }) 597 } 598 599 return tags 600 }