github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/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 MarkdownSectionOpts(opts, output) 62 63 generator, err := newAppGenerator("", modelNames, operationIDs, opts) 64 if err != nil { 65 return err 66 } 67 68 return generator.GenerateMarkdown() 69 } 70 71 func newAppGenerator(name string, modelNames, operationIDs []string, opts *GenOpts) (*appGenerator, error) { 72 if err := opts.CheckOpts(); err != nil { 73 return nil, err 74 } 75 76 if err := opts.setTemplates(); err != nil { 77 return nil, err 78 } 79 80 specDoc, analyzed, err := opts.analyzeSpec() 81 if err != nil { 82 return nil, err 83 } 84 85 models, err := gatherModels(specDoc, modelNames) 86 if err != nil { 87 return nil, err 88 } 89 90 operations := gatherOperations(analyzed, operationIDs) 91 92 if len(operations) == 0 && !opts.IgnoreOperations { 93 return nil, errors.New("no operations were selected") 94 } 95 96 opts.Name = appNameOrDefault(specDoc, name, defaultServerName) 97 if opts.IncludeMain && opts.MainPackage == "" { 98 // default target for the generated main 99 opts.MainPackage = swag.ToCommandName(mainNameOrDefault(specDoc, name, defaultServerName) + "-server") 100 } 101 102 apiPackage := opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget) 103 return &appGenerator{ 104 Name: opts.Name, 105 Receiver: "o", 106 SpecDoc: specDoc, 107 Analyzed: analyzed, 108 Models: models, 109 Operations: operations, 110 Target: opts.Target, 111 DumpData: opts.DumpData, 112 Package: opts.LanguageOpts.ManglePackageName(apiPackage, defaultOperationsTarget), 113 APIPackage: apiPackage, 114 ModelsPackage: opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget), 115 ServerPackage: opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget), 116 ClientPackage: opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget), 117 OperationsPackage: filepath.Join(opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget), apiPackage), 118 Principal: opts.PrincipalAlias(), 119 DefaultScheme: opts.DefaultScheme, 120 DefaultProduces: opts.DefaultProduces, 121 DefaultConsumes: opts.DefaultConsumes, 122 GenOpts: opts, 123 }, nil 124 } 125 126 type appGenerator struct { 127 Name string 128 Receiver string 129 SpecDoc *loads.Document 130 Analyzed *analysis.Spec 131 Package string 132 APIPackage string 133 ModelsPackage string 134 ServerPackage string 135 ClientPackage string 136 OperationsPackage string 137 MainPackage string 138 Principal string 139 Models map[string]spec.Schema 140 Operations map[string]opRef 141 Target string 142 DumpData bool 143 DefaultScheme string 144 DefaultProduces string 145 DefaultConsumes string 146 GenOpts *GenOpts 147 } 148 149 func (a *appGenerator) Generate() error { 150 app, err := a.makeCodegenApp() 151 if err != nil { 152 return err 153 } 154 155 if a.DumpData { 156 return dumpData(app) 157 } 158 159 // NOTE: relative to previous implem with chan. 160 // IPC removed concurrent execution because of the FuncMap that is being shared 161 // templates are now lazy loaded so there is concurrent map access I can't guard 162 if a.GenOpts.IncludeModel { 163 log.Printf("rendering %d models", len(app.Models)) 164 for _, md := range app.Models { 165 mod := md 166 mod.IncludeModel = true 167 mod.IncludeValidator = a.GenOpts.IncludeValidator 168 if err := a.GenOpts.renderDefinition(&mod); err != nil { 169 return err 170 } 171 } 172 } 173 174 if a.GenOpts.IncludeHandler { 175 log.Printf("rendering %d operation groups (tags)", app.OperationGroups.Len()) 176 for _, g := range app.OperationGroups { 177 opg := g 178 log.Printf("rendering %d operations for %s", opg.Operations.Len(), opg.Name) 179 for _, p := range opg.Operations { 180 op := p 181 if err := a.GenOpts.renderOperation(&op); err != nil { 182 return err 183 } 184 } 185 // optional OperationGroups templates generation 186 if err := a.GenOpts.renderOperationGroup(&opg); err != nil { 187 return fmt.Errorf("error while rendering operation group: %v", err) 188 } 189 } 190 } 191 192 if a.GenOpts.IncludeSupport { 193 log.Printf("rendering support") 194 if err := a.GenerateSupport(&app); err != nil { 195 return err 196 } 197 } 198 return nil 199 } 200 201 func (a *appGenerator) GenerateSupport(ap *GenApp) error { 202 app := ap 203 if ap == nil { 204 // allows for calling GenerateSupport standalone 205 ca, err := a.makeCodegenApp() 206 if err != nil { 207 return err 208 } 209 app = &ca 210 } 211 212 baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target) 213 serverPath := path.Join(baseImport, 214 a.GenOpts.LanguageOpts.ManglePackagePath(a.ServerPackage, defaultServerTarget)) 215 216 pkgAlias := deconflictPkg(importAlias(serverPath), renameServerPackage) 217 app.DefaultImports[pkgAlias] = serverPath 218 app.ServerPackageAlias = pkgAlias 219 220 // add client import for cli generation 221 clientPath := path.Join(baseImport, 222 a.GenOpts.LanguageOpts.ManglePackagePath(a.ClientPackage, defaultClientTarget)) 223 clientPkgAlias := importAlias(clientPath) 224 app.DefaultImports[clientPkgAlias] = clientPath 225 226 return a.GenOpts.renderApplication(app) 227 } 228 229 func (a *appGenerator) GenerateMarkdown() error { 230 app, err := a.makeCodegenApp() 231 if err != nil { 232 return err 233 } 234 235 return a.GenOpts.renderApplication(&app) 236 } 237 238 func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes { 239 requiredSecuritySchemes := make(map[string]spec.SecurityScheme, len(a.Analyzed.RequiredSecuritySchemes())) 240 for _, scheme := range a.Analyzed.RequiredSecuritySchemes() { 241 if req, ok := a.SpecDoc.Spec().SecurityDefinitions[scheme]; ok && req != nil { 242 requiredSecuritySchemes[scheme] = *req 243 } 244 } 245 return gatherSecuritySchemes(requiredSecuritySchemes, a.Name, a.Principal, a.Receiver, a.GenOpts.PrincipalIsNullable()) 246 } 247 248 func (a *appGenerator) makeCodegenApp() (GenApp, error) { 249 log.Println("building a plan for generation") 250 251 sw := a.SpecDoc.Spec() 252 receiver := a.Receiver 253 254 consumes, _ := a.makeConsumes() 255 produces, _ := a.makeProduces() 256 security := a.makeSecuritySchemes() 257 258 log.Println("generation target", a.Target) 259 260 baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target) 261 defaultImports := a.GenOpts.defaultImports() 262 263 imports := make(map[string]string, 50) 264 alias := deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.OperationsPackage, defaultOperationsTarget), renameAPIPackage) 265 imports[alias] = path.Join( 266 baseImport, 267 a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget)) 268 269 implAlias := "" 270 if a.GenOpts.ImplementationPackage != "" { 271 implAlias = deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.GenOpts.ImplementationPackage, defaultImplementationTarget), renameImplementationPackage) 272 imports[implAlias] = a.GenOpts.ImplementationPackage 273 } 274 275 log.Printf("planning definitions (found: %d)", len(a.Models)) 276 277 genModels := make(GenDefinitions, 0, len(a.Models)) 278 for mn, m := range a.Models { 279 model, err := makeGenDefinition( 280 mn, 281 a.ModelsPackage, 282 m, 283 a.SpecDoc, 284 a.GenOpts, 285 ) 286 if err != nil { 287 return GenApp{}, fmt.Errorf("error in model %s while planning definitions: %v", mn, err) 288 } 289 if model != nil { 290 if !model.External { 291 genModels = append(genModels, *model) 292 } 293 294 // Copy model imports to operation imports 295 // TODO(fredbi): mangle model pkg aliases 296 for alias, pkg := range model.Imports { 297 target := a.GenOpts.LanguageOpts.ManglePackageName(alias, "") 298 imports[target] = pkg 299 } 300 } 301 } 302 sort.Sort(genModels) 303 304 log.Printf("planning operations (found: %d)", len(a.Operations)) 305 306 genOps := make(GenOperations, 0, len(a.Operations)) 307 for operationName, opp := range a.Operations { 308 o := opp.Op 309 o.ID = operationName 310 311 bldr := codeGenOpBuilder{ 312 ModelsPackage: a.ModelsPackage, 313 Principal: a.GenOpts.PrincipalAlias(), 314 Target: a.Target, 315 DefaultImports: defaultImports, 316 Imports: imports, 317 DefaultScheme: a.DefaultScheme, 318 Doc: a.SpecDoc, 319 Analyzed: a.Analyzed, 320 BasePath: a.SpecDoc.BasePath(), 321 GenOpts: a.GenOpts, 322 Name: operationName, 323 Operation: *o, 324 Method: opp.Method, 325 Path: opp.Path, 326 IncludeValidator: a.GenOpts.IncludeValidator, 327 APIPackage: a.APIPackage, // defaults to main operations package 328 DefaultProduces: a.DefaultProduces, 329 DefaultConsumes: a.DefaultConsumes, 330 } 331 332 tag, tags, ok := bldr.analyzeTags() 333 if !ok { 334 continue // operation filtered according to CLI params 335 } 336 337 bldr.Authed = len(a.Analyzed.SecurityRequirementsFor(o)) > 0 338 bldr.Security = a.Analyzed.SecurityRequirementsFor(o) 339 bldr.SecurityDefinitions = a.Analyzed.SecurityDefinitionsFor(o) 340 bldr.RootAPIPackage = a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget) 341 342 st := o.Tags 343 if a.GenOpts != nil { 344 st = a.GenOpts.Tags 345 } 346 intersected := intersectTags(o.Tags, st) 347 if len(st) > 0 && len(intersected) == 0 { 348 continue 349 } 350 351 op, err := bldr.MakeOperation() 352 if err != nil { 353 return GenApp{}, err 354 } 355 356 op.ReceiverName = receiver 357 op.Tags = tags // ordered tags for this operation, possibly filtered by CLI params 358 genOps = append(genOps, op) 359 360 if !a.GenOpts.SkipTagPackages && tag != "" { 361 importPath := filepath.ToSlash( 362 path.Join( 363 baseImport, 364 a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget), 365 a.GenOpts.LanguageOpts.ManglePackageName(bldr.APIPackage, defaultOperationsTarget), 366 )) 367 defaultImports[bldr.APIPackageAlias] = importPath 368 } 369 } 370 sort.Sort(genOps) 371 372 opsGroupedByPackage := make(map[string]GenOperations, len(genOps)) 373 for _, operation := range genOps { 374 opsGroupedByPackage[operation.PackageAlias] = append(opsGroupedByPackage[operation.PackageAlias], operation) 375 } 376 377 log.Printf("grouping operations into packages (packages: %d)", len(opsGroupedByPackage)) 378 379 opGroups := make(GenOperationGroups, 0, len(opsGroupedByPackage)) 380 for k, v := range opsGroupedByPackage { 381 log.Printf("operations for package packages %q (found: %d)", k, len(v)) 382 sort.Sort(v) 383 // trim duplicate extra schemas within the same package 384 vv := make(GenOperations, 0, len(v)) 385 seenExtraSchema := make(map[string]bool) 386 for _, op := range v { 387 uniqueExtraSchemas := make(GenSchemaList, 0, len(op.ExtraSchemas)) 388 for _, xs := range op.ExtraSchemas { 389 if _, alreadyThere := seenExtraSchema[xs.Name]; !alreadyThere { 390 seenExtraSchema[xs.Name] = true 391 uniqueExtraSchemas = append(uniqueExtraSchemas, xs) 392 } 393 } 394 op.ExtraSchemas = uniqueExtraSchemas 395 vv = append(vv, op) 396 } 397 var pkg string 398 if len(vv) > 0 { 399 pkg = vv[0].Package 400 } else { 401 pkg = k 402 } 403 404 opGroup := GenOperationGroup{ 405 GenCommon: GenCommon{ 406 Copyright: a.GenOpts.Copyright, 407 TargetImportPath: baseImport, 408 }, 409 Name: pkg, 410 PackageAlias: k, 411 Operations: vv, 412 DefaultImports: defaultImports, 413 Imports: imports, 414 RootPackage: a.APIPackage, 415 GenOpts: a.GenOpts, 416 } 417 opGroups = append(opGroups, opGroup) 418 } 419 sort.Sort(opGroups) 420 421 log.Println("planning meta data and facades") 422 423 var collectedSchemes, extraSchemes []string 424 for _, op := range genOps { 425 collectedSchemes = concatUnique(collectedSchemes, op.Schemes) 426 extraSchemes = concatUnique(extraSchemes, op.ExtraSchemes) 427 } 428 sort.Strings(collectedSchemes) 429 sort.Strings(extraSchemes) 430 431 host := "localhost" 432 if sw.Host != "" { 433 host = sw.Host 434 } 435 436 basePath := "/" 437 if sw.BasePath != "" { 438 basePath = sw.BasePath 439 } 440 441 jsonb, _ := json.MarshalIndent(a.SpecDoc.OrigSpec(), "", " ") 442 flatjsonb, _ := json.MarshalIndent(a.SpecDoc.Spec(), "", " ") 443 444 return GenApp{ 445 GenCommon: GenCommon{ 446 Copyright: a.GenOpts.Copyright, 447 TargetImportPath: baseImport, 448 }, 449 APIPackage: a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget), 450 APIPackageAlias: alias, 451 ImplementationPackageAlias: implAlias, 452 Package: a.Package, 453 ReceiverName: receiver, 454 Name: a.Name, 455 Host: host, 456 BasePath: basePath, 457 Schemes: schemeOrDefault(collectedSchemes, a.DefaultScheme), 458 ExtraSchemes: extraSchemes, 459 ExternalDocs: trimExternalDoc(sw.ExternalDocs), 460 Tags: trimTags(sw.Tags), 461 Info: trimInfo(sw.Info), 462 Consumes: consumes, 463 Produces: produces, 464 DefaultConsumes: a.DefaultConsumes, 465 DefaultProduces: a.DefaultProduces, 466 DefaultImports: defaultImports, 467 Imports: imports, 468 SecurityDefinitions: security, 469 SecurityRequirements: securityRequirements(a.SpecDoc.Spec().Security), // top level securityRequirements 470 Models: genModels, 471 Operations: genOps, 472 OperationGroups: opGroups, 473 Principal: a.GenOpts.PrincipalAlias(), 474 SwaggerJSON: generateReadableSpec(jsonb), 475 FlatSwaggerJSON: generateReadableSpec(flatjsonb), 476 ExcludeSpec: a.GenOpts.ExcludeSpec, 477 GenOpts: a.GenOpts, 478 479 PrincipalIsNullable: a.GenOpts.PrincipalIsNullable(), 480 }, nil 481 } 482 483 // generateReadableSpec makes swagger json spec as a string instead of bytes 484 // the only character that needs to be escaped is '`' symbol, since it cannot be escaped in the GO string 485 // that is quoted as `string data`. The function doesn't care about the beginning or the ending of the 486 // string it escapes since all data that needs to be escaped is always in the middle of the swagger spec. 487 func generateReadableSpec(spec []byte) string { 488 buf := &bytes.Buffer{} 489 for _, b := range string(spec) { 490 if b == '`' { 491 buf.WriteString("`+\"`\"+`") 492 } else { 493 buf.WriteRune(b) 494 } 495 } 496 return buf.String() 497 } 498 499 func trimExternalDoc(in *spec.ExternalDocumentation) *spec.ExternalDocumentation { 500 if in == nil { 501 return nil 502 } 503 504 return &spec.ExternalDocumentation{ 505 URL: in.URL, 506 Description: trimBOM(in.Description), 507 } 508 } 509 510 func trimInfo(in *spec.Info) *spec.Info { 511 if in == nil { 512 return nil 513 } 514 515 return &spec.Info{ 516 InfoProps: spec.InfoProps{ 517 Contact: in.Contact, 518 Title: trimBOM(in.Title), 519 Description: trimBOM(in.Description), 520 TermsOfService: trimBOM(in.TermsOfService), 521 License: in.License, 522 Version: in.Version, 523 }, 524 VendorExtensible: in.VendorExtensible, 525 } 526 } 527 528 func trimTags(in []spec.Tag) []spec.Tag { 529 if in == nil { 530 return nil 531 } 532 533 tags := make([]spec.Tag, 0, len(in)) 534 535 for _, tag := range in { 536 tags = append(tags, spec.Tag{ 537 TagProps: spec.TagProps{ 538 Name: tag.Name, 539 Description: trimBOM(tag.Description), 540 ExternalDocs: trimExternalDoc(tag.ExternalDocs), 541 }, 542 }) 543 } 544 545 return tags 546 }