github.com/6543-forks/go-swagger@v0.26.0/generator/shared.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  	"reflect"
    28  	"sort"
    29  	"strings"
    30  	"text/template"
    31  
    32  	"github.com/go-openapi/analysis"
    33  	"github.com/go-openapi/loads"
    34  	"github.com/go-openapi/runtime"
    35  	"github.com/go-openapi/spec"
    36  	"github.com/go-openapi/swag"
    37  )
    38  
    39  //go:generate go-bindata -mode 420 -modtime 1482416923 -pkg=generator -ignore=.*\.sw? -ignore=.*\.md ./templates/...
    40  
    41  const (
    42  	// default generation targets structure
    43  	defaultModelsTarget     = "models"
    44  	defaultServerTarget     = "restapi"
    45  	defaultClientTarget     = "client"
    46  	defaultOperationsTarget = "operations"
    47  	defaultClientName       = "rest"
    48  	defaultServerName       = "swagger"
    49  	defaultScheme           = "http"
    50  )
    51  
    52  func init() {
    53  	// all initializations for the generator package
    54  	debugOptions()
    55  	initLanguage()
    56  	initTemplateRepo()
    57  	initTypes()
    58  }
    59  
    60  // DefaultSectionOpts for a given opts, this is used when no config file is passed
    61  // and uses the embedded templates when no local override can be found
    62  func DefaultSectionOpts(gen *GenOpts) {
    63  	sec := gen.Sections
    64  	if len(sec.Models) == 0 {
    65  		sec.Models = []TemplateOpts{
    66  			{
    67  				Name:     "definition",
    68  				Source:   "asset:model",
    69  				Target:   "{{ joinFilePath .Target (toPackagePath .ModelPackage) }}",
    70  				FileName: "{{ (snakize (pascalize .Name)) }}.go",
    71  			},
    72  		}
    73  	}
    74  
    75  	if len(sec.Operations) == 0 {
    76  		if gen.IsClient {
    77  			sec.Operations = []TemplateOpts{
    78  				{
    79  					Name:     "parameters",
    80  					Source:   "asset:clientParameter",
    81  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}",
    82  					FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
    83  				},
    84  				{
    85  					Name:     "responses",
    86  					Source:   "asset:clientResponse",
    87  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Package) }}",
    88  					FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
    89  				},
    90  			}
    91  		} else {
    92  			ops := []TemplateOpts{}
    93  			if gen.IncludeParameters {
    94  				ops = append(ops, TemplateOpts{
    95  					Name:     "parameters",
    96  					Source:   "asset:serverParameter",
    97  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package)  }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
    98  					FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
    99  				})
   100  			}
   101  			if gen.IncludeURLBuilder {
   102  				ops = append(ops, TemplateOpts{
   103  					Name:     "urlbuilder",
   104  					Source:   "asset:serverUrlbuilder",
   105  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   106  					FileName: "{{ (snakize (pascalize .Name)) }}_urlbuilder.go",
   107  				})
   108  			}
   109  			if gen.IncludeResponses {
   110  				ops = append(ops, TemplateOpts{
   111  					Name:     "responses",
   112  					Source:   "asset:serverResponses",
   113  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   114  					FileName: "{{ (snakize (pascalize .Name)) }}_responses.go",
   115  				})
   116  			}
   117  			if gen.IncludeHandler {
   118  				ops = append(ops, TemplateOpts{
   119  					Name:     "handler",
   120  					Source:   "asset:serverOperation",
   121  					Target:   "{{ if .UseTags }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) (toPackagePath .Package) }}{{ else }}{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .Package) }}{{ end }}",
   122  					FileName: "{{ (snakize (pascalize .Name)) }}.go",
   123  				})
   124  			}
   125  			sec.Operations = ops
   126  		}
   127  	}
   128  
   129  	if len(sec.OperationGroups) == 0 {
   130  		if gen.IsClient {
   131  			sec.OperationGroups = []TemplateOpts{
   132  				{
   133  					Name:     "client",
   134  					Source:   "asset:clientClient",
   135  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) (toPackagePath .Name)}}",
   136  					FileName: "{{ (snakize (pascalize .Name)) }}_client.go",
   137  				},
   138  			}
   139  		} else {
   140  			sec.OperationGroups = []TemplateOpts{}
   141  		}
   142  	}
   143  
   144  	if len(sec.Application) == 0 {
   145  		if gen.IsClient {
   146  			sec.Application = []TemplateOpts{
   147  				{
   148  					Name:     "facade",
   149  					Source:   "asset:clientFacade",
   150  					Target:   "{{ joinFilePath .Target (toPackagePath .ClientPackage) }}",
   151  					FileName: "{{ snakize .Name }}Client.go",
   152  				},
   153  			}
   154  		} else {
   155  			sec.Application = []TemplateOpts{
   156  				{
   157  					Name:       "configure",
   158  					Source:     "asset:serverConfigureapi",
   159  					Target:     "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   160  					FileName:   "configure_{{ (snakize (pascalize .Name)) }}.go",
   161  					SkipExists: !gen.RegenerateConfigureAPI,
   162  				},
   163  				{
   164  					Name:     "main",
   165  					Source:   "asset:serverMain",
   166  					Target:   "{{ joinFilePath .Target \"cmd\" .MainPackage }}",
   167  					FileName: "main.go",
   168  				},
   169  				{
   170  					Name:     "embedded_spec",
   171  					Source:   "asset:swaggerJsonEmbed",
   172  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   173  					FileName: "embedded_spec.go",
   174  				},
   175  				{
   176  					Name:     "server",
   177  					Source:   "asset:serverServer",
   178  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   179  					FileName: "server.go",
   180  				},
   181  				{
   182  					Name:     "builder",
   183  					Source:   "asset:serverBuilder",
   184  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) (toPackagePath .APIPackage) }}",
   185  					FileName: "{{ snakize (pascalize .Name) }}_api.go",
   186  				},
   187  				{
   188  					Name:     "doc",
   189  					Source:   "asset:serverDoc",
   190  					Target:   "{{ joinFilePath .Target (toPackagePath .ServerPackage) }}",
   191  					FileName: "doc.go",
   192  				},
   193  			}
   194  		}
   195  	}
   196  	gen.Sections = sec
   197  
   198  }
   199  
   200  // TemplateOpts allows for codegen customization
   201  type TemplateOpts struct {
   202  	Name       string `mapstructure:"name"`
   203  	Source     string `mapstructure:"source"`
   204  	Target     string `mapstructure:"target"`
   205  	FileName   string `mapstructure:"file_name"`
   206  	SkipExists bool   `mapstructure:"skip_exists"`
   207  	SkipFormat bool   `mapstructure:"skip_format"`
   208  }
   209  
   210  // SectionOpts allows for specifying options to customize the templates used for generation
   211  type SectionOpts struct {
   212  	Application     []TemplateOpts `mapstructure:"application"`
   213  	Operations      []TemplateOpts `mapstructure:"operations"`
   214  	OperationGroups []TemplateOpts `mapstructure:"operation_groups"`
   215  	Models          []TemplateOpts `mapstructure:"models"`
   216  }
   217  
   218  // GenOpts the options for the generator
   219  type GenOpts struct {
   220  	IncludeModel               bool
   221  	IncludeValidator           bool
   222  	IncludeHandler             bool
   223  	IncludeParameters          bool
   224  	IncludeResponses           bool
   225  	IncludeURLBuilder          bool
   226  	IncludeMain                bool
   227  	IncludeSupport             bool
   228  	ExcludeSpec                bool
   229  	DumpData                   bool
   230  	ValidateSpec               bool
   231  	FlattenOpts                *analysis.FlattenOpts
   232  	IsClient                   bool
   233  	defaultsEnsured            bool
   234  	PropertiesSpecOrder        bool
   235  	StrictAdditionalProperties bool
   236  	AllowTemplateOverride      bool
   237  
   238  	Spec                   string
   239  	APIPackage             string
   240  	ModelPackage           string
   241  	ServerPackage          string
   242  	ClientPackage          string
   243  	Principal              string
   244  	Target                 string
   245  	Sections               SectionOpts
   246  	LanguageOpts           *LanguageOpts
   247  	TypeMapping            map[string]string
   248  	Imports                map[string]string
   249  	DefaultScheme          string
   250  	DefaultProduces        string
   251  	DefaultConsumes        string
   252  	WithXML                bool
   253  	TemplateDir            string
   254  	Template               string
   255  	RegenerateConfigureAPI bool
   256  	Operations             []string
   257  	Models                 []string
   258  	Tags                   []string
   259  	StructTags             []string
   260  	Name                   string
   261  	FlagStrategy           string
   262  	CompatibilityMode      string
   263  	ExistingModels         string
   264  	Copyright              string
   265  	SkipTagPackages        bool
   266  	MainPackage            string
   267  	IgnoreOperations       bool
   268  	AllowEnumCI            bool
   269  	StrictResponders       bool
   270  	AcceptDefinitionsOnly  bool
   271  }
   272  
   273  // CheckOpts carries out some global consistency checks on options.
   274  func (g *GenOpts) CheckOpts() error {
   275  	if g == nil {
   276  		return errors.New("gen opts are required")
   277  	}
   278  
   279  	if !filepath.IsAbs(g.Target) {
   280  		if _, err := filepath.Abs(g.Target); err != nil {
   281  			return fmt.Errorf("could not locate target %s: %v", g.Target, err)
   282  		}
   283  	}
   284  
   285  	if filepath.IsAbs(g.ServerPackage) {
   286  		return fmt.Errorf("you shouldn't specify an absolute path in --server-package: %s", g.ServerPackage)
   287  	}
   288  
   289  	if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") {
   290  		return nil
   291  	}
   292  
   293  	pth, err := findSwaggerSpec(g.Spec)
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	// ensure spec path is absolute
   299  	g.Spec, err = filepath.Abs(pth)
   300  	if err != nil {
   301  		return fmt.Errorf("could not locate spec: %s", g.Spec)
   302  	}
   303  
   304  	return nil
   305  }
   306  
   307  // TargetPath returns the target generation path relative to the server package.
   308  // This method is used by templates, e.g. with {{ .TargetPath }}
   309  //
   310  // Errors cases are prevented by calling CheckOpts beforehand.
   311  //
   312  // Example:
   313  // Target: ${PWD}/tmp
   314  // ServerPackage: abc/efg
   315  //
   316  // Server is generated in ${PWD}/tmp/abc/efg
   317  // relative TargetPath returned: ../../../tmp
   318  //
   319  func (g *GenOpts) TargetPath() string {
   320  	var tgt string
   321  	if g.Target == "" {
   322  		tgt = "." // That's for windows
   323  	} else {
   324  		tgt = g.Target
   325  	}
   326  	tgtAbs, _ := filepath.Abs(tgt)
   327  	srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server"))
   328  	srvrAbs := filepath.Join(tgtAbs, srvPkg)
   329  	tgtRel, _ := filepath.Rel(srvrAbs, filepath.Dir(tgtAbs))
   330  	tgtRel = filepath.Join(tgtRel, filepath.Base(tgtAbs))
   331  	return tgtRel
   332  }
   333  
   334  // SpecPath returns the path to the spec relative to the server package.
   335  // If the spec is remote keep this absolute location.
   336  //
   337  // If spec is not relative to server (e.g. lives on a different drive on windows),
   338  // then the resolved path is absolute.
   339  //
   340  // This method is used by templates, e.g. with {{ .SpecPath }}
   341  //
   342  // Errors cases are prevented by calling CheckOpts beforehand.
   343  func (g *GenOpts) SpecPath() string {
   344  	if strings.HasPrefix(g.Spec, "http://") || strings.HasPrefix(g.Spec, "https://") {
   345  		return g.Spec
   346  	}
   347  	// Local specifications
   348  	specAbs, _ := filepath.Abs(g.Spec)
   349  	var tgt string
   350  	if g.Target == "" {
   351  		tgt = "." // That's for windows
   352  	} else {
   353  		tgt = g.Target
   354  	}
   355  	tgtAbs, _ := filepath.Abs(tgt)
   356  	srvPkg := filepath.FromSlash(g.LanguageOpts.ManglePackagePath(g.ServerPackage, "server"))
   357  	srvAbs := filepath.Join(tgtAbs, srvPkg)
   358  	specRel, err := filepath.Rel(srvAbs, specAbs)
   359  	if err != nil {
   360  		return specAbs
   361  	}
   362  	return specRel
   363  }
   364  
   365  // EnsureDefaults for these gen opts
   366  func (g *GenOpts) EnsureDefaults() error {
   367  	if g.defaultsEnsured {
   368  		return nil
   369  	}
   370  
   371  	if g.LanguageOpts == nil {
   372  		g.LanguageOpts = DefaultLanguageFunc()
   373  	}
   374  
   375  	DefaultSectionOpts(g)
   376  
   377  	// set defaults for flattening options
   378  	if g.FlattenOpts == nil {
   379  		g.FlattenOpts = &analysis.FlattenOpts{
   380  			Minimal:      true,
   381  			Verbose:      true,
   382  			RemoveUnused: false,
   383  			Expand:       false,
   384  		}
   385  	}
   386  
   387  	if g.DefaultScheme == "" {
   388  		g.DefaultScheme = defaultScheme
   389  	}
   390  
   391  	if g.DefaultConsumes == "" {
   392  		g.DefaultConsumes = runtime.JSONMime
   393  	}
   394  
   395  	if g.DefaultProduces == "" {
   396  		g.DefaultProduces = runtime.JSONMime
   397  	}
   398  
   399  	// always include validator with models
   400  	g.IncludeValidator = true
   401  
   402  	if g.Principal == "" {
   403  		g.Principal = iface
   404  	}
   405  
   406  	g.defaultsEnsured = true
   407  	return nil
   408  }
   409  
   410  func (g *GenOpts) location(t *TemplateOpts, data interface{}) (string, string, error) {
   411  	v := reflect.Indirect(reflect.ValueOf(data))
   412  	fld := v.FieldByName("Name")
   413  	var name string
   414  	if fld.IsValid() {
   415  		log.Println("name field", fld.String())
   416  		name = fld.String()
   417  	}
   418  
   419  	fldpack := v.FieldByName("Package")
   420  	pkg := g.APIPackage
   421  	if fldpack.IsValid() {
   422  		log.Println("package field", fldpack.String())
   423  		pkg = fldpack.String()
   424  	}
   425  
   426  	var tags []string
   427  	tagsF := v.FieldByName("Tags")
   428  	if tagsF.IsValid() {
   429  		tags = tagsF.Interface().([]string)
   430  	}
   431  
   432  	var useTags bool
   433  	useTagsF := v.FieldByName("UseTags")
   434  	if useTagsF.IsValid() {
   435  		useTags = useTagsF.Interface().(bool)
   436  	}
   437  
   438  	funcMap := FuncMapFunc(g.LanguageOpts)
   439  
   440  	pthTpl, err := template.New(t.Name + "-target").Funcs(funcMap).Parse(t.Target)
   441  	if err != nil {
   442  		return "", "", err
   443  	}
   444  
   445  	fNameTpl, err := template.New(t.Name + "-filename").Funcs(funcMap).Parse(t.FileName)
   446  	if err != nil {
   447  		return "", "", err
   448  	}
   449  
   450  	d := struct {
   451  		Name, Package, APIPackage, ServerPackage, ClientPackage, ModelPackage, MainPackage, Target string
   452  		Tags                                                                                       []string
   453  		UseTags                                                                                    bool
   454  		Context                                                                                    interface{}
   455  	}{
   456  		Name:          name,
   457  		Package:       pkg,
   458  		APIPackage:    g.APIPackage,
   459  		ServerPackage: g.ServerPackage,
   460  		ClientPackage: g.ClientPackage,
   461  		ModelPackage:  g.ModelPackage,
   462  		MainPackage:   g.MainPackage,
   463  		Target:        g.Target,
   464  		Tags:          tags,
   465  		UseTags:       useTags,
   466  		Context:       data,
   467  	}
   468  
   469  	var pthBuf bytes.Buffer
   470  	if e := pthTpl.Execute(&pthBuf, d); e != nil {
   471  		return "", "", e
   472  	}
   473  
   474  	var fNameBuf bytes.Buffer
   475  	if e := fNameTpl.Execute(&fNameBuf, d); e != nil {
   476  		return "", "", e
   477  	}
   478  	return pthBuf.String(), fileName(fNameBuf.String()), nil
   479  }
   480  
   481  func (g *GenOpts) render(t *TemplateOpts, data interface{}) ([]byte, error) {
   482  	var templ *template.Template
   483  
   484  	if strings.HasPrefix(strings.ToLower(t.Source), "asset:") {
   485  		tt, err := templates.Get(strings.TrimPrefix(t.Source, "asset:"))
   486  		if err != nil {
   487  			return nil, err
   488  		}
   489  		templ = tt
   490  	}
   491  
   492  	if templ == nil {
   493  		// try to load from repository (and enable dependencies)
   494  		name := swag.ToJSONName(strings.TrimSuffix(t.Source, ".gotmpl"))
   495  		tt, err := templates.Get(name)
   496  		if err == nil {
   497  			templ = tt
   498  		}
   499  	}
   500  
   501  	if templ == nil {
   502  		// try to load template from disk, in TemplateDir if specified
   503  		// (dependencies resolution is limited to preloaded assets)
   504  		var templateFile string
   505  		if g.TemplateDir != "" {
   506  			templateFile = filepath.Join(g.TemplateDir, t.Source)
   507  		} else {
   508  			templateFile = t.Source
   509  		}
   510  		content, err := ioutil.ReadFile(templateFile)
   511  		if err != nil {
   512  			return nil, fmt.Errorf("error while opening %s template file: %v", templateFile, err)
   513  		}
   514  		tt, err := template.New(t.Source).Funcs(FuncMapFunc(g.LanguageOpts)).Parse(string(content))
   515  		if err != nil {
   516  			return nil, fmt.Errorf("template parsing failed on template %s: %v", t.Name, err)
   517  		}
   518  		templ = tt
   519  	}
   520  
   521  	if templ == nil {
   522  		return nil, fmt.Errorf("template %q not found", t.Source)
   523  	}
   524  
   525  	var tBuf bytes.Buffer
   526  	if err := templ.Execute(&tBuf, data); err != nil {
   527  		return nil, fmt.Errorf("template execution failed for template %s: %v", t.Name, err)
   528  	}
   529  	log.Printf("executed template %s", t.Source)
   530  
   531  	return tBuf.Bytes(), nil
   532  }
   533  
   534  // Render template and write generated source code
   535  // generated code is reformatted ("linted"), which gives an
   536  // additional level of checking. If this step fails, the generated
   537  // code is still dumped, for template debugging purposes.
   538  func (g *GenOpts) write(t *TemplateOpts, data interface{}) error {
   539  	dir, fname, err := g.location(t, data)
   540  	if err != nil {
   541  		return fmt.Errorf("failed to resolve template location for template %s: %v", t.Name, err)
   542  	}
   543  
   544  	if t.SkipExists && fileExists(dir, fname) {
   545  		debugLog("skipping generation of %s because it already exists and skip_exist directive is set for %s",
   546  			filepath.Join(dir, fname), t.Name)
   547  		return nil
   548  	}
   549  
   550  	log.Printf("creating generated file %q in %q as %s", fname, dir, t.Name)
   551  	content, err := g.render(t, data)
   552  	if err != nil {
   553  		return fmt.Errorf("failed rendering template data for %s: %v", t.Name, err)
   554  	}
   555  
   556  	if dir != "" {
   557  		_, exists := os.Stat(dir)
   558  		if os.IsNotExist(exists) {
   559  			debugLog("creating directory %q for \"%s\"", dir, t.Name)
   560  			// Directory settings consistent with file privileges.
   561  			// Environment's umask may alter this setup
   562  			if e := os.MkdirAll(dir, 0755); e != nil {
   563  				return e
   564  			}
   565  		}
   566  	}
   567  
   568  	// Conditionally format the code, unless the user wants to skip
   569  	formatted := content
   570  	var writeerr error
   571  
   572  	if !t.SkipFormat {
   573  		formatted, err = g.LanguageOpts.FormatContent(filepath.Join(dir, fname), content)
   574  		if err != nil {
   575  			log.Printf("source formatting failed on template-generated source (%q for %s). Check that your template produces valid code", filepath.Join(dir, fname), t.Name)
   576  			writeerr = ioutil.WriteFile(filepath.Join(dir, fname), content, 0644) // #nosec
   577  			if writeerr != nil {
   578  				return fmt.Errorf("failed to write (unformatted) file %q in %q: %v", fname, dir, writeerr)
   579  			}
   580  			log.Printf("unformatted generated source %q has been dumped for template debugging purposes. DO NOT build on this source!", fname)
   581  			return fmt.Errorf("source formatting on generated source %q failed: %v", t.Name, err)
   582  		}
   583  	}
   584  
   585  	writeerr = ioutil.WriteFile(filepath.Join(dir, fname), formatted, 0644) // #nosec
   586  	if writeerr != nil {
   587  		return fmt.Errorf("failed to write file %q in %q: %v", fname, dir, writeerr)
   588  	}
   589  	return err
   590  }
   591  
   592  func fileName(in string) string {
   593  	ext := filepath.Ext(in)
   594  	return swag.ToFileName(strings.TrimSuffix(in, ext)) + ext
   595  }
   596  
   597  func (g *GenOpts) shouldRenderApp(t *TemplateOpts, app *GenApp) bool {
   598  	switch swag.ToFileName(swag.ToGoName(t.Name)) {
   599  	case "main":
   600  		return g.IncludeMain
   601  	case "embedded_spec":
   602  		return !g.ExcludeSpec
   603  	default:
   604  		return true
   605  	}
   606  }
   607  
   608  func (g *GenOpts) shouldRenderOperations() bool {
   609  	return g.IncludeHandler || g.IncludeParameters || g.IncludeResponses
   610  }
   611  
   612  func (g *GenOpts) renderApplication(app *GenApp) error {
   613  	log.Printf("rendering %d templates for application %s", len(g.Sections.Application), app.Name)
   614  	for _, templ := range g.Sections.Application {
   615  		if !g.shouldRenderApp(&templ, app) {
   616  			continue
   617  		}
   618  		if err := g.write(&templ, app); err != nil {
   619  			return err
   620  		}
   621  	}
   622  	return nil
   623  }
   624  
   625  func (g *GenOpts) renderOperationGroup(gg *GenOperationGroup) error {
   626  	log.Printf("rendering %d templates for operation group %s", len(g.Sections.OperationGroups), g.Name)
   627  	for _, templ := range g.Sections.OperationGroups {
   628  		if !g.shouldRenderOperations() {
   629  			continue
   630  		}
   631  
   632  		if err := g.write(&templ, gg); err != nil {
   633  			return err
   634  		}
   635  	}
   636  	return nil
   637  }
   638  
   639  func (g *GenOpts) renderOperation(gg *GenOperation) error {
   640  	log.Printf("rendering %d templates for operation %s", len(g.Sections.Operations), g.Name)
   641  	for _, templ := range g.Sections.Operations {
   642  		if !g.shouldRenderOperations() {
   643  			continue
   644  		}
   645  
   646  		if err := g.write(&templ, gg); err != nil {
   647  			return err
   648  		}
   649  	}
   650  	return nil
   651  }
   652  
   653  func (g *GenOpts) renderDefinition(gg *GenDefinition) error {
   654  	log.Printf("rendering %d templates for model %s", len(g.Sections.Models), gg.Name)
   655  	for _, templ := range g.Sections.Models {
   656  		if !g.IncludeModel {
   657  			continue
   658  		}
   659  
   660  		if err := g.write(&templ, gg); err != nil {
   661  			return err
   662  		}
   663  	}
   664  	return nil
   665  }
   666  
   667  func (g *GenOpts) setTemplates() error {
   668  	templates.LoadDefaults()
   669  
   670  	if g.Template != "" {
   671  		// set contrib templates
   672  		if err := templates.LoadContrib(g.Template); err != nil {
   673  			return err
   674  		}
   675  	}
   676  
   677  	templates.SetAllowOverride(g.AllowTemplateOverride)
   678  
   679  	if g.TemplateDir != "" {
   680  		// set custom templates
   681  		if err := templates.LoadDir(g.TemplateDir); err != nil {
   682  			return err
   683  		}
   684  	}
   685  	return nil
   686  }
   687  
   688  // defaultImports produces a default map for imports with models
   689  func (g *GenOpts) defaultImports() map[string]string {
   690  	baseImport := g.LanguageOpts.baseImport(g.Target)
   691  	defaultImports := make(map[string]string, 50)
   692  
   693  	if g.ExistingModels == "" {
   694  		// generated models
   695  		importPath := path.Join(
   696  			baseImport,
   697  			g.LanguageOpts.ManglePackagePath(g.ModelPackage, defaultModelsTarget))
   698  		defaultImports[g.LanguageOpts.ManglePackageName(g.ModelPackage, defaultModelsTarget)] = importPath
   699  	} else {
   700  		// external models
   701  		importPath := g.LanguageOpts.ManglePackagePath(g.ExistingModels, "")
   702  		defaultImports["models"] = importPath
   703  	}
   704  
   705  	alias, _, target := g.resolvePrincipal()
   706  	if alias != "" {
   707  		if pth, _ := path.Split(target); pth != "" {
   708  			// if principal is specified with an path, generate this import
   709  			defaultImports[alias] = target
   710  		} else {
   711  			// if principal is specified with a relative path, assume it is located in generated target
   712  			defaultImports[alias] = path.Join(baseImport, target)
   713  		}
   714  	}
   715  	return defaultImports
   716  }
   717  
   718  // initImports produces a default map for import with the specified root for operations
   719  func (g *GenOpts) initImports(operationsPackage string) map[string]string {
   720  	baseImport := g.LanguageOpts.baseImport(g.Target)
   721  
   722  	imports := make(map[string]string, 50)
   723  	imports[g.LanguageOpts.ManglePackageName(operationsPackage, defaultOperationsTarget)] = path.Join(
   724  		baseImport,
   725  		g.LanguageOpts.ManglePackagePath(operationsPackage, defaultOperationsTarget))
   726  	return imports
   727  }
   728  
   729  // PrincipalAlias returns an aliased type to the principal
   730  func (g *GenOpts) PrincipalAlias() string {
   731  	_, principal, _ := g.resolvePrincipal()
   732  	return principal
   733  }
   734  
   735  func (g *GenOpts) resolvePrincipal() (string, string, string) {
   736  	dotLocation := strings.LastIndex(g.Principal, ".")
   737  	if dotLocation < 0 {
   738  		return "", g.Principal, ""
   739  	}
   740  
   741  	// handle possible conflicts with injected principal package
   742  	// NOTE(fred): we do not check here for conflicts with packages created from operation tags, only standard imports
   743  	alias := deconflictPrincipal(importAlias(g.Principal[:dotLocation]))
   744  	return alias, alias + g.Principal[dotLocation:], g.Principal[:dotLocation]
   745  }
   746  
   747  func fileExists(target, name string) bool {
   748  	_, err := os.Stat(filepath.Join(target, name))
   749  	return !os.IsNotExist(err)
   750  }
   751  
   752  func gatherModels(specDoc *loads.Document, modelNames []string) (map[string]spec.Schema, error) {
   753  	modelNames = pruneEmpty(modelNames)
   754  	models, mnc := make(map[string]spec.Schema), len(modelNames)
   755  	defs := specDoc.Spec().Definitions
   756  
   757  	if mnc > 0 {
   758  		var unknownModels []string
   759  		for _, m := range modelNames {
   760  			_, ok := defs[m]
   761  			if !ok {
   762  				unknownModels = append(unknownModels, m)
   763  			}
   764  		}
   765  		if len(unknownModels) != 0 {
   766  			return nil, fmt.Errorf("unknown models: %s", strings.Join(unknownModels, ", "))
   767  		}
   768  	}
   769  	for k, v := range defs {
   770  		if mnc == 0 {
   771  			models[k] = v
   772  		}
   773  		for _, nm := range modelNames {
   774  			if k == nm {
   775  				models[k] = v
   776  			}
   777  		}
   778  	}
   779  	return models, nil
   780  }
   781  
   782  // titleOrDefault infers a name for the app from the title of the spec
   783  func titleOrDefault(specDoc *loads.Document, name, defaultName string) string {
   784  	if strings.TrimSpace(name) == "" {
   785  		if specDoc.Spec().Info != nil && strings.TrimSpace(specDoc.Spec().Info.Title) != "" {
   786  			name = specDoc.Spec().Info.Title
   787  		} else {
   788  			name = defaultName
   789  		}
   790  	}
   791  	return swag.ToGoName(name)
   792  }
   793  
   794  func mainNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
   795  	// *_test won't do as main server name
   796  	return strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test")
   797  }
   798  
   799  func appNameOrDefault(specDoc *loads.Document, name, defaultName string) string {
   800  	// *_test won't do as app names
   801  	name = strings.TrimSuffix(titleOrDefault(specDoc, name, defaultName), "Test")
   802  	if name == "" {
   803  		name = swag.ToGoName(defaultName)
   804  	}
   805  	return name
   806  }
   807  
   808  type opRef struct {
   809  	Method string
   810  	Path   string
   811  	Key    string
   812  	ID     string
   813  	Op     *spec.Operation
   814  }
   815  
   816  type opRefs []opRef
   817  
   818  func (o opRefs) Len() int           { return len(o) }
   819  func (o opRefs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
   820  func (o opRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }
   821  
   822  func gatherOperations(specDoc *analysis.Spec, operationIDs []string) map[string]opRef {
   823  	operationIDs = pruneEmpty(operationIDs)
   824  	var oprefs opRefs
   825  
   826  	for method, pathItem := range specDoc.Operations() {
   827  		for path, operation := range pathItem {
   828  			vv := *operation
   829  			oprefs = append(oprefs, opRef{
   830  				Key:    swag.ToGoName(strings.ToLower(method) + " " + strings.Title(path)),
   831  				Method: method,
   832  				Path:   path,
   833  				ID:     vv.ID,
   834  				Op:     &vv,
   835  			})
   836  		}
   837  	}
   838  
   839  	sort.Sort(oprefs)
   840  
   841  	operations := make(map[string]opRef)
   842  	for _, opr := range oprefs {
   843  		nm := opr.ID
   844  		if nm == "" {
   845  			nm = opr.Key
   846  		}
   847  
   848  		oo, found := operations[nm]
   849  		if found && oo.Method != opr.Method && oo.Path != opr.Path {
   850  			nm = opr.Key
   851  		}
   852  		if len(operationIDs) == 0 || swag.ContainsStrings(operationIDs, opr.ID) || swag.ContainsStrings(operationIDs, nm) {
   853  			opr.ID = nm
   854  			opr.Op.ID = nm
   855  			operations[nm] = opr
   856  		}
   857  	}
   858  
   859  	return operations
   860  }
   861  
   862  func pruneEmpty(in []string) (out []string) {
   863  	for _, v := range in {
   864  		if v != "" {
   865  			out = append(out, v)
   866  		}
   867  	}
   868  	return
   869  }
   870  
   871  func trimBOM(in string) string {
   872  	return strings.Trim(in, "\xef\xbb\xbf")
   873  }
   874  
   875  // gatherSecuritySchemes produces a sorted representation from a map of spec security schemes
   876  func gatherSecuritySchemes(securitySchemes map[string]spec.SecurityScheme, appName, principal, receiver string) (security GenSecuritySchemes) {
   877  	for scheme, req := range securitySchemes {
   878  		isOAuth2 := strings.ToLower(req.Type) == "oauth2"
   879  		var scopes []string
   880  		if isOAuth2 {
   881  			for k := range req.Scopes {
   882  				scopes = append(scopes, k)
   883  			}
   884  		}
   885  		sort.Strings(scopes)
   886  
   887  		security = append(security, GenSecurityScheme{
   888  			AppName:      appName,
   889  			ID:           scheme,
   890  			ReceiverName: receiver,
   891  			Name:         req.Name,
   892  			IsBasicAuth:  strings.ToLower(req.Type) == "basic",
   893  			IsAPIKeyAuth: strings.ToLower(req.Type) == "apikey",
   894  			IsOAuth2:     isOAuth2,
   895  			Scopes:       scopes,
   896  			Principal:    principal,
   897  			Source:       req.In,
   898  			// from original spec
   899  			Description:      req.Description,
   900  			Type:             strings.ToLower(req.Type),
   901  			In:               req.In,
   902  			Flow:             req.Flow,
   903  			AuthorizationURL: req.AuthorizationURL,
   904  			TokenURL:         req.TokenURL,
   905  			Extensions:       req.Extensions,
   906  		})
   907  	}
   908  	sort.Sort(security)
   909  	return
   910  }
   911  
   912  // gatherExtraSchemas produces a sorted list of extra schemas.
   913  //
   914  // ExtraSchemas are inlined types rendered in the same model file.
   915  func gatherExtraSchemas(extraMap map[string]GenSchema) (extras GenSchemaList) {
   916  	var extraKeys []string
   917  	for k := range extraMap {
   918  		extraKeys = append(extraKeys, k)
   919  	}
   920  	sort.Strings(extraKeys)
   921  	for _, k := range extraKeys {
   922  		// figure out if top level validations are needed
   923  		p := extraMap[k]
   924  		p.HasValidations = shallowValidationLookup(p)
   925  		extras = append(extras, p)
   926  	}
   927  	return
   928  }
   929  
   930  func sharedValidationsFromSimple(v spec.CommonValidations, isRequired bool) (sh sharedValidations) {
   931  	sh = sharedValidations{
   932  		Required:         isRequired,
   933  		Maximum:          v.Maximum,
   934  		ExclusiveMaximum: v.ExclusiveMaximum,
   935  		Minimum:          v.Minimum,
   936  		ExclusiveMinimum: v.ExclusiveMinimum,
   937  		MaxLength:        v.MaxLength,
   938  		MinLength:        v.MinLength,
   939  		Pattern:          v.Pattern,
   940  		MaxItems:         v.MaxItems,
   941  		MinItems:         v.MinItems,
   942  		UniqueItems:      v.UniqueItems,
   943  		MultipleOf:       v.MultipleOf,
   944  		Enum:             v.Enum,
   945  	}
   946  	return
   947  }
   948  
   949  func sharedValidationsFromSchema(v spec.Schema, isRequired bool) (sh sharedValidations) {
   950  	sh = sharedValidations{
   951  		Required:         isRequired,
   952  		Maximum:          v.Maximum,
   953  		ExclusiveMaximum: v.ExclusiveMaximum,
   954  		Minimum:          v.Minimum,
   955  		ExclusiveMinimum: v.ExclusiveMinimum,
   956  		MaxLength:        v.MaxLength,
   957  		MinLength:        v.MinLength,
   958  		Pattern:          v.Pattern,
   959  		MaxItems:         v.MaxItems,
   960  		MinItems:         v.MinItems,
   961  		UniqueItems:      v.UniqueItems,
   962  		MultipleOf:       v.MultipleOf,
   963  		Enum:             v.Enum,
   964  	}
   965  	return
   966  }
   967  
   968  func dumpData(data interface{}) error {
   969  	bb, err := json.MarshalIndent(data, "", "  ")
   970  	if err != nil {
   971  		return err
   972  	}
   973  	fmt.Fprintln(os.Stdout, string(bb))
   974  	return nil
   975  }
   976  
   977  func importAlias(pkg string) string {
   978  	_, k := path.Split(pkg)
   979  	return k
   980  }