github.com/anchorlabsinc/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  }