github.com/AngusLu/go-swagger@v0.28.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  	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  }