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